九, Java实现哈希表(拉链法, 数组+单链表)
九, 哈希表
9.1 哈希表的定义和特点
散列表(Hash table, 也叫哈希表),是根据关键码 - 值(Key - value)而直接进行访问的数据结构。 也就是说, 它通过把关键码 - 值映射到表中一个位置来访问记录, 以加快查找的速度。这个映射的函数叫做散列函数,存放记录的数组叫做散列表。
- 其实把哈希表看做是字典来理解哈希表就很容易明白了,我们通过关键码即可快速定位关键值。显而易见哈希表有一个很大的优点就是查找数据的速度快。
- 下图就是一个典型的哈希表(数组+链表实现的,使用拉链法解决冲突)
9.2 哈希表的一些实际应用
根据散列表的特点可以想到,散列表比较适合无序、需要快速访问的情况
- 用作缓存, 提升数据的查找速度, 减轻数据库的压力
9.3 一个简单哈希表(数组+单链表)的实现
下面以 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;
}
本案例中要实现一个哈希表,需要三个类:一个类作为结点(EmployeeNode)类存放雇员的个人信息、一个类作为链表类(EmployeeLinkedList),具体实现对结点的增删改查等操作, 一个类作为哈希表类(EmployeeHashTableImp),通过散列函数把链表分配到数组中, 并使用链表类的增删改查方法. 注意: 增删改查的具体操作代码,是要通过链表类来实现
。
代码结构图如下:
哈希表(数组+单链表)的应用实例代码实现:
- 雇员结点类(管理雇员链表的指针域, 初始化和输出雇员信息)
public class EmployeeNode {
//结点类, 存放雇员的具体信息
int id;
String name;
int salary;
EmployeeNode next;
//构造方法和toString()
public EmployeeNode(int id, String name, int salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "EmployeeNode{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
- 雇员链表类(操纵链表结点, 具体实现对链表结点的添加, 查找,删除, 遍历操作)
public class EmployeeLinkedList {
//链表操作结点, 实现对雇员的添加, 查找, 删除,
//头结点
EmployeeNode head = null;
//添加雇员
public void add(EmployeeNode node){
//头结点为空, 直接添加
if(head == null){
head = node;
return;
}
//尾插法实现链表新结点的添加
//临时结点
EmployeeNode temp = head;
while(true){
//跳出循环条件
if( temp.next==null){
break;
}
temp = temp.next;
}
temp.next = node;
///通过debug我们可以知道, 如果不把链表最后一个结点的指针域置空\
//会进入node.next=node的无限循环
node.next = null;
}
//查找结点
public EmployeeNode findById(int id){
if(head == null){
System.out.println("链表为空, 查找失败");
return null;
}
EmployeeNode temp = head;
while(true){
//找到了, 返回
if(temp.id == id){
break;
}
temp = temp.next;
//遍历到末尾都没找到
if(temp == null){
break;
}
}
return temp;
}
//删除结点
public void delById(int id){
//判空
if(head == null){
System.out.println("链表为空, 删除失败");
return;
}
//1. 删除的结点为头结点
if(head.id == id){
head = head.next;
System.out.printf("id=%d的结点删除成功! \n ",id);
return;
}
//2. 删除的结点非头结点
//临时结点
EmployeeNode temp = head;
while(true){
//删除结点必须知道被删除结点的前驱结点, 所以我们使用 temp.next查找要删除的结点
if(temp.next == null){
System.out.println("id="+id+"的结点不存在或已经被删除!");
break;
}
if(temp.next.id == id){
temp.next = temp.next.next;
System.out.printf("id=%d的结点删除成功! \n ",id);
break;
}
temp = temp.next;
}
}
//遍历结点
public void list(){
if(head == null){
System.out.print("链表为空");
return;
}
//临时结点
EmployeeNode temp = head;
while(true){
if(temp == null) break;
System.out.printf("\t"+temp + "===>");
temp = temp.next;
}
}
}
- 哈希表的操作类(借助数组管理多条链表, 通过散列函数去使用链表类提供的添加, 查找, 删除, 遍历操作)
public class EmployeeHashTableImp {
//对链表的增删改查和散列到数组
EmployeeLinkedList[] hashArr; //存储链表的哈希数组
private static int maxsize;
public EmployeeHashTableImp(int maxsize){
this.maxsize = maxsize;
hashArr = new EmployeeLinkedList[maxsize];
/注意:
初始化数组, 因为数组的内容为自定义类型,
// 所以必须用自定义类型初始化每一个index
for(int i=0; i< maxsize; i++){
hashArr[i] = new EmployeeLinkedList();
}
}
//散列函数
public static int hashFun(int id){
return id%maxsize;
}
//添加结点到链表
public void add(EmployeeNode node){
//找到链表在数组中的索引
int index = hashFun(node.id);
//添加结点到链表
hashArr[index].add(node);
}
//查询雇员信息
/**
* 1. 需要雇员id, 根据id计算散列值
* 2. 根据散列值找到链表
* 3. 使用链表的查询
*/
public void findById(int id){
//根据散列函数找到链表的位置
int index = hashFun(id);
EmployeeNode employee = hashArr[index].findById(id);
if(employee != null){
System.out.println("找到了 id="+id+"的具体信息为: "+employee);
}else{
System.out.println("未找到id="+id+"的信息");
}
}
//删除雇员信息
public void delById(int id){
//找到链表在数组的索引
int index = hashFun(id);
hashArr[index].delById(id);
}
//打印所有雇员的信息
public void list(){
for(int i=0; i<hashArr.length; i++){
System.out.println("第"+i+"号链表的信息为: ");
hashArr[i].list();
System.out.println();
}
}
//测试方法
public static void main(String[] args) {
EmployeeHashTableImp hashtable = new EmployeeHashTableImp(7);
//雇员结点
EmployeeNode node1 = new EmployeeNode(1,"xiaowang",22442);
EmployeeNode node2 = new EmployeeNode(2, "xiaogou", 3545);
EmployeeNode node3 = new EmployeeNode(3, "lisi", 46464);
EmployeeNode node4 = new EmployeeNode(4, "mazi",3553);
EmployeeNode node5 = new EmployeeNode(5, "zhangsan",5757);
EmployeeNode node6 = new EmployeeNode(6, "qingwa",253535);
EmployeeNode node7 = new EmployeeNode(7, "sanbing",4646);
EmployeeNode node8 = new EmployeeNode(8, "xiaop",24255);
EmployeeNode node9 = new EmployeeNode(9, "duzi",4646);
EmployeeNode node10 = new EmployeeNode(10, "wangba",464635);
///添加 到哈希表
hashtable. add(node1);
hashtable. add(node2);
hashtable. add(node3);
hashtable. add(node4);
hashtable. add(node5);
hashtable. add(node6);
hashtable. add(node7);
hashtable. add(node8);
hashtable. add(node9);
hashtable. add(node10);
//打印出整体的哈希表
hashtable.list();
System.out.println();
System.out.println("============================");
//查询雇员信息
System.out.println("1. 查询雇员 id=3的详细信息");
hashtable.findById(3);
System.out.println("查询雇员 id=7的详细信息");
hashtable.findById(7);
hashtable.findById(15);
hashtable.findById(8);
//删除雇员信息
System.out.println("================");
System.out.println("2. 删除雇员id=1的信息");
hashtable.delById(1);
System.out.println("删除雇员id=5的信息");
hashtable.delById(5);
System.out.println("删除雇员id=9的信息");
hashtable.delById(9);
System.out.println("删除雇员id=9的信息");
hashtable.delById(9);
System.out.println("删除雇员id=2的信息");
hashtable.delById(2);
System.out.println("删除雇员id=7的信息");
hashtable.delById(7);
//打印雇员信息
System.out.println("================");
hashtable.list();
}
}
代码运行结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)