9.2哈希表的基本介绍
哈希表的基本介绍:
散列表(Hash table,也叫哈希表), 是根据关键码值(Key value)而直接进 行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表
链式哈希表的结构:
哈希表实质上就是一个数组,数组的每一个元素里面存放的是一条链表(也就是一个链表数组)。在存入数据时,根据要存入数据的id求出该数据应该存放在哪一条链表之上(通常是采用求模的方法:id % 数组的长度),之后将该数据加入到对应的链表之后,这样哈希表就实现了
9.3谷歌公司的一道上机题的实现
google公司的一个上机题: 有一个公司,当有新的员工来报道时,要求将该员工的信息加入 (id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工的所有信息,要求:不使用数据库,速度越快越好=>哈希表(散列)
package com.atguigu08.hashtable;
import java.util.Scanner;
/**
* @author peng
* @date 2021/11/24 - 10:44
*/
public class HashTableDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
HashTable hashTable = new HashTable(7);
//编写一个简单的菜单
String order = "";
boolean loop = true;
while (loop) {
System.out.println("请选择以下的指令:");
System.out.println("add:添加员工");
System.out.println("show:员工信息");
System.out.println("find:查找指定的员工");
System.out.println("delete:删除指定的员工");
System.out.println("exit:退出程序");
System.out.println("请输入您的指令:");
order = scanner.next();
switch (order) {
case "add":
System.out.println("请输入员工的编号:");
int id = scanner.nextInt();
System.out.println("请输入员工的姓名:");
String name = scanner.next();
Emp emp = new Emp(id, name);
hashTable.hashTableAddEmp(emp);
break;
case "show":
hashTable.hashTableShowList();
break;
case "find":
System.out.println("请输入要查找员工的编号:");
int number = scanner.nextInt();
hashTable.hashTablefindEmpById(number);
break;
case "delete":
System.out.println("请输入要删除的员工编号:");
id = scanner.nextInt();
hashTable.hashTableDeleteEmpById(id);
break;
case "exit":
loop = false;
break;
default:
break;
}
}
}
}
/**
* 编写哈希表
*/
class HashTable {
//哈希表中放的是一条条的链表,所以首先要创建一个链表数组
private EmpLinkedList[] empLinkedLists;
public int size;//定义链表数组的长度
//初始化链表数组
public HashTable(int size) {
this.size = size;//将链表数组的长度传进来
empLinkedLists = new EmpLinkedList[size];//初始化链表数组
//此时虽然数组已经创建好了,但是里面的内容还是空的,因此需要分别初始化每一条链表
//这里很重要:在使用hash表的时候经常会遗忘!
for (int i = 0; i < size; i++) {
empLinkedLists[i] = new EmpLinkedList();
}
}
//定义一个方法实现:根据员工的编号找到员工对应的链表
public int hashFun(int id) {
//最常用的方法就是求模
return id % size;
}
//定义一个方法实现:将添加员工到链表之上
public void hashTableAddEmp(Emp emp) {
//在链表之中也定义了一个添加员工的方法,那为什么在hashTable中又要定义一个相同功能的方法呢?
//原因是:在实际的操作者的眼里,操作的是HashTable而不是底层的链表,相当于通过HashTable去操控链表实现添加员工
int empLinkedListNo = hashFun(emp.id);//根据id找到当前员工应该加在哪一条链表之下
empLinkedLists[empLinkedListNo].addEmp(emp);//将员工加到对应的链表之下
}
//定义一个方法实现:遍历所有的链表
public void hashTableShowList() {
for (int i = 0; i < size; i++) {
empLinkedLists[i].showEmp(i);//hashTable通过调用链表的showEmp方法实现遍历链表
}
}
//定义一个方法实现:根据id查找指定的员工
public void hashTablefindEmpById(int id) {
//在老师的版本中是根据散列函数找出应该在哪一条的链表上进行查找,但是我的做法是,全部的链表都找 了一遍
for (int i = 0; i < size; i++) {
empLinkedLists[i].findEmpById(id, i);
}
}
//定义一个方法实现:删除指定id的员工
public void hashTableDeleteEmpById(int id) {
//根据散列值找到对应的链表去删除
int deleteId = hashFun(id);
empLinkedLists[deleteId].deleteEmpById(id);
}
}
/**
* 创建一个雇员节点
*/
class Emp {
public int id;//员工编号
public String name;//员工姓名
com.atguigu08.hashtable.Emp next;//指向下一个员工
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
}
/**
* 创建一个链表
*/
class EmpLinkedList {
private Emp head;//创建头节点,头节点指向第一个员工,默认是null
/**
* 添加员工:在老师的版本中没有设置id不能重复,这导致在添加员工的时候允许添加相同id的员工,会使得程序出现一些难以预料的问题
* 程序优化:在原来的版本之上增加id不能重复的设置
*
* @param emp
*/
public void addEmp(Emp emp) {
//默认员工添加在链表的最后的位置
//如果头节点为空
if (head == null) {
head = emp;
return;
}
Emp temp = head;//定义一个辅助指针用于找到链表的最后一个节点
while (temp != null) {
if (temp.id == emp.id) {
System.out.println("生产线上已经存在相同序号的员工,请为该员工选择其他的编号!");
return;
}
temp = temp.next;
}
temp = emp;
System.out.println("员工信息添加成功!");
}
//遍历雇员的信息
public void showEmp(int number) {
//先判断链表是否为空
if (head == null) {
//为了使数量从1开始,所以使用number++
System.out.println("第" + (++number) + "生产线当前员工数量为0, 赶紧去招人!");
return;
}
System.out.print("第" + (++number) + "生产线的员工为:");
Emp temp = head;
while (temp != null) {
System.out.print("--->id = " + temp.id + ", name = " + temp.name);
temp = temp.next;
}
System.out.println();
}
//实现根据id查找对应的员工
public Emp findEmpById(int id, int number) {
//首先判断链表是否为空
if (head == null) {
System.out.println("第" + (++number) + "生产线上没有这名员工,请去其他生产线查找");
return null;
}
Emp temp = head;
while (temp != null) {
if (temp.id == id) {
System.out.println("在第" + (++number) + "条生产线上找到这个员工啦!这个员工的信息是:id = " + temp.id + ", name = " +
temp.name);
return temp;
}
temp = temp.next;
}
System.out.println("第" + (++number) + "生产线上没有这名员工,请去其他生产线查找");
return null;
}
//实现根据id删除指定的员工
public void deleteEmpById(int id) {
//首先判断链表是否为空
if (head == null) {
System.out.println("当前生产线没有员工,删除失败!");
return;
}
if (head.id == id) {
//如果删除的是头节点
head = head.next;
System.out.println("删除成功!");
return;
}
Emp temp = head;//因为这是单向链表的删除,所以我们要找到被删除节点的前一个节点,这样才能实现删除操作
while (temp.next != null) {
if (temp.next.id == id) {
if (temp.next.next == null) {
//说明要删除的是尾结点
temp.next = null;
System.out.println("删除成功!");
return;
} else {
//如果删除的是非尾节点
temp.next = temp.next.next;//删除节点
System.out.println("删除成功!");
return;
}
}
temp = temp.next;
}
System.out.println("生产线上没有该员工,删除失败!");
}
}