哈希表
哈希表是一种数据结构
不同于往常的数据结构的是,往常的数据结构我们存储的数据比较单一,存储的标识比较简单,不能像数据库中的一条信息一样包含多个属性,为此,哈希表就可以简单实现这些要求
基本介绍
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
哈希表的结构
数据不一定跟我们下面的例子有关,这里我是再网上找了一张图来表示
那这个就像是一个数组+链表组成的,当然还有例如数组+二叉树的这种数据结构的哈希表。
怎么手写哈希表呢?怎么实现呢?
题目
有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时,要求查找到该员工的所有信息.
要求:
不使用数据库,速度越快越好=>哈希表(散列)
添加时,保证按照id从低到高插入[课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?]
使用链表来实现哈希表,该链表不带表头
[即:链表的第–个结点就存放雇员信息]
思路
我们采用的是数组+链表的方式来实现
那么就需要有这个模型结构,用老师的图
蓝色的框框就是哈希表
那么雇员emp有些什么呢?有的就是我们的属性字段
class Emp{
id;
name;
address;
}
那我们的链表有些什么呢?
class EmpLinkedList{
Emp head = null;//头指针指向当前链表的第一个雇员
}
那我们的哈希表有什么?
class HashTab{
EmpLinkedList[] empLinkedListArr;//是一个数组,管理我们的许多个链表
}
//我们的增删改查也会在hashTab中进行操作
//其中散列函数也在这个hashTab中,管理那个id对应哪个链表
代码
雇员类
//雇员类
class Emp{
public int id;
public String name;
public Emp next;//next 默认为空
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
链表怎么写
//创建EmpLinkedList,表示链表
class EmpLinkedList{
//头指针,指向我们第一个有效的雇员,head是直接指向有效数据的
private Emp head;//默认为空
//添加雇员列表
//1.假设我们添加雇员时,id是自增长的,即id分配从小到大
//2.因此我们将该雇员直接加入到本链表的最后一个即可
public void add(Emp emp){
//如果是添加第一个雇员
if(head == null){
head = emp;
return;
}
//如果不是第一个雇员,则使用辅助指针帮助我们定位到链表最后
Emp curEmp = head;
while(true){
if(curEmp.next == null){
//说明到链表最后了
break;
}
curEmp = curEmp.next;
}
//退出时直接将emp加入链表
curEmp.next = emp;
}
//遍历链表雇员信息
public void show(int no){
if(head == null){
System.out.println("第"+(no+1)+"链表为空");
return;
}
System.out.print("第"+(no+1)+"链表的信息为");
Emp curEmp = head;//辅助指针,帮助我们循环遍历
while(true){
System.out.println("id="+curEmp.id+curEmp.name);
if(curEmp.next == null){
//已经是最后节点
break;
}
curEmp = curEmp.next;
}
}
//根据id查找雇员
public Emp findEmpById(int id){
//判断链表是否为空
if(head ==null){
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while(true){
if(curEmp.id == id){
//查找到了
break;
}
if(curEmp.next == null){
//链表没有这个id的雇员
curEmp = null;
break;//一定要写,不然找不到的话就会抛出空指针错误
}
curEmp = curEmp.next;//没有找到后移
}
return curEmp;
}
//根据id删除链表
//找到要删除链表的前一个
public Emp deleteEmp(int no,int id) {
if(head==null) {
return null;
}
Emp curEmp=head;//辅助指针
if(head.id==id) {
head=null;
System.out.printf("第%d个链表中的id=%d的节点将被删除\n",no,id);
return curEmp;
}
while(curEmp.next!=null) {
if(curEmp.next.id==id) {
System.out.printf("第%d个链表中的id=%d的节点将被删除\n",no,id);
Emp res=curEmp.next;
curEmp=curEmp.next.next;
return res;
}
}
System.out.printf("链表中没有id=%d的雇员\n",id);
return null;
}
再然后是我们的hashtab
//写出我们的hashtab,管理我们的多条链表
class HashTab{
private EmpLinkedList[] empLinkedListArray;//链表数组
public int size;//数组大小,数组中有多少条链表
//构造器
/**
*
* @param size 数组大小,也就是有几条链表
*/
public HashTab(int size) {
//初始化我们的链表
this.size = size;
empLinkedListArray = new EmpLinkedList[size];
//留一个坑
//我们发现有空指针异常,是因为虽然我们大的框架创建了,
//但是框架里面没东西,不能直接和雇员连接的
for (int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员操作
public void add(Emp emp){
//根据员工id,得到改员工应该添加到那个链表
int empLinkedListNo = hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListArray[empLinkedListNo].add(emp);
}
//遍历我们所有的链表,遍历hashtab哈希表,就是遍历数组+链表
public void list(){
for (int i = 0; i < size; i++) {
empLinkedListArray[i].show(i);
}
}
public void findEmpById(int id){
//使用散列函数确定在哪条链表中查找
int empLinkedListNo = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNo].findEmpById(id);
if(emp != null){
//找到了
System.out.println("在第"+(empLinkedListNo+1)+"条链表中找到了雇员id = "+id);
}
if(emp == null){
System.out.println("在哈希表中,你没有找到该雇员");
}
}
public void deleteEmp(int id) {
//使用散列函数确定删除哪条链表
int empLinkedListNo=hashFun(id);
Emp emp=empLinkedListArray[empLinkedListNo].deleteEmp(empLinkedListNo+1,id);
if(emp!=null) {
//找到
// System.out.printf("在第%d条链表中找到该雇员 id=%d\n",empLinkedListNo+1,id);
}
else {
System.out.println("在哈希表中没有找到该雇员~");
}
}
//编写散列函数,使用简单的取模法
public int hashFun(int id){
return id % size;
}
}
完整版代码
package hashTab数据结构;
//哈希表的实现 数组+链表
//@author 王
//2021年1月23日22:34:46
import java.util.Scanner;
public class HashTabDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1.创建hashtab哈希表
HashTab hashTab = new HashTab(7);
//写一个简单的菜单
String key = "";
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("add:添加雇员");
System.out.println("show:显示雇员");
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);
hashTab.add(emp);
break;
}
case "show":{
hashTab.list();
break;
}
case "find":{
System.out.println("请输入你要查找的id");
int id = scanner.nextInt();
hashTab.findEmpById(id);
break;
}
case "delete":{
System.out.println("请输入你要删除的id");
int id = scanner.nextInt();
hashTab.deleteEmp(id);
break;
}
case "exit":{
scanner.close();
System.exit(0);
}
default:
break;
}
}
}
}
//写出我们的hashtab,管理我们的多条链表
class HashTab{
private EmpLinkedList[] empLinkedListArray;//链表数组
public int size;//数组大小,数组中有多少条链表
//构造器
/**
*
* @param size 数组大小,也就是有几条链表
*/
public HashTab(int size) {
//初始化我们的链表
this.size = size;
empLinkedListArray = new EmpLinkedList[size];
//留一个坑
//我们发现有空指针异常,是因为虽然我们大的框架创建了,
//但是框架里面没东西,不能直接和雇员连接的
for (int i = 0; i < size; i++) {
empLinkedListArray[i] = new EmpLinkedList();
}
}
//添加雇员操作
public void add(Emp emp){
//根据员工id,得到改员工应该添加到那个链表
int empLinkedListNo = hashFun(emp.id);
//将emp添加到对应的链表中
empLinkedListArray[empLinkedListNo].add(emp);
}
//遍历我们所有的链表,遍历hashtab哈希表,就是遍历数组+链表
public void list(){
for (int i = 0; i < size; i++) {
empLinkedListArray[i].show(i);
}
}
public void findEmpById(int id){
//使用散列函数确定在哪条链表中查找
int empLinkedListNo = hashFun(id);
Emp emp = empLinkedListArray[empLinkedListNo].findEmpById(id);
if(emp != null){
//找到了
System.out.println("在第"+(empLinkedListNo+1)+"条链表中找到了雇员id = "+id);
}
if(emp == null){
System.out.println("在哈希表中,你没有找到该雇员");
}
}
public void deleteEmp(int id) {
//使用散列函数确定删除哪条链表
int empLinkedListNo=hashFun(id);
Emp emp=empLinkedListArray[empLinkedListNo].deleteEmp(empLinkedListNo+1,id);
if(emp!=null) {
//找到
// System.out.printf("在第%d条链表中找到该雇员 id=%d\n",empLinkedListNo+1,id);
}
else {
System.out.println("在哈希表中没有找到该雇员~");
}
}
//编写散列函数,使用简单的取模法
public int hashFun(int id){
return id % size;
}
}
//雇员类
class Emp{
public int id;
public String name;
public Emp next;//next 默认为空
public Emp(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
//创建EmpLinkedList,表示链表
class EmpLinkedList{
//头指针,指向我们第一个有效的雇员,head是直接指向有效数据的
private Emp head;//默认为空
//添加雇员列表
//1.假设我们添加雇员时,id是自增长的,即id分配从小到大
//2.因此我们将该雇员直接加入到本链表的最后一个即可
public void add(Emp emp){
//如果是添加第一个雇员
if(head == null){
head = emp;
return;
}
//如果不是第一个雇员,则使用辅助指针帮助我们定位到链表最后
Emp curEmp = head;
while(true){
if(curEmp.next == null){
//说明到链表最后了
break;
}
curEmp = curEmp.next;
}
//退出时直接将emp加入链表
curEmp.next = emp;
}
//遍历链表雇员信息
public void show(int no){
if(head == null){
System.out.println("第"+(no+1)+"链表为空");
return;
}
System.out.print("第"+(no+1)+"链表的信息为");
Emp curEmp = head;//辅助指针,帮助我们循环遍历
while(true){
System.out.println("id="+curEmp.id+curEmp.name);
if(curEmp.next == null){
//已经是最后节点
break;
}
curEmp = curEmp.next;
}
}
//根据id查找雇员
public Emp findEmpById(int id){
//判断链表是否为空
if(head ==null){
System.out.println("链表为空");
return null;
}
//辅助指针
Emp curEmp = head;
while(true){
if(curEmp.id == id){
//查找到了
break;
}
if(curEmp.next == null){
//链表没有这个id的雇员
curEmp = null;
break;//一定要写,不然找不到的话就会抛出空指针错误
}
curEmp = curEmp.next;//没有找到后移
}
return curEmp;
}
//根据id删除链表
//找到要删除链表的前一个
public Emp deleteEmp(int no,int id) {
if(head==null) {
return null;
}
Emp curEmp=head;//辅助指针
if(head.id==id) {
head=null;
System.out.printf("第%d个链表中的id=%d的节点将被删除\n",no,id);
return curEmp;
}
while(curEmp.next!=null) {
if(curEmp.next.id==id) {
System.out.printf("第%d个链表中的id=%d的节点将被删除\n",no,id);
Emp res=curEmp.next;
curEmp=curEmp.next.next;
return res;
}
}
System.out.printf("链表中没有id=%d的雇员\n",id);
return null;
}
}