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

目錄                                         

內容                                          

一、引言                       html

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

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

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

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

 question 2:那麼,Hash Function 如何定義呢?算法

 hash function 有不少種定義方法,其中 最經常使用的是除法散列;在散列函數小節中會進行詳細介紹【除法散列、乘法散列、全域散列、徹底散列】vim

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

以下圖所示,想經過各hash function將Non-InTeger key轉換爲Integer key,而後再進行正常的運算。併發

 

Question 4:當多個關鍵字Key,經過hash function計算出的索引相同,就是說他們產生了「衝突」,這時該怎麼辦呢?dom

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

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

二、直接尋址              

    當關鍵字的的全域(範圍)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

三、散列尋址                 

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

 

這裏存在一個問題:兩個關鍵字可能映射到同一個槽中。咱們稱這種情形爲衝突(collision),解決衝突能夠經過選擇一個合適的散列函數h來作到這一點。可是,因爲|U|>m,故至少有兩個關鍵字其散列值相同,因此想要徹底避免衝突是不可能的。所以一方面能夠經過精心設計的散列函數來儘可能減小衝突的次數,另外一方面仍須要解決可能出現衝突的方法。

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

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

四、散列函數                 

    好的散列函數的特色是每一個關鍵字都等可能的散列到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.一、除法散列

經過取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.二、乘法散列

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

    4.三、全域散列

任何一個特定的散列函數均可能將特定的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.四、徹底散列

若是某種散列技術能夠在查找時,最壞狀況內存訪問次數爲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.一、鏈表法 

在連接法中,把散列到同一槽中的全部元素(衝突的元素)都放在一個鏈表中;

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

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

5.二、開放尋址法

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

5.2.一、線性探查

給定一個普通的散列函數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.二、二次探查

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.三、雙重散列

雙重散列(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.四、隨機散列

隨機散列散列函數:

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)是系統產生的隨機數,隨機散列數組在探查以前生成,數組內的隨機數相互獨立;相似於全域散列函數,其實屬於散列函數範疇的,這裏專門當作一種探查方法來講明,只是爲了說明一個隨機的探查思想;

六、再散列問題                   

若是散列表滿了,再往散列表中插入新的元素時候就會失敗;或者散列表快滿時,進行插入是一個效率很低的過程;這個時候能夠經過建立另一個散列表,使得新的散列表的長度是當前散列表的2倍多一些,從新計算各個元素的hash值,插入到新的散列表中。再散列的問題是在何時進行最好,有下面狀況能夠判斷是否該進行再散列:

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

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

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

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

七、完整源碼 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

 

測試結果

八、參考資料                    

【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

相關文章
相關標籤/搜索