数据结构——哈希表

一 概念

Hash表也称散列表,也有直接译作哈希表,Hash表是一种根据关键字值(key - value)而直接进行访问的数据结构。它基于数组,通过把关键字映射到数组的某个下标来加快查找速度,但是又和数组、链表、树等数据结构不同,在这些数据结构中查找某个关键字,通常要遍历整个数据结构,也就是O(N)的时间级,但是对于哈希表来说,只是O(1)的时间级。

  注意,这里有个重要的问题就是如何把关键字转换为数组的下标,这个转换的函数称为哈希函数(也称散列函数),转换的过程称为哈希化


二、哈希函数


幂的连乘

  我们将单词表示的数拆成数列,用适当的 27 的幂乘以这些位数(因为有26个可能的字符,以及空格,一共27个),然后把乘积相加,这样就得出了每个单词独一无二的数字。

  比如把单词cats 转换为数字:

  cats = 3*273 + 1*272 + 20*271 + 19*270 = 59049 + 729 + 540 + 19 = 60337

  这个过程会为每个单词创建一个独一无二的数,但是注意的是我们这里只是计算了 4 个字母组成的单词,如果单词很长,比如最长的10个字母的单词 zzzzzzzzzz,仅仅是279 结果就超出了7000000000000,这个结果是很巨大的,在实际内存中,根本不可能为一个数组分配这么大的空间。

  所以这个方案的问题就是虽然为每个单词都分配了独一无二的下标,但是只有一小部分存放了单词,很大一部分都是空着的。那么现在就需要一种方法,把数位幂的连乘系统中得到的巨大的整数范围压缩到可接受的数组范围中。

  对于英语字典,假设只有5000个单词,这里我们选定容量为10000 的数组空间来存放(后面会介绍为啥需要多出一倍的空间)。那么我们就需要将从 0 到超过 7000000000000 的范围,压缩到从0到10000的范围。

  第一种方法:取余,得到一个数被另一个整数除后的余数。首先我们假设要把从0-199的数字(用largeNumber表示),压缩为从0-9的数字(用smallNumber表示),后者有10个数,所以变量smallRange 的值为10,这个转换的表达式为:

  smallNumber = largeNumber % smallRange

  当一个数被 10 整除时,余数一定在0-9之间,这样,我们就把从0-199的数压缩为从0-9的数,压缩率为 20 :1。

  

 

  我们也可以用类似的方法把表示单词唯一的数压缩成数组的下标:

  arrayIndex = largerNumber % smallRange

  这也就是哈希函数。它把一个大范围的数字哈希(转化)成一个小范围的数字,这个小范围的数对应着数组的下标。使用哈希函数向数组插入数据后,这个数组就是哈希表。


三、哈希冲突

  把巨大的数字范围压缩到较小的数字范围,那么肯定会有几个不同的单词哈希化到同一个数组下标,即产生了冲突

  冲突可能会导致哈希化方案无法实施,前面我们说指定的数组范围大小是实际存储数据的两倍,因此可能有一半的空间是空着的,所以,当冲突产生时,一个方法是通过系统的方法找到数组的一个空位,并把这个单词填入,而不再用哈希函数得到数组的下标,这种方法称为开放地址法。比如加入单词 cats 哈希化的结果为5421,但是它的位置已经被单词parsnip占用了,那么我们会考虑将单词 cats 存放在parsnip后面的一个位置 5422 上。

  另一种方法,前面我们也提到过,就是数组的每个数据项都创建一个子链表或子数组,那么数组内不直接存放单词,当产生冲突时,新的数据项直接存放到这个数组下标表示的链表中,这种方法称为链地址法。


四、链地址法

某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。其他同样映射到这个位置的数据项只需要加到链表中,不需要在原始的数组中寻找空位。

  

有序链表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package  com.ys.hash;
 
public  class  SortLink {
     private  LinkNode first;
     public  SortLink(){
         first =  null ;
     }
     public  boolean  isEmpty(){
         return  (first ==  null );
     }
     public  void  insert(LinkNode node){
         int  key = node.getKey();
         LinkNode previous =  null ;
         LinkNode current = first;
         while (current !=  null  && current.getKey() < key){
             previous = current;
             current = current.next;
         }
         if (previous ==  null ){
             first = node;
         } else {
             node.next = current;
             previous.next = node;
         }
     }
     public  void  delete( int  key){
         LinkNode previous =  null ;
         LinkNode current = first;
         if (isEmpty()){
             System.out.println( "Linked is Empty!!!" );
             return ;
         }
         while (current !=  null  && current.getKey() != key){
             previous = current;
             current = current.next;
         }
         if (previous ==  null ){
             first = first.next;
         } else {
             previous.next = current.next;
         }
     }
     
     public  LinkNode find( int  key){
         LinkNode current = first;
         while (current !=  null  && current.getKey() <= key){
             if (current.getKey() == key){
                 return  current;
             }
         }
         return  null ;
     }
     
     public  void  displayLink(){
         System.out.println( "Link(First->Last)" );
         LinkNode current = first;
         while (current !=  null ){
             current.displayLink();
             current = current.next;
         }
         System.out.println( "" );
     }
     class  LinkNode{
         private  int  iData;
         public  LinkNode next;
         public  LinkNode( int  iData){
             this .iData = iData;
         }
         public  int  getKey(){
             return  iData;
         }
         public  void  displayLink(){
             System.out.println(iData +  " " );
         }
     }
}

  链地址法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package  com.ys.hash;
 
import  com.ys.hash.SortLink.LinkNode;
 
public  class  HashChain {
     private  SortLink[] hashArray; //数组中存放链表
     private  int  arraySize;
     public  HashChain( int  size){
         arraySize = size;
         hashArray =  new  SortLink[arraySize];
         //new 出每个空链表初始化数组
         for ( int  i =  0  ; i < arraySize ; i++){
             hashArray[i] =  new  SortLink();
         }
     }
     
     public  void  displayTable(){
         for ( int  i =  0  ; i < arraySize ; i++){
             System.out.print(i +  ":" );
             hashArray[i].displayLink();
         }
     }
     
     public  int  hashFunction( int  key){
         return  key%arraySize;
     }
     
     public  void  insert(LinkNode node){
         int  key = node.getKey();
         int  hashVal = hashFunction(key);
         hashArray[hashVal].insert(node); //直接往链表中添加即可
     }
     
     public  LinkNode delete( int  key){
         int  hashVal = hashFunction(key);
         LinkNode temp = find(key);
         hashArray[hashVal].delete(key); //从链表中找到要删除的数据项,直接删除
         return  temp;
     }
     
     public  LinkNode find( int  key){
         int  hashVal = hashFunction(key);
         LinkNode node = hashArray[hashVal].find(key);
         return  node;
     }
 










猜你喜欢

转载自blog.csdn.net/yz18931904/article/details/80514137