数据结构与算法之哈希表结构

一、概念

也称散列表,是指根据关键码值(key-value)而直接进行访问的数据结构,也就是说它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表,也称哈希表。

二、一句话概括

哈希表就是由数组和链表二者结合而成的新型数据结构。数组存放key(经过hash计算后生成的存放key的数组位置),链表存放value。

三、图解

在这里插入图片描述

hash算法有很多,比如最简单的哈希算法就是用存放的值去取模数组长度。比如存放元素20,那就是20%5=0,所以20这个元素就存放在数组下标为0的位置,再比如4%5=4,所以元素4存放在数组下标为4的位置。
回想下Java的hashmap,典型的key-value,那么我们put的时候是存放到哪了呢?其实就是key的hash算法得到一个数组下标位置,将其存进去。当然比我说的复杂的多,但是大致原理确是如此。
当我们getKey的时候,首先会根据key进行hash算法得到数组下标位置,查找时间复杂度为O(1)。

四、哈希碰撞

多个key经过hash算法后得到的是同一个值(同一个数组下标),这就称之为哈希碰撞。这时候可以将他们的值都存放在链表中。(毕竟链表是拉链式的,可以往后next next next)

五、Google上机题

Google机试题。
就是让你存储员工信息,员工包含id和name,要求能高效率的根据id迅速查找到对应的记录,题目额外要求不能用mysql、redis、hashmap等存储介质。

分析:很明显,这道题是再考我们哈希表结构,看我们是否懂hashmap内部存储的原理以及结构是怎样的。
思路:首先要有员工类、员工链表类、hashtable类(管理多条链表的类,也就是链表数组类)。总的思路就是根据员工id进行hash算法定位到存储的位置,然后将员工类对象信息存储到上一步定位的位置的链表中。

六、coding

package com.chentongwei.struct.hashtable;

import java.util.Scanner;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-18 15:48:01
 */
public class HashTableDemo {
    public static void main(String[] args) {
        HashTable hashTable = new HashTable(7);
        String key = "";
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("add:添加雇员");
            System.out.println("list:遍历雇员");
            System.out.println("find:查找雇员");
            System.out.println("exit:退出系统");

            key = scanner.next();
            switch (key) {
                case "add" :
                    System.out.println("请输入id");
                    int id = scanner.nextInt();
                    System.out.println("输入名字");
                    String name = scanner.next();

                    // 创建雇员
                    Emp emp = new Emp(id, name);
                    hashTable.add(emp);
                    break;
                case "list":
                    hashTable.list();
                    break;
                case "find":
                    System.out.println("请输入id");
                    int findId = scanner.nextInt();
                    hashTable.findEmpById(findId);
                    break;
                case "exit":
                    scanner.close();
                    System.exit(0);
                default:
                    break;
            }
        }
    }
}

// 创建HashTable,管理多条链表
class HashTable {
    private EmpLinkedList[] empLinkedListArray;
    // 表示有多少条链表
    private int size;

    public HashTable(int size) {
        // 初始化链表
        empLinkedListArray = new EmpLinkedList[size];
        this.size = size;
        for (int i = 0; i < empLinkedListArray.length; i ++) {
            empLinkedListArray[i] = new EmpLinkedList();
        }
    }

    // 添加雇员
    public void add (Emp emp) {
        // 根据员工id,得到该员工应该添加到哪条链表上
        int empLinkedListNo = hash(emp.id);
        // 将emp加入到对应的链表中
        empLinkedListArray[empLinkedListNo].add(emp);
    }

    // 遍历所有链表,遍历hash表
    public void list () {
        for (int i = 0; i < size; i ++) {
            empLinkedListArray[i].list(i);
        }
    }

    // 输入id查找emp
    public void findEmpById(int no) {
        int hash = hash(no);
        Emp emp = empLinkedListArray[hash].findEmpById(no);
        if (emp != null) {
            System.out.printf("在第%d条链表中找到该雇员, id = %d\n", (hash + 1), no);
        } else {
            System.out.println("再哈希表中没找到该雇员");
        }
    }

    // 编写一个散列函数,采取最简单的取模法
    public int hash (int id) {
        return id % size;
    }
}

// 创建链表
class EmpLinkedList {
    // 头指针,指向第一个Emp,因此我们这个链表的head是直接指向第一个Emp
    private Emp head;

    // 添加雇员到链表
    // 说明:
    // 1.假定添加雇员就直接往链表后面追加,不考虑按照雇员id排序
    // 2.因此我们将该雇员直接加入到本链表的最后一个即可
    public void add (Emp emp) {
        if (head == null) {
            head = emp;
            return;
        }
        // 辅助指针帮助我们定位到链表最后
        Emp tmp = head;
        while (true) {
            if (tmp.next == null) {
                break;
            }
            //  后移一位,直到最后
            tmp = tmp.next;
        }
        // 加入到链表最后
        tmp.next = emp;
    }

    // 遍历链表的雇员信息
    public void list (int no) {
        if (head == null) {
            System.out.println("第" + (no + 1) + "条链表为空");
            return;
        }
        Emp tmp = head;
        System.out.print("第" + (no + 1) + "条链表的信息为:");
        while (true) {
            System.out.printf("=> id = %d name=%s\t", tmp.id, tmp.name);
            if (tmp.next == null) {
                break;
            }
            tmp = tmp.next;
        }
        System.out.println();
    }

	// 根据id查找员工信息
    public Emp findEmpById (int id) {
        // 链表为空
        if (head == null) {
            System.out.println("链表为空");
            return null;
        }
        // 创建辅助指针
        Emp tmp = head;
        while (true) {
        	// 若找到了则break
            if (tmp.id == id) {
                break;
            }
            // 代表遍历完当前链表没找到该雇员
            if (tmp.next == null) {
                tmp = null;
                break;
            }
            tmp = tmp.next;
        }
        return tmp;
    }

}
// 充当链表中的Node节点的作用
class Emp {
    public int id;
    public String name;
    public Emp next;

    public Emp(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

结果
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
add
请输入id
0
输入名字
tom
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
add
请输入id
1
输入名字
jim
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
add
请输入id
2
输入名字
pom
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
add
请输入id
3
输入名字
angle
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
add
请输入id
5
输入名字
haha
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
list
第1条链表的信息为:=> id = 0 name=tom => id = 5 name=haha
第2条链表的信息为:=> id = 1 name=jim
第3条链表的信息为:=> id = 2 name=pom
第4条链表的信息为:=> id = 3 name=angle
第5条链表为空
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统
find
请输入id
5
在第1条链表中找到该雇员, id = 5
add:添加雇员
list:遍历雇员
find:查找雇员
exit:退出系统

发布了28 篇原创文章 · 获赞 33 · 访问量 8301

猜你喜欢

转载自blog.csdn.net/ctwctw/article/details/103604666