Java数据结构与算法——哈希表篇

书读的越多而不加思考,你就会觉得你知道得很多;而当你读书而思考得越多的时候,你就会越清楚地看到,你知道得很少。

导读:本篇文章讲解 Java数据结构与算法——哈希表篇,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Java数据结构与算法: https://blog.csdn.net/weixin_46822367/article/details/115478461?spm=1001.2014.3001.5502.

1、哈希表原理

什么是哈希表?
哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

记录的存储位置=f(关键字)
这里的对应关系f称为散列函数,又称为哈希(Hash函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。

哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。(或者:把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数)

而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

数组的特点是:寻址容易,插入和删除困难;

而链表的特点是:寻址困难,插入和删除容易。

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:
在这里插入图片描述
左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。

2、哈希表实现图解

在这里插入图片描述

3、哈希表代码实现

package com.lyp.hashtab;

import java.util.Scanner;

public class HashTabDemo {

	public static void main(String[] args) {
		//创建哈希表
		HashTab hashTab = new HashTab(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("del:  删除雇员");
			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 "list": 
				hashTab.list();
				break;
			case "find": 
				System.out.println("请输入要查找的id");
				id = scanner.nextInt();
				hashTab.findEmpById(id);
				break;
			case "del": 
				System.out.println("请输入要删除的id");
				id = scanner.nextInt();
				hashTab.del(id);
				break;
			case "exit":
				scanner.close();
				System.exit(0);;
			default :
				break;
			}
		}
	}

}

//创建HashTab 管理多条链表
class HashTab{
	private EmpLinkedList[] empLinkedListArray;
	private int size;//表示有多少条链表
	//构造器
	public HashTab(int size) {
		this.size = size;
		//初始化empLinkedListArray
		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].list(i);
		}
	}
	
	//根据输入的id,查找雇员
	public void findEmpById (int id) {
		//使用散列函数确定到哪条链表查询
		int empLinkedListNO = hashFun(id);
		Emp emp = empLinkedListArray[empLinkedListNO].findEmpById(id);
		if(emp != null) {//找到
			System.out.printf("在第%d条链表中找到 雇员 id = %d\n",(empLinkedListNO + 1),id);
		} else {
			System.out.println("在哈希表中,没有找到该雇员~");
		}
	}
	
	//根据输入的id,删除雇员
	public void del(int id) {
		//使用散列函数确定到哪条链表查询
		int empLinkedListNO = hashFun(id);
		empLinkedListArray[empLinkedListNO].del(id);
			System.out.printf("已删除第%d条链表中 雇员 id = %d\n",(empLinkedListNO + 1),id);
	}
	
	
	//编写数列函数,使用一个简单取模法
	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;
	}
	
}



//创建 EmpLiskedList,表示链表
class EmpLinkedList {
	//头指针,执行第一个Emp ,因此我们这个链表的head 是直接指向第一个Emp
	private Emp head;//n默认 null
	
	//添加雇员到链表
	//1.假定,当添加雇员时,id是自增长,即id的分配总是从小到大
	//因此我们将该雇员直接加到本链表的最后即可
	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 list(int no) {
		if(head == null) {
			System.out.println("第"+(no+1)+"条链表为空");
			return ;
		}
		System.out.print("第"+(no+1)+"条链表的信息为:");
		Emp curEmp = head;//辅助指针
		while(true) {
			System.out.printf(" => id=%d name=%s\t",curEmp.id,curEmp.name);
			if(curEmp.next == null) {//说明curEmp 已经是最后节点
				break;
			}
			curEmp = curEmp.next;//后移,遍历
		}
		System.out.println();
	}
	
	//根据id 查找雇员
	//如果查找到,就返回Emp,如果没有找到,就返回null
	public Emp findEmpById(int id) {
		//判断链表是否为空
		if(head == null) {
			System.out.println("链表为空");
			return null;
		}
		Emp curEmp = head;
		while(true) {
			if(curEmp.id == id) {//找到
				break;//这时候curEmp 就指向要查找的雇员
			}
			if(curEmp.next == null) {//退出
				curEmp = null;
				break;
			}
			curEmp = curEmp.next;//向后
		}
		return curEmp;
	}
	
	public void del(int id) {
		if(head == null) {
			System.out.println("链表为空");
		}
		Emp curEmp = head;
		while(true) {
			if(head.id == id && head.next == null) {//如果要删除的雇员为第一个节点时,且没有后续节点时
				head = null;
				break;
			} else if(head.id == id && head.next != null) {//如果要删除的雇员为第一个节点时,且不是最后一个节点时
				head = head.next;
				break;
			} else if(curEmp.next.id == id) {//curEmp为要删除的前一个节点
				curEmp.next = curEmp.next.next;
				break;
			} else if(head.next != null && curEmp.next == null) {//退出
				System.out.println("在哈希表中,没有找到该雇员,无法删除");
				break;
			}
			curEmp = curEmp.next;
		}
	}
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/123091.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!