《算法導論》第十一章----散列表(直接尋址、連接法解決碰撞)

《算法導論》學習記錄目錄html

散列表(哈希表)是根據關鍵字直接訪問內存存儲位置的數據結構,僅支持插入、查找、刪除操做。在最壞狀況下,查找一個元素的時間爲Θ(n),而在一些合理的假設下,查找一個元素的指望時間爲O(1)。算法

散列表是普通數組的推廣。對於普通數組:數組

    一、咱們能夠將關鍵字爲k的元素存到數組下標爲k的位置裏。數據結構

    二、若是有一個關鍵字k,咱們直接查看數組下標爲k的位置。ide

這種方式爲直接尋址方式。可是這種方式有不足:只適用於關鍵字的全域比較小,並且沒有兩個元素的關鍵字徹底相同。而顯示中存儲的關鍵字集合會比關鍵字的全域相對小不少。函數

下圖爲直接尋址表:性能

代碼實現以下:(剛開始打算直接開個裝元素的數組,慢慢發現刪除的時候有點麻煩,因此改成指針數組,方便刪除後直接指向NULL指針)學習

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 #define MAX 6
 5 
 6 typedef struct {
 7     int key;
 8     int satellite;
 9 }Data;                               //元素結構體,有關鍵字和衛星數據
10 
11 Data* direct_address_search(Data *T[], int key);        //散列表查找操做
12 
13 void direct_address_insert(Data *T[], Data *x);         //散列表插入操做
14 
15 void direct_address_delete(Data *T[], Data *x);         //散列表刪除操做
16 
17 void print_table(Data *T[]);                            //打印散列表(爲了方便查看刪除操做後,散列表的變化)
18 
19 int main(){
20     int i, num, key, satellite;
21     Data *data[MAX];
22     Data *d;
23     for(i = 0; i < MAX; i++){
24         data[i] = (Data *)malloc(sizeof(Data));
25         data[i] = NULL;
26     }
27     
28     for(i = 0; i <= 3; i++){
29         d = (Data *)malloc(sizeof(Data));
30         printf("Input the key_value:\n");
31         scanf("%d", &key);
32         printf("Input the satellite:\n");
33         scanf("%d", &satellite);
34         d->key = key;
35         d->satellite = satellite;
36         direct_address_insert(data, d);
37     }
38     print_table(data);
39     key = 3;
40     d = direct_address_search(data, key);
41     printf("the key is %d, and its satellite is %d\n", d->key, d->satellite);
42 
43     direct_address_delete(data, d);
44     print_table(data);
45     free(d);
46     for(i = 0; i < MAX; i++)
47         free(data[i]);
48     return 0;
49 }
50 
51 /*
52  *直接返回下標爲key的元素
53  */
54 Data* direct_address_search(Data *T[], int key){
55     return T[key];
56 }
57 
58 /*
59  * 直接將元素插入下標key的位置裏
60  */
61 void direct_address_insert(Data *T[], Data *x){
62     T[x->key] = x;
63 }
64 
65 /*
66  * 將要刪除的元素所在的位置指向空指針
67  */
68 void direct_address_delete(Data *T[], Data *x){
69     T[x->key] = NULL;
70 }
71 
72 /*
73  * 打印直接尋址表
74  */
75 void print_table(Data *T[]){
76     int i; 
77     for(i = 0; i < MAX; i++){
78         if(T[i] != NULL){
79             printf("key is %d, and its satellite is %d\n", T[i]->key, T[i]->satellite);
80         }
81     }
82 }
View Code

散列方式爲經過散列函數將關鍵字域映射到散列表的位置上,使用散列函數的目標是爲了縮小須要處理的下標位置,從而下降空間開銷。可是仍是有小問題:有可能兩個關鍵字映射到散列表同一個位置(碰撞)。spa

解決碰撞的最理想方法爲徹底避免碰撞(是散列函數儘量「隨機」),由於關鍵字全域比存儲關鍵字域要大,因此存在兩個或者以上的元素的散列值相同。設計

解決碰撞的兩種方法有鏈接法和開放尋址法。PS:該文章暫時只有連接法。。。。。。

下圖爲連接法的示意圖

代碼實現以下:(基於雙鏈表實現,關於鏈表的東西見此第十章 已添加雙鏈表實現!)(註釋可參考)

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 #define MAX 10
  5 typedef struct {
  6     int key;
  7     int satellite;
  8 }Data;                      //元素結構體,有關鍵字和衛星數據
  9 
 10 typedef struct ListNode {
 11     Data data;
 12     struct ListNode * next;
 13     struct ListNode * prev;
 14 }ListNode;                  //雙鏈表結點結構體,存儲的數據爲元素,先後指針
 15 
 16 typedef struct {
 17     ListNode *List[MAX];
 18 }Hash_table;                //散列表結構體,雙鏈表數組
 19 
 20 int hash_function(int n);       //散列函數
 21 
 22 void list_insert(ListNode *l, Data d);  //鏈表插入
 23 
 24 int list_search(ListNode *l, int key);  //鏈表查找
 25 
 26 int list_delete(ListNode *l, Data d);   //鏈表刪除
 27 
 28 void print_list(ListNode *l);           //打印鏈表
 29 
 30 void chained_hash_insert(Hash_table *T, Data x);    //散列表插入,基於鏈表插入
 31 
 32 int chained_hash_delete(Hash_table *T, Data x);     //散列表刪除,基於鏈表刪除
 33 
 34 int chained_hash_search(Hash_table *T, int key);    //散列表查找,基於鏈表查找
 35 
 36 void print_table(Hash_table *T);                    //打印散列表,基於打印鏈表
 37 
 38 
 39 int main(){
 40     int i, num, key, satellite;
 41     Data dd;
 42     Hash_table *ht = (Hash_table *)malloc(sizeof(Hash_table));
 43     for(i = 0; i < MAX; i++){
 44         ht->List[i] = (ListNode *)malloc(sizeof(ListNode));
 45         ht->List[i]->next = NULL;
 46     }
 47 
 48     for(i = 0; i < 15; i++){
 49         Data d;  
 50         printf("Input the key:\n");
 51         scanf("%d", &key);
 52         printf("Input the satellite:\n");
 53         scanf("%d", &satellite);
 54         d.key = key;
 55         d.satellite = satellite;
 56         chained_hash_insert(ht, d);
 57 
 58         if(i == 14){
 59             dd.key = d.key;
 60             dd.satellite = d.satellite; 
 61         }
 62     }
 63 
 64     key = 3;
 65     printf("Does %d in the table?\n", key);
 66     if(chained_hash_search(ht, key))
 67         printf("Yes!\n");
 68     else
 69         printf("No!\n");
 70 
 71     printf("Delete the last input element!\n");
 72     chained_hash_delete(ht,dd);
 73     
 74     print_table(ht);
 75     free(ht);
 76 
 77     return 0;
 78 }
 79 
 80 /*
 81  * 散列函數爲直接將n對散列表大小取餘
 82  */
 83 int hash_function(int n){
 84     return n % 10;
 85 }
 86 
 87 void list_insert(ListNode *l, Data d){
 88     ListNode *p;
 89     p = (ListNode *)malloc(sizeof(ListNode));
 90     p->data = d;
 91 
 92     if(l->next == NULL){
 93         l->next = p;
 94         p->prev = l;
 95         p->next = NULL;
 96     }
 97     else{
 98         l->next->prev = p;
 99         p->next = l->next;
100         l->next = p;
101         p->prev = l;
102     }
103 }
104 
105 int list_search(ListNode *l, int key){
106     ListNode *p = l->next;
107     
108     while(p != NULL){
109         if(p->data.key == key)
110             return 1;
111         p = p->next;
112     }
113 
114     return 0;
115 }
116 
117 int list_delete(ListNode *l, Data d){
118     ListNode *p = l->next;
119 
120     while(p != NULL){
121         if(p->data.key == d.key){
122             p->prev->next = p->next;
123             p->next->prev = p->prev;
124 
125             free(p);
126             return 1;
127         }
128         p = p->next;
129     }
130     return 0;
131 }
132 
133 void print_list(ListNode *l){
134     ListNode *p = l->next;
135 
136     while(p != NULL){
137         printf("\tKey: %d, Satellite: %d", p->data.key, p->data.satellite);
138         p = p->next;
139     }
140     printf("\n");
141 }
142 
143 
144 void chained_hash_insert(Hash_table *T, Data d){
145    list_insert(T->List[hash_function(d.key)], d);
146 }
147 
148 int chained_hash_delete(Hash_table *T, Data d){
149    return list_delete(T->List[hash_function(d.key)], d);
150 }
151 
152 int chained_hash_search(Hash_table *T, int key){
153     return list_search(T->List[hash_function(key)], key);
154 }
155 
156 void print_table(Hash_table *T){
157     int i;
158     for(i = 0; i < MAX; i++){
159         printf("Row %d in Table: ", i);
160         print_list(T->List[i]);
161     }
162 }
View Code

連接法散列的最壞狀況下運行時間爲:Θ(n),全部關鍵字都散列到同一個位置上,就至關於在一個鏈表上進行查找。

散列方法的平均性能依賴於所選取的散列函數在通常狀況下將全部的關鍵字分佈在散列表的位置上的均勻程度。

簡單一致性散列:假設任何元素散列到長度爲m的散列表中的每個位置的可能性都是相同的,且和其餘已被散列到什麼位置上是無關的。

對於n個數據,在長度爲m的散列表上,每一個爲位置的鏈表長度爲a, a=n/m。

在簡單一致性散列的假設下,在用連接法的散列表上的一次不成功的查找的指望時間爲Θ(1+a)。

由於鏈表的平均長度爲a,一次不成功查找平均檢查a個元素,再加上計算該元素的散列值的總時間爲Θ(1+a)。

對於如何設計好的散列函數,有兩種方法:啓發式(除法進行散列和乘法進行散列)、隨機化(全域散列)。

除法散列法的散列函數爲: h(k) = k mod m 。m的取值應該爲與2的整數冪不太接近的質數。

乘法散列法的散列函數爲: h(k) = floor(m(k * A mod 1)) 。其中k * A mod 1 爲k * A 的小數部分, A 爲小數。Knuth大神認爲A 應該約等於√5 - 1 。。。

全域散列的散列函數爲在散列函數組裏隨機選擇散列函數。。。。。。。

 

PS:書中有不少理論的東西都看不懂。。。。。。。繼續努力。。。。該文章會繼續更新!!!特別是開放尋址法。。。。。

相關文章
相關標籤/搜索