Java 数据结构与算法-哈希表
Posted 学Java的冬瓜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 数据结构与算法-哈希表相关的知识,希望对你有一定的参考价值。
作者
:学Java的冬瓜
博客主页
:☀冬瓜的主页🌙
专栏
:【Java 数据结构与算法】
分享
:曾梦想仗剑走天涯,看一看世界的繁华。——许巍《曾经的你》
主要内容
:哈希方法,哈希函数,哈希表,哈希冲突,负载因子,降低哈希冲突,解决哈希冲突。
文章目录
一、什么是哈希表?
- 在顺序结构和平衡树中,想要查找一个元素,必须先经过多次比较,才能找到。但是在哈希表中不一样。
- 哈希表是一种数据结构,通过哈希函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系(
即这个关键码通过哈希函数计算可以得到元素的存储位置,所以速度极快
),这个存储结构就是哈希表。
二、哈希函数
- 为了避免哈希冲突,有两种哈希函数方法比较普遍。
1、直接定制法
- 取关键字的某个线性函数为散列地址:
Hash(Key)= A*Key + B
- 优点:简单、均匀 ;缺点:需要事先知道关键字的分布情况
- 使用场景:适合查找比较小且连续的情况
- 面试题:【字符串中的第一个唯一字符】
2、除留取余法
- 设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:
Hash(key) = key% p(p<=m),将关键码转换成哈希地址。
三、哈希冲突
1、什么是负载因子?
负载因子也可称为荷载因子。
负载因子 α = 哈希表中元素个数 / 哈希表的长度
哈希表的长度没有扩容之前是定长的,当哈希表中元素的个数增加时,负载因子α就会变大,那么越容易出现哈希冲突,所以我们应该严格控制负载因子的大小,在 Java 中,限制了负载因子最大为 0.75,当超过了这个值,就要进行扩容哈希表,重新哈希(重新将各个元素放在新的位置上)。
2、怎么降低哈希冲突?
<1> 使用合适的哈希函数
<2> 增加散列表的长度。当负载因子越来越大时,冲突率越来越高。如果想要降低冲突率,就是要降低负载因子,那就增加散列表的长度。
3、怎么解决哈希冲突?
@ 闭散列
- 闭散列又称开放地址法
开放地址法具体操作有以下两种:
集合:1,7,4,5,9
线性探测
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素;
如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
二次探测:
首先H0经过key%m得到,然后如果冲突,就再次计算,直到算出不冲突的值。
@ 开散列(哈希桶 重点)
- 开散列又称链地址法,哈希桶。
集合:1,7,4,5,9
要插入:34,44,84
- 插入操作:JDK1.7之前,采用头插法;JDK1.8开始,使用尾插法。
- 哈希表使用:数组+链表的形式,
当链表长度超过 8且数组长度超过 64时,链表会变成红黑树
。 - 那么就出现一个问题,当链表长度超过 8且数组长度超过 64,二者都满足时,链表才会变成红黑树。那有没有可能链表长度很长,但数组长度不超过64呢?
没有可能!因为链表长度很长时,代表哈希冲突率很高了,那要降低哈希冲突,就需要降低负载因子(Java的系统库限制了负载因子为0.75),负载因子和数组长度成反比,那么就是得增大数组长度,这样当链表长度超过8,数组长度超过64时,链表就变成了红黑树。这样就把数组长度和链表长度联系起来了。 - 哈希表查找时间复杂度:近似
log(1)
当链表长度比较短时,近似O(1)
链表长度变长时,会通过加长数组来降低冲突率,从而使链表长度降低(数组长度增长后原来的链表会被打散),也近似O(1)
当链表长度超过8,数组长度超过64时,复杂度为O(logn)。
其实复杂度随着数组长度变化,链表长度的变化而变化。但是可以近似看成O(1)。
数据结构与算法-java-哈希表
什么是哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
其实可以这样理解,哈希表就像一个缓冲层
下面来看一个案例
大致可以分析如下
package hashTable; import java.util.Scanner; //表示一个雇员 class Emp{ public int id; public String name; public Emp next; public Emp(int id, String name) { this.id = id; this.name = name; } } //表示链表 class EmpLinkedList{ private Emp head; //默认为null //add public void add(Emp emp) { //如果是添加第一个 if(head ==null) { head=emp; return; } //不是第一个 Emp temp = head; while (true) { if(temp.next==null) { //说明到最后了 break; } temp=temp.next; //后移 } //while退出时一定为最后,指向加入链表就行了 temp.next=emp; } public void show(){ if(head==null) { System.out.println("当前链表为空"); return; } System.out.println("当前链表信息为"); Emp temp=head; while (true) { System.out.printf("=>id=%d name=%s \\t",temp.id,temp.name); if (temp.next == null) { break; } temp=temp.next;//后移即++ } System.out.println();//换行 } } //创建hash表,管理多条链表,如图那样 class HashTab{ private EmpLinkedList [] empLinkedListArray; private int 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) { int empNo=hashFunc(emp.id); empLinkedListArray[empNo].add(emp); } public void show(){ for (int i=0;i<size;i++) { empLinkedListArray[i].show(); } } public int hashFunc(int id) { return id%size; //通过模判断在hash表的位置 } } public class hashTableDemo { 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("show:显示雇员"); 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.show(); break; case "exit": scanner.close(); break; default: break; } } } }
以上是关于Java 数据结构与算法-哈希表的主要内容,如果未能解决你的问题,请参考以下文章