数据结构论坛

首页 » 分类 » 常识 » 数据结构和算法五哈希表
TUhjnbcbe - 2021/8/26 17:12:00
白癜风治疗多久能好 http://baidianfeng.39.net/a_bdfys/160605/4866607.html
哈希表什么是哈希表

哈希表表(Hashtable,散列表),是根据关键码值(Keyvalue)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash)函数。

举个例子:统计s="fsgererhgerh"中字符出现次数(其中s只包含小写字母)

我们通常会使用这样的数组:int[]freq=newint[26];

这个freq就是一个哈希表

其中每一个字符都和一个索引对应:

哈希表设计思想:

哈希表充分体现了算法设计领域的经典思想:空间换时间。

哈希表是空间和时间之间的平衡

哈希函数的设计十分重要

“键”通过哈希函数得到的“索引”分布越均匀越好

哈希函数的设计

哈希函数设计原则:

1.一致性:如果a==b,则hash(a)==hash(b),反之,则不成立

2.高效性:计算高效简便

3.均匀性:哈希值均匀分布

整数

小范围正整数:

直接使用

小范围负整数:

进行偏移,比如[-,],左右区间都+,进行偏移,得到[0,]

大整数:比如身份证号

通常做法:取模

1.取后4位,相当于mod00--获得

2.取后6位,相当于mod0000--获得,但是获取的数据不可能超过32万,因为在身份证中19为表示的是一个月的某一天,最大值是31,即所谓分布不均匀问题;只取后6位,就没有利用所有信息。

3.解决2中的两个问题的简单解决办法:模一个素数

浮点型

浮点型在计算机中也是32位或者64位的二进制表示的,只不过是计算机解析成浮点数的,因此,可以根据浮点数的二进制表示,转化为整数。

字符串

转换成整型处理

""--1*10^2+6*10^1+3*10^0

"code"--c*26^3+o*26^2+d*26^1+e*26^0

"code"也可以这样转换:

"code"--c*B^3+o*B^2+d*B^1+e*B^0

(26和B都是进制,合理选择进制即可)

因此有如下哈希函数:

hash("code")=(c*B^3+o*B^2+d*B^1+e*B^0)%M

进一步转化:

hash("code")=(((c*B+o)*B+d)*B+e)%M

(((c*B+o)*B+d)*B+e)数据有可能过大,再进一步进行转化:

hash("code")=((((c%M)*B+o)%M*B+d)%M*B+e)%M

对应代码如下:

inthash=0;for(inti=0;is.length();i++){hash=(hash*B+s.charAt(i))%M;}复合类型

转化成整型处理

比如:对于自定义的Date

classDate{intyear;intmonth;intday;}

hash(date)=(((date.year%M)*B+date.month)%M*B+date.day)%M

注意:以上都是都是转化为整型处理,但这并不是唯一的方法。

Java中hashCode方法

Java中hashCode函数的使用

publicclassMain{publicstaticvoidmain(String[]args){Integera=newInteger(3);System.out.println(a.hashCode());//3Integerb=newInteger(-3);System.out.println(b.hashCode());//-3Doublec=newDouble(.4);System.out.println(c.hashCode());//--Strings="abcd";System.out.println(s.hashCode());//}}

在自定义对象中重写hashCode函数

publicclassStudent{privateintid;privatedoublescore;privateStringname;publicStudent(intid,doublescore,Stringname){this.id=id;this.score=score;this.name=name;}

OverridepublicinthashCode(){intB=26;inthash=0;hash=hash*B+id;hash=hash*B+((Double)score).hashCode();//忽略name的大小写问题hash=hash*B+name.toLowerCase().hashCode();returnhash;}}

publicclassMain2{publicstaticvoidmain(String[]args){Students=newStudent(1,90.0,"aaa");System.out.println(s.hashCode());//-Students2=newStudent(1,90.0,"aaa");System.out.println(s2.hashCode());//-}}

重写equals()方法

检查是否为同一个对象的引用,如果是直接返回true;

检查是否是同一个类型(判断Class对象是否相同),如果不是,直接返回false;

将Object对象进行转型

判断每个关键域是否相等

Overridepublicbooleanequals(Objectobj){if(this==obj){returntrue;}if(obj==null

this.getClass()!=obj.getClass()){returnfalse;}Studentanother=(Student)obj;returnthis.id==another.idthis.score==another.scorethis.name.toLowerCase().equals(another.name.toLowerCase());}

publicclassMain2{publicstaticvoidmain(String[]args){Students=newStudent(1,90.0,"aaa");System.out.println(s.hashCode());//-Students2=newStudent(1,90.0,"aaa");System.out.println(s2.hashCode());//-//s和s2的地址是不同的,但是内容是相同的System.out.println(s==s2);//falseSystem.out.println(s.equals(s2));//true}}

注意:Java中每个对象默认有默认的hashCode实现。hashCode就是该对象的地址。

重写hashCode的情况:

Students=newStudent(1,90.0,"aaa");System.out.println(s.hashCode());//-Students2=newStudent(1,90.0,"aaa");System.out.println(s2.hashCode());//-

未重写hashCode的情况:

Students=newStudent(1,90.0,"aaa");System.out.println(s.hashCode());//Students2=newStudent(1,90.0,"aaa");System.out.println(s2.hashCode());//

s和s2的hashCode不同,因为s和s2的地址不同。

哈希冲突的处理SeperateChaining

计算哈希值(hashCode(k1)0x7fffffff)%M

采用查找表解决哈希冲突,查找表也可以使用平衡树结构

使用HashMap作为查找表

注意:

1.Java8之前,每个位置对应一个链表

2.Java8之后,当哈希冲突达到一定程度,每个位置从链表转成红黑树

哈希表编程

publicclassHashTableK,V{privateTreeMapK,V[]hashtable;privateintM;privateintsize;publicHashTable(intM){this.M=M;this.size=0;this.hashtable=newTreeMap[M];for(inti=0;iM;i++){hashtable=newTreeMap();}}publicHashTable(){this(97);}privateinthash(Kkey){return(key.hashCode()0x7fffffff)%M;}publicintgetSize(){returnsize;}}

哈希表具体功能实现

publicvoidadd(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(map.containsKey(key)){map.put(key,value);}else{map.put(key,value);size++;}}publicVremove(Kkey){TreeMapK,Vmap=hashtable[hash(key)];Vret=null;if(map.containsKey(key)){ret=map.remove(key);size--;}returnret;}publicvoidset(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(!map.containsKey(key)){thrownewIllegalArgumentException(key+"doesnotexist!");}map.put(key,value);}publicbooleancontains(Kkey){returnhashtable[hash(key)].containsKey(key);}publicVget(Kkey){returnhashtable[hash(key)].get(key);}

哈希表链表法时间复杂度分析

哈希表的动态空间处理

publicclassHashTable2K,V{privatestaticfinalintupperTol=10;privatestaticfinalintlowerTol=2;privatestaticfinalintinitCapacity=7;privateTreeMapK,V[]hashtable;privateintM;privateintsize;publicHashTable2(intM){this.M=M;this.size=0;this.hashtable=newTreeMap[M];for(inti=0;iM;i++){hashtable=newTreeMap();}}publicHashTable2(){this(initCapacity);}privateinthash(Kkey){return(key.hashCode()0x7fffffff)%M;}privatevoidresize(intnewM){TreeMapK,V[]newHashtable=newTreeMap[newM];for(inti=0;inewM;i++){newHashtable=newTreeMap();}intoldM=M;this.M=newM;for(inti=0;ioldM;i++){TreeMapK,Vmap=hashtable;for(Kkey:map.keySet()){newHashtable[hash(key)].put(key,map.get(key));}}hashtable=newHashtable;}publicintgetSize(){returnsize;}publicvoidadd(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(map.containsKey(key)){map.put(key,value);}else{map.put(key,value);size++;if(size=upperTol*M){resize(2*M);}}}publicVremove(Kkey){TreeMapK,Vmap=hashtable[hash(key)];Vret=null;if(map.containsKey(key)){ret=map.remove(key);size--;if(sizelowerTol*MM/2=initCapacity){resize(M/2);}}returnret;}publicvoidset(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(!map.containsKey(key)){thrownewIllegalArgumentException(key+"doesnotexist!");}map.put(key,value);}publicbooleancontains(Kkey){returnhashtable[hash(key)].containsKey(key);}publicVget(Kkey){returnhashtable[hash(key)].get(key);}}

时间复杂度分析:

平均时间复杂度:O(1)

实际上每个操作的时间复杂度是:O(log(lowerTol))~O(log(upperTol)),而lowerTol和upperTol都是常数。

优化哈希表

publicclassHashTable3K,V{//哈希表容量的常量表privatefinalint[]capacity={53,97,,,,,,,,,,,,,,,,,,,,663319,,,,};//int类型的最大素数就是privatestaticfinalintupperTol=10;privatestaticfinalintlowerTol=2;privateintcapacityIndex=0;privateTreeMapK,V[]hashtable;privateintM;privateintsize;publicHashTable3(){this.M=capacity[capacityIndex];this.size=0;this.hashtable=newTreeMap[M];for(inti=0;iM;i++){hashtable=newTreeMap();}}privateinthash(Kkey){return(key.hashCode()0x7fffffff)%M;}privatevoidresize(intnewM){TreeMapK,V[]newHashtable=newTreeMap[newM];for(inti=0;inewM;i++){newHashtable=newTreeMap();}intoldM=M;this.M=newM;for(inti=0;ioldM;i++){TreeMapK,Vmap=hashtable;for(Kkey:map.keySet()){newHashtable[hash(key)].put(key,map.get(key));}}hashtable=newHashtable;}publicintgetSize(){returnsize;}publicvoidadd(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(map.containsKey(key)){map.put(key,value);}else{map.put(key,value);size++;if(size=upperTol*McapacityIndex+1capacity.length){capacityIndex++;resize(capacity[capacityIndex]);}}}publicVremove(Kkey){TreeMapK,Vmap=hashtable[hash(key)];Vret=null;if(map.containsKey(key)){ret=map.remove(key);size--;if(sizelowerTol*McapacityIndex-1=0){capacityIndex--;resize(capacity[capacityIndex]);}}returnret;}publicvoidset(Kkey,Vvalue){TreeMapK,Vmap=hashtable[hash(key)];if(!map.containsKey(key)){thrownewIllegalArgumentException(key+"doesnotexist!");}map.put(key,value);}publicbooleancontains(Kkey){returnhashtable[hash(key)].containsKey(key);}publicVget(Kkey){returnhashtable[hash(key)].get(key);}}设计的哈希表中的不足

Java8之前,每一个位置对应一个链表,K不要求具有可比性,所以是无序的。

Java8之后,当哈希冲突得到一定程度时,每一个位置从链表转化成红黑树,这时就要求K具有可比性。

开放地址法解决哈希冲突1.线性探测法

每次遇到哈希冲突,就+1

2.平方探测法

遇到哈希冲突,就+1

再次遇到哈希冲突,就+4

再次遇到哈希冲突,就+9

...

3.二次哈希

每次遇到哈希冲突,就+hash2(key)

其他解决哈希冲突的方法:

再哈希法Rehashing

CoalescedHashing(综合了开放地址法和链地址法)

预览时标签不可点收录于话题#个上一篇下一篇
1