一、概念
也称散列表,是指根据关键码值(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:退出系统