算法導論-散列表(Hash Table)-大量數據快速查找算法


目錄                                         

內容                                          

1、引言                       

    如果想在一個n個元素的列表中,查詢元素x是否存在於列表中,首先想到的就是從頭到尾遍歷一遍列表,逐個進行比較,這種方法效率是Θ(n);當然,如果列表是已經排好序的話,可以采用二分查找算法進行查找,這時效率提升到Θ(logn);  本文中,我們介紹散列表(HashTable),能使查找效率提升到Θ(1);

Question 1:那么什么是Hash Table,是如何定義的呢?

給定一個關鍵字Key(整數),通過一個定義好的散列函數,可以計算出數據存放的索引位置,這樣我們不用遍歷,就可以通過計算出的索引位置獲取到要查詢的數。如下圖所示:

因此:散列表是普通數組概念的推廣,在散列表中,不是直接把關鍵字用作數組下標,而是根據關鍵字通過散列函數計算出來的。下面會進行講解。

 question 2:那么,Hash Function 如何定義呢?

 hash function 有很多種定義方法,其中 最常用的是除法散列;在散列函數小節中會進行詳細介紹【除法散列、乘法散列、全域散列、完全散列】

 question 3:當給定的keys不是整數怎么辦?

如下圖所示,想通過各hash function將Non-InTeger key轉換為Integer key,然后再進行正常的運算。

 

Question 4:當多個關鍵字Key,通過hash function計算出的索引相同,就是說他們產生了“沖突”,這時該怎么辦呢?

針對這個問題,我們的處理方法有:開放尋址法和鏈表法。具體會在碰撞處理方法小節講解。

Ok,下面開始枯燥地講解了:

2、直接尋址              

    當關鍵字的的全域(范圍)U比較小的時,直接尋址是簡單有效的技術,一般可以采用數組實現直接尋址表,數組下標對應的就是關鍵字的值,即具有關鍵字k的元素被放在直接尋址表的槽k中。直接尋址表的字典操作實現比較簡單,直接操作數組即可以,只需O(1)的時間。見下圖:

假設某應用要用到一個動態集合,其中每個元素都是取自全域U={0,1,...,m-1}中的一個關鍵字,這里m不是一個很大的數。另外,假設沒有兩個元素具有相同的關鍵字。

為表示動態集合,我們用一個數組,或稱為直接尋址表(direct-address table),記為T[0...m-1]。其中每個位置,稱為一個槽(slot),對應全域U中的一個關鍵字。上圖描述了該方法。槽k指向集合中一個關鍵字為k的元素。如果該集合中沒有關鍵字為k的元素,則T[k]為NIL

3、散列尋址                 

直接尋址技術的缺點是非常明顯的:如果全域U很大,則在一台標准的計算機可用內存容量中,要存儲大小為|U|的一張表T也許不大實際,甚至不可能。還有,實際存儲的關鍵字集合K相對於U來說可能很小,使得分配給T的大部分空間都將浪費掉。當存儲在字典中的關鍵字集合K比所有可能的關鍵字的全域U要小許多時,散列表需要的存儲空間要比直接尋址表少的多。在散列方式下,元素存放在槽h(k)中即利用散列函數h,由關鍵字k計算出槽的位置。這里,函數h將關鍵字的全域U映射到散列表T[0...m-1]的槽位上。

 

這里存在一個問題:兩個關鍵字可能映射到同一個槽中。我們稱這種情形為沖突(collision),解決沖突可以通過選擇一個合適的散列函數h來做到這一點。但是,由於|U|>m,故至少有兩個關鍵字其散列值相同,所以想要完全避免沖突是不可能的。因此一方面可以通過精心設計的散列函數來盡量減少沖突的次數,另一方面仍需要解決可能出現沖突的方法。

本文介紹幾種沖突解決的方法,主要包括鏈表法和開放尋址法。其中開放尋址法又有幾種可選的方法:線性探查、二次探查、雙重散列、隨機散列

接下來介紹幾種常用的散列函數

4、散列函數                 

    好的散列函數的特點是每個關鍵字都等可能的散列到m個槽位上的任何一個中去,並與其他的關鍵字已被散列到哪一個槽位無關。多數散列函數都是假定關鍵字域為自然數N={0,1,2,....},如果給的關鍵字不是自然數,則必須有一種方法將它們解釋為自然數。例如對關鍵字為字符串時,可以通過將字符串中每個字符的ASCII碼相加,轉換為自然數。在實際工作中經常用字符串作為關鍵字,例如身姓名、職位等等。這個時候需要設計一個好的散列函數進程處理關鍵字為字符串的元素。下面代碼為將字符串中每個字符的ASCII碼相加,轉換為自然數的方法。

1 int Hash(const string& key,int tablesize)
2 {
3     int hashVal = 0;
4     for(int i=0;i<key.length();i++)
5            hashVal += key[i];
6     return hashVal % tableSize;
7 }

 

有許多優秀的字符串散列函數,下面鏈接可以參考https://www.byvoid.com/blog/string-hash-compare

   4.1、除法散列

通過取k除以m的余數,將關鍵字k映射到m個槽的某一個中去。散列函數為:h(k)=k mod m 。m不應是2的冪,通常m的值是與2的整數冪不太接近的質數。

例如:下面的數如何通過除法散列映射到具有11個槽的散列表中:

23

346

48

通過除法散列:

23 % 11 = 1(余數是1)

346 % 11 = 5(余數是5)

48 % 11 = 4(余數是4)

則應該插入到1,5,4槽中,想下面所示:

    4.2、乘法散列

乘法散列法構造散列函數需要兩個步驟。第一步,用關鍵字k乘上常數A(0<A<1),並抽取kA的小數部分。然后,用m乘以這個值,再取結果的底。散列函數如下:h(k) = m(kA mod 1)。

    4.3、全域散列

任何一個特定的散列函數都可能將特定的n個關鍵字全部散列到同一個槽中,使得平均的檢索時間為Θ(n)。為了避免這種情況,唯一有效的改進方法是隨機地選擇散列函數,使之獨立與要存儲的關鍵字。這種方法稱為全域散列(universal hashing)

全域散列在執行開始時,就從一組精心設計的函數中,隨機地選擇一個作為散列函數。因為隨機地選擇散列函數,算法在每一次執行時都會有所不同,甚至相同的輸入都會如此。這樣就可以確保對於任何輸入,算法都具有較好的平均情況性能. 

選擇一個足夠大的質數p,使得每一個可能的關鍵字都落在0到p-1的范圍內。設Zp表示集合{0, 1, …, p-1},Zp*表示集合{1, 2, …, p-1}。對於任何a∈Zp*和任何b∈Zp,定義散列函數ha,b

ha,b = ((ak+b) mod p) mod m;其中a,b是滿足自己集合的隨機數;

    4.4、完全散列

如果某種散列技術可以在查找時,最壞情況內存訪問次數為O(1)的話,則稱其為完全散列(perfect hashing)。當關鍵字集合是靜態的時,這種最壞情況的性能是可以達到的。所謂靜態就是指一旦各關鍵字存入表中后,關鍵字集合就不再變化了。

我們可以用一種兩級的散列方案來實現完全散列,其中每級上采用的都是全域散列。如下圖:

首先第一級使用全域散列把元素散列到各個槽中,這與其它的散列表沒什么不一樣。但在處理碰撞時,並不像鏈接法(碰撞處理方法)一樣使用鏈表,而是對在同一個槽中的元素再進行一次散列操作。也就是說,每一個(有元素的)槽里都維護着一張散列表,該表的大小為槽中元素數的平方,例如,有3個元素在同一個槽的話,該槽的二級散列表大小為9。不僅如此,每個槽都使用不同的散列函數,在全域散列函數簇h(k) = ((a*k+b) mod p) mod m中選擇不同的a值和b值,但所有槽共用一個p值如101。每個槽中的(二級)散列函數可以保證不發生碰撞情況。

可 以證明,當二級散列表的大小為槽內元素數的平方時,從全域散列函數簇中隨機選擇一個散列函數,會產生碰撞的概率小於1/2。所以每個槽隨機選擇散列函數后,如果產生了碰撞,可以再次嘗試選擇其它散列函數,但這種嘗試的次數是非常少的。

雖然二級散列表的大小要求是槽內元素數的平方,看起來很大,但可以證明,當散列表的槽的數量和元素數量相同時(m=n),所有的二級散列表的大小的總量的期望值會小於2*n,即Ө(n)

5、碰撞處理方法                                

下面介紹幾種沖突解決的方法,主要包括鏈表法和開放尋址法。其中開放尋址法又有幾種可選的方法:線性探查、二次探查、雙重散列、隨機散列

5.1、鏈表法 

在鏈接法中,把散列到同一槽中的所有元素(沖突的元素)都放在一個鏈表中;

若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數組T[0..m-1]。凡是散列地址為i的結點,均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大於1,但一般均取α≤1。

  舉例說明鏈接法的執行過程,設有一組關鍵字為(26,36,41,38,44,15,68,12,6,51),用除余法構造散列函數,初始情況如下圖所示:

5.2、開放尋址法

   開放尋址法是另外一個處理元素沖突的方法;鏈表法是把沖突的元素依次放到一串鏈表中,而開放尋址法的思路是:在產生沖突的情況下,在hashtable中尋找其他空閑的槽位插入;當然,如何尋找其他空閑的槽位,我們有幾種方法,包括:線性探查、二次探查、雙重散列、隨機散列;下面逐個講解。

5.2.1、線性探查

給定一個普通的散列函數h':U-->{0,1,...,m-1},稱為輔助散列函數,線性探查方法采用的散列函數為

h(k,i)=(h'(k)+i) mod m, i = 0,1,...,m-1

給定一個關鍵字k,首先探查槽T[h'(k)],即由輔助三列函數所給出的槽位。再探測T[h'(k)+1],依次類推,直到槽T[m-1]。然后,又繞到槽T[0],T[1],...,直到最后探測到槽T[h'(k)-1]。

線性探測方法比較容易實現,但它存在着一個問題,稱為一次群集。隨着連續被占用的槽不斷增加,平均查找時間也隨之不斷增加。集群現象很容易出現,這是因為當一個空槽前有i個滿的槽時,該空槽下一個將被占用的概率是(i+1)/m。連續被占用的槽就會變得越來越長,因而平均查詢時間也會越來越大。采用例子進行說明線性探測過程,已知一組關鍵字為(26,36,41,38,44,15,68,12,6,51),用除法散列構造散列函數,初始情況如下圖所示:

5.2.2、二次探查

h(k,i)=(h'(k)+c₁i+c₂i²) mod m  , i = 0,1,...,m-1

其中h'是一個輔助散列函數,c₁和c₂為正的輔助常數,i=0,1,...m-1。初始的探查位置為T[h'(k)],后續的探查位置要加上一個偏移量,該偏移量以二次的方式依賴於探查序號i。這種探查方法的效果要比線性探查好很多,但是,為了能夠充分利用散列表,c₁,c₂和m的值要受到限制。此外,如果兩個關鍵字的初始探查位置相同,那么它們的探查序列也是相同的。這一性質可導致一種輕度的群集,稱為二次群集。

5.2.3、雙重散列

雙重散列(double hashing)是用於開放尋址法的最好方法之一,因為它所產生的排列具有隨機選擇隊列的許多特性。雙重散列采用如下形式的散列函數:

h(k,i)=(h₁(k)+ih₂(k)) mod m, i = 0,1,...,m-1

其中h₁和h₂均為輔助散列函數。初始探查位置為T[h₁(k)],后續的探查位置是前一個位置加上偏移量h₂(k)模m。因此,不像線性探查或二次探查,這里的探查序列以兩種不同方式依賴於關鍵字k,因為初始探查位置、偏移量或者兩則都可能發生變化。下圖給出了一個使用雙重散列法進行插入的例子。

上圖說明:雙重散列法的插入。此處,散列表的大小為13,h₁(k)=k mod13,h₂(k)=1+(k mod 11)。以元素14為例:因為h₁(14)=(14 mod13)=1,槽1已被79占用,--》h₂(14)=1+(14 mod 11)=4,則h(14,1)=h₁(14)+h₂(14)=1+4=5,槽5已被98占用,--》h(14,2)=h₁(14)+2*h₂(14)=1+2*4=9,槽9空閑,則插入到槽9中;所以在探查了槽1和槽5,並發現它們被占用后,關鍵字14插入了槽9中

5.2.4、隨機散列

隨機散列散列函數:

h(k,i)=(h₁(k)+Random(i)) mod m,Random(i)是隨機整數,大小屬於集合{0,1,2,......,m-1}

其中h₁為輔助散列函數。初始探查位置為T[h₁(k)],后續的探查位置是前一個位置加上偏移量Random(i)模m;Random(i)是系統產生的隨機數,隨機散列數組在探查之前生成,數組內的隨機數相互獨立;類似於全域散列函數,其實屬於散列函數范疇的,這里專門當做一種探查方法來說明,只是為了說明一個隨機的探查思想;

6、再散列問題                   

如果散列表滿了,再往散列表中插入新的元素時候就會失敗;或者散列表快滿時,進行插入是一個效率很低的過程;這個時候可以通過創建另外一個散列表,使得新的散列表的長度是當前散列表的2倍多一些,重新計算各個元素的hash值,插入到新的散列表中。再散列的問題是在什么時候進行最好,有下面情況可以判斷是否該進行再散列:

(1)當散列表將快要滿了,給定一個范圍,例如散列被中已經被用到了80%,這個時候進行再散列。

(2)當插入一個新元素失敗時候(相同關鍵字失敗除外),進行再散列。

(3)當插入一個新元素產生沖突次數過多時,進行再散列。

(3)對於鏈表法,根據裝載因子(已存放n個元素的、具有m個槽位的散列表T,裝載因子α=n/m)進行判斷,當裝載因子達到一定的閾值時候,進行再散列。

7、完整源碼 c++                   

下面代碼采用開放尋址法處理沖突,包括線性探查、二次探查、雙重散列探查、隨機散列探查實現;散列函數采用簡單的除法散列函數;當插入一個新元素產生沖突次數過多時,進行再散列

github源碼下載地址

HashTable.h

  1 //HashTable.h 開放尋址法哈希表類(HashTable類)
  2 #ifndef _HAXI_H_
  3 #define _HAXI_H_
  4 const int SUCCESS = 1;//成功
  5 const int UNSUCCESS = 0;//不成功
  6 const int DUPLICATE = -1;//關節字沖突(重復),不能再插入
  7 const int N = 4;//hashsize[]的容量
  8 int hashsize[N] = { 11, 19, 37, 73 };
  9 //哈希表容量遞增表,一個合適的素數序列,(重)建哈希表用到
 10 
 11 
 12 template<typename D>class HashTable
 13 {//帶數據元素類型D模板的開放尋址法哈希表類
 14 private://6個私有成員函數,5個私有數據成員
 15     D *elem;//數據元素存儲基址,動態分配數組
 16     int count, length;//數據元素個數,哈希表容量
 17     int sizeindex;//hashsize[sizeindex]為當前容量
 18     int    *rando;//隨機數數組指針
 19     int Hash(KeyType Key)
 20     {//一個簡單的哈希函數
 21         return Key%length;
 22     }
 23     int Hash2(KeyType Key)//用於雙重散列探索法
 24     {//雙重散列探查法的第二個哈希函數
 25         return Key % (length - 2);
 26     }
 27     void Random()
 28     {//建立偽隨機數組(用於隨機探查法)
 29         bool *ra = new bool[length];//
 30         rando = new int[length];
 31         int i;
 32         for (i = 1; i<length; i++)//設置ra[i]的初值
 33             ra[i] = false;//i不在隨機數數組中的標志
 34         //        srand(time(0));//設置隨機數種子
 35         for (i = 1; i<length; i++)//依次給rando[i]賦隨機值
 36         {
 37             do
 38             {
 39                 rando[i] = rand() % (length - 1) + 1;//給rando[i]賦值(1-length-1)
 40                 if (!ra[rando[i]])//偽隨機數組中沒有此數
 41                     ra[rando[i]] = true;//賦值成功
 42                 else
 43                     rando[i] = 0;
 44             } while (rando[i] == 0);//賦值失敗則重新賦值
 45             cout << "rando[" << i << "]=" << rando[i] << endl;
 46         }
 47         delete[]ra;
 48     }
 49     int d(int i, KeyType Key)//增量序列函數
 50     {//返回第i次沖突的增量
 51         switch (type)
 52         {
 53         case 0: return i;//線性探查法
 54         case 1: return ((i + 1) / 2)*((i + 1) / 2)*(int)pow(-1, i - 1);
 55             //二次探查法(1,-1,4,-4,9,-9,......)
 56         case 2: return i*Hash2(Key);//雙重散列探查法
 57         case 3: return rando[i];//隨機探查法(由Random()建立的一個偽隨機數列)
 58         default:return i;//默認線性探查法
 59         }
 60     }
 61     //開放尋址法求得關鍵字為Key的第i次沖突的地址p
 62     void collision(KeyType Key, int &p, int i)
 63     {
 64         p = (Hash(Key) + d(i, Key)) % length;//哈希函數加增量后再求余
 65         if (p<0)//得到負數(雙重探查可能出現)
 66             p = p + length;//保證非負
 67     }
 68     //重建哈希表
 69     void RecreateHashTable()
 70     {
 71         int i, len = length;//原容量
 72         D *p = elem;//p指向哈希表原有數據空間
 73         sizeindex++;//增大容量為下一個序列數
 74         if (sizeindex<N)
 75         {
 76             length = hashsize[sizeindex];
 77             elem = new D[length];
 78             assert(elem != NULL);
 79             for (i = 0; i<length; i++)//未填數據的標志
 80                 elem[i].key = EMPTY;
 81             for (i = 0; i<len; i++)//將p所指原elem中的數據插入到重建的哈希表中
 82             if (p[i].key != EMPTY && p[i].key != TOMB)
 83                 InsertHash(p[i]);
 84             delete[]p;
 85             if (type == 3)//隨機探查法
 86                 Random();
 87         }
 88     }
 89 public://7個公有成員函數,1個共有數據成員
 90     int type;//探查法類型(0-3)
 91     HashTable()
 92     {//構造函數,構造一個空的哈希表
 93         count = 0;
 94         sizeindex = 0;
 95         length = hashsize[sizeindex];
 96         elem = new D[length];
 97         assert(elem != NULL);
 98         for (int i = 0; i<length; i++)
 99             elem[i].key = EMPTY;//未填數據的格式
100         cout << "請輸入探查法的類型(0:線性;1:二次;2:雙散列;3:隨機):";
101         cin >> type;
102         if (type == 3)
103             Random();
104         else
105             rando = NULL;
106     }
107     ~HashTable()
108     {//析構函數,銷毀哈希表
109         if (elem != NULL)
110             delete[]elem;
111         if (type == 3)
112             delete[]rando;
113     }
114     //在開放尋址哈希表中查找關鍵字為Key的元素,若查找成功,以p指向待查數據元素在表中位置
115     //並返回SUCCESS;否則,以p指示插入位置,並返回UNSUCCESS
116     //c用以計沖突次數,其初值置零,供建表插入時參考
117     bool SearchHash(KeyType Key, int &p, int &c)
118     {
119         int c1, tomb = -1;//存找到的第一個墓碑地址(被刪除數據)
120         p = Hash(Key);//哈希地址
121         //下面的while代碼段,如果哈希地址處數據不是要查找的數據,
122         //則求下一個探查地址p,進行查找,直到碰撞次數超出定義的閾值
123 
124         while (elem[p].key == TOMB || elem[p].key != EMPTY && !EQ(Key, elem[p].key))
125         {
126             if (elem[p].key == TOMB && tomb == -1)//數據已被刪除,且是找到的第一個墓碑
127             {
128                 tomb = p;
129                 c1 = c;//沖突次數存於c1
130             }
131             c++;//沖突次數+1
132             if (c <= hashsize[sizeindex]/2)//在沖突次數閾值內,求下一個探查地址p
133                 collision(Key, p, c);
134             else
135                 break;
136         }
137         if EQ(Key, elem[p].key)//查找成功
138             return true;
139         else//查找不成功
140         {
141             if (tomb != -1)//查找過程中遇到過墓碑
142             {
143                 p = tomb;//將墓碑作為插入位置
144                 c = c1;//沖突次數
145             }
146             return false;
147         }
148     }
149     //查找不成功時將數據元素e插入到開放尋址哈希表中,並返回SUCCESS;查找成功時返回
150     //DUPLICATE,不插入數據元素;若沖突次數過大,則不插入,並重建哈希表,返回UNSUCCESS
151     int InsertHash(D e)
152     {
153 
154         int p, c = 0;
155         if (SearchHash(e.key, p, c))//查找成功,已有與e相同關鍵字 元素,不再插入
156             return DUPLICATE;
157         else if (c <= hashsize[sizeindex]/2)//為找到,沖突次數c也未達到上限(c的閾值可調),插入
158         {
159             elem[p] = e;
160             ++count;
161             return SUCCESS;
162         }
163         else//未找到,但沖突次數已達到上限,重建哈希表
164         {
165             cout << "按哈希地址的順序遍歷重建前的哈希表:" << endl;
166             TraverseHash(Visit);
167             cout << "重建哈希表" << endl;
168             RecreateHashTable();
169             return UNSUCCESS;
170         }
171     }
172     //從哈希表中刪除關節字為Key的數據元素,成功返回true,並將該位置的關鍵字設為TMOB;
173     //不成功返回false
174     bool DeleteHash(KeyType Key, D &e)
175     {
176 
177         int p, c=0;//一定要賦初值,不然c會是個隨機的數
178         if (SearchHash(Key, p, c))//查找成功
179         {
180             e = elem[p];
181             elem[p].key = TOMB;
182             --count;
183             return true;
184         }
185         else
186             return false;
187     }
188     //返回元素[i]的值
189     D GetElem(int i)const
190     {
191         return elem[i];
192     }
193     //按哈希地址的順序遍歷哈希表H
194     void TraverseHash(void(*visit)(int, D*))const
195     {
196         int i;
197         cout << "哈希地址0~" << length - 1 << endl;
198         for (i = 0; i<length; i++)
199         if (elem[i].key != EMPTY && elem[i].key != TOMB)
200             visit(i, &elem[i]);
201     }
202 };
203 #endif
View Code

HashTable.cpp(主測試函數)

  1 // 驗證HashTable類的成員函數
  2 #include <iostream>
  3 #include <fstream>
  4 #include <string>
  5 #include <assert.h>
  6 using namespace std;
  7 // 對兩個數值型關鍵字的比較約定為如下的宏定義
  8 #define EQ(a, b) ((a)==(b))
  9 const int EMPTY=0;//設置0為無數據標志(此時關鍵字不可為0)
 10 const int TOMB=-1;//設置-1為刪除數據標志(此時關鍵字不可為-1)
 11 typedef int KeyType;
 12 #include "HashTable.h"
 13 // 定義模板<D>的實參HD及相應的I/O操作
 14 struct HD
 15 {
 16     KeyType key;
 17     int order;
 18 };
 19 void Visit(int i, HD* c)
 20 {
 21     cout << '[' << i << "]: " << '(' << c->key << ", " << c->order << ')' << endl;
 22 }
 23 void Visit(HD c)
 24 {
 25     cout << '(' << c.key << ", " << c.order << ')';
 26 }
 27 void InputFromFile(ifstream &f, HD &c)
 28 {
 29     f >> c.key >> c.order;
 30 }
 31 void InputKey(int &k)
 32 {
 33     cin >> k;
 34 }
 35 
 36 void main()
 37 {
 38     HashTable<HD> h;
 39     int i, j, n, p=0;
 40     bool m;
 41     HD e;
 42     KeyType k;
 43     ifstream fin("input.txt");//第一行的數表示數據個數
 44     fin>>n;//由文件輸入數據個數
 45     //建立哈希表
 46     for(i=0; i<n; i++)
 47     {
 48         InputFromFile(fin, e);
 49         j=h.InsertHash(e);
 50         if(j==DUPLICATE)
 51         {
 52             cout<<"哈希表中已有關鍵字為"<<e.key<<"的數據,無法再插入數據";
 53             Visit(e);
 54             cout<<endl;
 55         }
 56         if(j==UNSUCCESS)//插入不成功,重建哈希表
 57             j=h.InsertHash(e);
 58     }
 59     fin.close();
 60     cout<<"按哈希地址的順序遍歷哈希表:"<<endl;
 61     h.TraverseHash(Visit);
 62 
 63     //刪除數據測試
 64     cout<<"請輸入待刪除數據的關鍵字:";
 65     InputKey(k);
 66     m=h.DeleteHash(k, e);
 67     if (m)
 68     {
 69         cout << "成功刪除數據";
 70         Visit(e);
 71         cout << endl;
 72     }
 73     else
 74         cout << "不存在關鍵字,無法刪除!" << endl;
 75     cout << "按哈希地址的順序遍歷哈希表:" << endl;
 76     h.TraverseHash(Visit);
 77     //查詢數據測試
 78     cout<<"請輸入待查找數據的關鍵字:";
 79     InputKey(k);
 80     n=0;
 81     j=h.SearchHash(k, p, n);
 82     if(j==SUCCESS)
 83     {
 84         Visit(h.GetElem(p));
 85         cout<<endl;
 86     }
 87     else
 88         cout<<"未找到"<<endl;
 89     
 90     //插入數據測試
 91     cout<<"插入數據,請輸入待插入數據的關鍵字:";
 92     InputKey(e.key);
 93     cout<<"請輸入待插入數據的order:";
 94     cin>>e.order;
 95     j=h.InsertHash(e);
 96     if (j==DUPLICATE)
 97     {
 98         cout << "哈希表中已有關鍵字為" << e.key << "的數據,無法再插入數據";
 99         Visit(e);
100         cout << endl;
101     }
102     if (j == UNSUCCESS)//插入不成功,重建哈希表
103         j = h.InsertHash(e);
104     cout<<"按哈希地址的順序遍歷哈希表:"<<endl;
105     h.TraverseHash(Visit);
106     
107 }
View Code

input.txt 文件內容

10
17 1
60 2
29 3
38 4
1 5
2 6
3 7
4 8
60 9
13 10
View Code

 

測試結果

8、參考資料                    

【1】 http://www.junevimer.com/2014/06/10/algorithms-hash-table.html#universal%20hashing

【2】 http://www.cnblogs.com/Anker/archive/2013/01/27/2879150.html

【3】 http://www.cs.uregina.ca/Links/class-info/210/Hash/#EXERCISE

【4】 https://www.byvoid.com/blog/string-hash-compare

【5】 http://blog.chinaunix.net/uid-26822401-id-3169705.html

【6】 http://blog.csdn.net/intrepyd/article/details/4359818


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM