一、哈希表概述
散列表(Hash table, 也叫哈希表),是根据关键码 - 值(Key - value)而直接进行访问的数据结构。 也就是说, 它通过把关键码 - 值映射到表中一个位置来访问记录, 以加快查找的速度。这个映射的函数叫做散列函数,存放记录的数组叫做散列表。
其实把哈希表看做是字典来理解哈希表就很容易明白了,我们通过关键码即可快速定位关键值。显而易见哈希表有一个很大的优点就是查找数据的速度快。
如下所示就是一个典型的哈希表:
哈希表是数组和链表的结合体,上图的哈希表左边是一个数组,右边是链表,即数组中的每个元素都是一个链表。一个新的结点具体添加到哪个链表中是由映射关系来决定的。同样,我们如果想要查找某个结点,只需要通对结点的关键码进行映射关系运算,计算出在数组的第几个元素(即哪个链表),然后遍历链表比对关键码即可得出结果。
从典型的哈希表结构中,我们可以得出这样的结论:本质上哈希表是一个元素为链表的数组。
二、哈希表的代码实现
下面以 google 的一个上机题为例来做代码实现。
【案例描述】
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id、名字、…),当输入该员工的 id 时,要求查找到该员工的所有信息。要求不使用数据库,速度越快越好。
【思路分析】
要求不使用数据库,而且是通过员工 id 来查询员工的所有信息,很明显可以使用哈希表来解决。
在本例中,员工的 id 是关键码,我们可以对员工的 id 取余来作为到链表的映射。假设数组的长度是 5,比如 id 取余 5 等于 0 的员工都存放到第一个链表中(如 5、15…)、再比如 id 取余5 等于 2 的都存放到第 3 个链表中(如 2、7、12…)。其实现代码如下:
// 模拟获取哈希值,该值决定结点要存放到数组的第几个链表
public int hashFun(int id){
return id % size;
}
本案例中要实现一个哈希表,需要三个类:一个类作为结点类存放雇员的个人信息、一个类作为链表类,用于记录相同余数的雇员结点、一个类作为哈希表类。通过哈希表类可以实现对任意结点(也即员工)进行增删改查,而增删改查的具体操作也就是对结点的具体操作,则是要通过链表类来实现。
【代码实现】
本案例完整的哈希表代码实现如下:
public class No1_HashTable {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
MyHashTable hashTable = new MyHashTable(6); // 创建哈希表
while (true){
System.out.println();
System.out.println("(add)添加雇员");
System.out.println("(ser)查找雇员");
System.out.println("(lis)显示雇员");
System.out.println("(del)删除雇员");
System.out.println("(ext)退出");
System.out.print("请输入选项:");
String choice = input.nextLine();
switch (choice){
case "add": // 添加
System.out.print("请输入ID:");
int id = Integer.parseInt(input.nextLine());
System.out.print("请输入姓名:");
String name = input.nextLine();
hashTable.addEmp(new Employee(id, name));
break;
case "ser": // 查找
System.out.print("请输入要查找ID:");
hashTable.findById(Integer.parseInt(input.nextLine()));
break;
case "lis": // 显示
hashTable.list();
break;
case "del": // 删除
System.out.print("请输入要删除ID:");
hashTable.deleteById(Integer.parseInt(input.nextLine()));
break;
case "ext": // 退出
break;
default:
System.out.println("输入有误!");
}
}
}
}
/**
* 定义一个哈希表
*/
class MyHashTable{
private int size;
private EmployeeLinkedList[] empList;
public MyHashTable(int maxSize){
size = maxSize;
empList = new EmployeeLinkedList[maxSize];
for (int i=0; i<maxSize; i++){ // 数组中存放的不是基本类型时,必须初始化数组
empList[i] = new EmployeeLinkedList();
}
}
// 添加一个结点到哈希表中
public void addEmp(Employee emp){
int index = hashFun(emp.id);
empList[index].addEmp(emp);
}
// 显示所有结点
public void list(){
for (int i=0; i<size; i++){
System.out.printf("第 %d 个链表为:",i);
empList[i].list();
System.out.println();
}
}
// 根据 id 查找某个结点
public void findById(int id){
int index = hashFun(id);
empList[index].findById(id);
}
// 根据 id 删除某个结点
public void deleteById(int id){
int index = hashFun(id);
empList[index].deleteById(id);
}
// 模拟获取哈希值,该值决定结点要存放到数组的第几个链表
public int hashFun(int id){
return id % size;
}
}
/**
* 定义一个雇员链表
*/
class EmployeeLinkedList{
private Employee head = null; // 这里的头结点是有意义的,是一个真实的雇员
// 添加结点
public void addEmp(Employee employee){
if (head == null){ // 判断链表是否为空
head = employee;
return;
}
Employee cur = head; // 辅助指针
while(cur.next != null){ // 找到最后一个结点
cur = cur.next;
}
cur.next = employee;
employee.next = null;
}
// 通过 id 查找结点
public void findById(int id){
if (head == null){ // 判断链表是否为空
System.out.printf("链表为空, ID 为 %d 的结点不存在!", id);
return;
}
Employee cur = head; // 第一个结点不能动,要用辅助指针
while (cur != null){
if (cur.id == id){
System.out.printf("找到了!该结点信息为:ID = %d,NAME = %s", id, head.name);
return;
}
cur = cur.next;
}
System.out.println("该结点不存在!");
}
// 通过 id 删除结点
public void deleteById(int id){
if (head == null){
System.out.println("链表为空,无法删除!");
return;
}
// 因为 head 结点也是一个有效结点,他没有前驱,所以要单独判断
if (head.id == id){ // 如果第一个结点是目标结点
head = head.next;
return;
}
// head 之后的节点都是有前驱结点的结点
Employee cur = head; // 辅助指针
while (true){
if (cur.next == null){ // 如果判断到了最后一个结点还是没有找到
System.out.println("该结点不存在!");
return;
}
if (cur.next.id == id){
cur.next = cur.next.next;
return;
}
cur = cur.next;
}
}
// 显示所有结点
public void list(){
if (head == null){
System.out.print("链表为空!");
return;
}
Employee cur = head; // 辅助指针
while (cur != null){
System.out.printf(" =>ID:%d,NAME:%s", cur.id, cur.name);
cur = cur.next;
}
}
}
/**
* 定义一个雇员结点
*/
class Employee{
public int id;
public String name;
public Employee next;
public Employee(int id, String name){
this.id = id;
this.name = name;
}
}