原理
介紹
哈希表(Hash table,也叫散列表), 是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
哈希表hash table(key,value) 的做法其實很簡單,就是把Key通過一個固定的算法函數既所謂的哈希函數轉換成一個整型數字,然后就將該數字對數組長度進行取余,取余結果就當作數組的下標,將value存儲在以該數字為下標的數組空間里。
而當使用哈希表進行查詢的時候,就是再次使用哈希函數將key轉換為對應的數組下標,並定位到該空間獲取value,如此一來,就可以充分利用到數組的定位性能進行數據定位。
哈希表最大的優點,就是把數據的存儲和查找消耗的時間大大降低,幾乎可以看成是常數時間;而代價僅僅是消耗比較多的內存。然而在當前可利用內存越來越多的情況下,用空間換時間的做法是值得的。另外,編碼比較容易也是它的特點之一。 哈希表又叫做散列表,分為“開散列” 和“閉散列”。
我們使用一個下標范圍比較大的數組來存儲元素。可以設計一個函數(哈希函數, 也叫做散列函數),使得每個元素的關鍵字都與一個函數值(即數組下標)相對應,於是用這個數組單元來存儲這個元素;也可以簡單的理解為,按照關鍵字為每一 個元素“分類”,然后將這個元素存儲在相應“類”所對應的地方。
但是,不能夠保證每個元素的關鍵字與函數值是一一對應的,因此極有可能出現對於不同的元素,卻計算出了相同的函數值,這樣就產生了“沖突”,換句話說,就是把不同的元素分在了相同的“類”之中。后面我們將看到一種解決“沖突”的簡便做法。 總的來說,“直接定址”與“解決沖突”是哈希表的兩大特點。
哈希函數構造
就是映射函數構造,看某個元素具體屬於哪一個類別。
除余法: 選擇一個適當的正整數 p ,令 h(k ) = k mod p ,這里, p 如果選取的是比較大的素數,效果比較好。而且此法非常容易實現,因此是最常用的方法。最直觀的一種,上圖使用的就是這種散列法,公式:
index = value % 16
學過匯編的都知道,求模數其實是通過一個除法運算得到的,所以叫“除法散列法”。
平方散列法
求index是非常頻繁的操作,而乘法的運算要比除法來得省時(對現在的CPU來說,估計我們感覺不出來),所以我們考慮把除法換成乘法和一個位移操作。公式:
index = (value * value) >> 28 ( 右移,除以2^28。記法:左移變大,是乘。右移變小,是除)
數字選擇法: 如果關鍵字的位數比較多,超過長整型范圍而無法直接運算,可以選擇其中數字分布比較均勻的若干位,所組成的新的值作為關鍵字或者直接作為函數值。
斐波那契(Fibonacci)散列法:平方散列法的缺點是顯而易見的,所以我們能不能找出一個理想的乘數,而不是拿value本身當作乘數呢?答案是肯定的。
1,對於16位整數而言,這個乘數是40503
2,對於32位整數而言,這個乘數是2654435769
3,對於64位整數而言,這個乘數是11400714819323198485
這幾個“理想乘數”是如何得出來的呢?這跟一個法則有關,叫黃金分割法則,而描述黃金分割法則的最經典表達式無疑就是著名的斐波那契數列,即如此形式的序列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377, 610, 987, 1597, 2584, 4181, 6765, 10946,…。另外,斐波那契數列的值和太陽系八大行星的軌道半徑的比例出奇吻合。
對我們常見的32位整數而言,公式:
index = (value * 2654435769) >> 28
沖突處理
線性重新散列技術易於實現且可以較好的達到目的。令數組元素個數為 S ,則當 h(k) 已經存儲了元素的時候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存儲單元為止(或者從頭到尾掃描一圈仍未發現空單元,這就是哈希表已經滿了,發生了錯誤。當然這是可以通過擴大數組范圍避免的)。
舉例
哈希表支持的運算主要有:初始化(makenull)、哈希函數值的運算(h(x))、插入元素(insert)、查找元素(member)。 設插入的元素的關鍵字為 x ,A 為存儲的數組。
偽代碼
初始化:
const empty=maxlongint; // 用非常大的整數代表這個位置沒有存儲元素 p=9997; // 表的大小 procedure makenull; var i:integer; begin for i:=0 to p-1 do A[i]:=empty; End;
哈希函數值的運算根據函數的不同而變化,例如除余法的一個例子:
function h(x:longint):Integer; begin h:= x mod p; end;
我們注意到,插入和查找首先都需要對這個元素定位,即如果這個元素若存在,它應該存儲在什么位置,因此加入一個定位的函數 locate:
function locate(x:longint):integer; var orig,i:integer; begin orig:=h(x); i:=0; while (i<S)and(A[(orig+i)mod S]<>x)and(A[(orig+i)mod S]<>empty) do inc(i); //當這個循環停下來時,要么找到一個空的存儲單元,要么找到這個元 //素存儲的單元,要么表已經滿了 locate:=(orig+i) mod S; end;
插入元素 :
procedure insert(x:longint); var posi:integer; begin posi:=locate(x); //定位函數的返回值 if A[posi]=empty then A[posi]:=x else error; //error 即為發生了錯誤,當然這是可以避免的 end;
查找元素是否已經在表中:
procedure member(x:longint):boolean; var posi:integer; begin posi:=locate(x); if A[posi]=x then member:=true else member:=false; end;
當數據規模接近哈希表上界或者下界的時候,哈希表完全不能夠體現高效的特點,甚至還不如一般算法。但是如果規模在中央,它高效的特點可以充分體現。試驗表明當元素充滿哈希表的 90% 的時候,效率就已經開始明顯下降。這就給了我們提示:如果確定使用哈希表,應該盡量使數組開大,但對最太大的數組進行操作也比較費時間,需要找到一個平衡點。通常使它的容量至少是題目最大需求的 120% ,效果比較好(這個僅僅是經驗,沒有嚴格證明)。
什么時候適合應用哈希表呢?如果發現解決這個問題時經常要詢問:“某個元素是否在已知集合中?”,也就是需要高效的數據存儲和查找,則使用哈希表是最好不過的了!那么,在應用哈希表的過程中,值得注意的是什么呢?
哈希函數的設計很重要。一個不好的哈希函數,就是指造成很多沖突的情況,從前面的例子已經可以看出來,解決沖突會浪費掉大量時間,因此我們的目標 就是盡力避免沖突。前面提到,在使用“除余法”的時候,h(k)=k mod p ,p 最好是一個大素數。這就是為了盡力避免沖突。為什么呢?假設 p=1000 ,則哈希函數分類的標准實際上就變成了按照末三位數分類,這樣最多1000類,沖突會很多。一般地說,如果 p 的約數越多,那么沖突的幾率就越大。
簡單的證明:假設 p 是一個有較多約數的數,同時在數據中存在 q 滿足 gcd(p,q)=d >1 ,即有 p=a*d , q=b*d, 則有 q mod p= q – p* [q div p] =q – p*[b div a] . ① 其中 [b div a ] 的取值范圍是不會超過 [0,b] 的正整數。也就是說, [b div a] 的值只有 b+1 種可能,而 p 是一個預先確定的數。因此 ① 式的值就只有 b+1 種可能了。這樣,雖然mod 運算之后的余數仍然在 [0,p-1] 內,但是它的取值僅限於 ① 可能取到的那些值。也就是說余數的分布變得不均勻了。容易看出, p 的約數越多,發生這種余數分布不均勻的情況就越頻繁,沖突的幾率越高。而素數的約數是最少的,因此我們選用大素數。記住“素數是我們的得力助手”。
另一方面,一味的追求低沖突率也不好。理論上,是可以設計出一個幾乎完美,幾乎沒有沖突的函數的。然而,這樣做顯然不值得,因為這樣的函數設計 很浪費時間而且編碼一定很復雜,與其花費這么大的精力去設計函數,還不如用一個雖然沖突多一些但是編碼簡單的函數。因此,函數還需要易於編碼,即易於實 現。綜上所述,設計一個好的哈希函數是很關鍵的。而“好”的標准,就是較低的沖突率和易於實現。另外,使用哈希表並不是記住了前面的基本操作就能以不變應萬變的。有的時候,需要按照題目的要求對哈希表的結構作一些改進。往往一些簡單的改進就可以帶來巨大的方便。
這些只是一般原則,真正遇到試題的時候實際情況千變萬化,需要具體問題具體分析才行。
當然,以上講解的都是閉散列,如果使用鏈表,做開散列的話就可以更方便存儲和刪除了。其實這個和之前做18-600的malloc里面說的東西很類似。
拉鏈法
上面的方法使用數組實現的,其實很多時候需要使用數組鏈表來做。開一個數組,數組每個元素都是一個鏈表。(hash函數選擇,針對字符串,整數,排列,具體相應的hash方法。 碰撞處理,一種是open hashing,也稱為拉鏈法;另一種就是closed hashing,也稱開地址法,opened addressing。)
使用除法散列:
使用斐波那契散列:
使用擴展法:
d-left hashing中的d是多個的意思,我們先簡化這個問題,看一看2-left hashing。2-left hashing指的是將一個哈希表分成長度相等的兩半,分別叫做T1和T2,給T1和T2分別配備一個哈希函數,h1和h2。在存儲一個新的key時,同時用兩個哈希函數進行計算,得出兩個地址h1[key]和h2[key]。這時需要檢查T1中的h1[key]位置和T2中的h2[key]位置,哪一個位置已經存儲的(有碰撞的)key比較多,然后將新key存儲在負載少的位置。如果兩邊一樣多,比如兩個位置都為空或者都存儲了一個key,就把新key 存儲在左邊的T1子表中,2-left也由此而來。在查找一個key時,必須進行兩次hash,同時查找兩個位置。
hash索引跟B樹索引的區別。
Hash 索引結構的特殊性,其檢索效率非常高,索引的檢索可以一次定位,不像B-Tree 索引需要從根節點到枝節點,最后才能訪問到頁節點這樣多次的IO訪問,所以 Hash 索引的查詢效率要遠高於 B-Tree 索引。
(1)Hash 索引僅僅能滿足”=”,”IN”和”<=>”查詢,不能使用范圍查詢。
由於 Hash 索引比較的是進行 Hash 運算之后的 Hash 值,所以它只能用於等值的過濾,不能用於基於范圍的過濾,因為經過相應的 Hash 算法處理之后的 Hash 值的大小關系,並不能保證和Hash運算前完全一樣。
(2)Hash 索引無法被用來避免數據的排序操作。
由於 Hash 索引中存放的是經過 Hash 計算之后的 Hash 值,而且Hash值的大小關系並不一定和 Hash 運算前的鍵值完全一樣,所以數據庫無法利用索引的數據來避免任何排序運算;
(3)Hash 索引不能利用部分索引鍵查詢。
對於組合索引,Hash 索引在計算 Hash 值的時候是組合索引鍵合並后再一起計算 Hash 值,而不是單獨計算 Hash 值,所以通過組合索引的前面一個或幾個索引鍵進行查詢的時候,Hash 索引也無法被利用。
(4)Hash 索引在任何時候都不能避免表掃描。
前面已經知道,Hash 索引是將索引鍵通過 Hash 運算之后,將 Hash運算結果的 Hash 值和所對應的行指針信息存放於一個 Hash 表中,由於不同索引鍵存在相同 Hash 值,所以即使取滿足某個 Hash 鍵值的數據的記錄條數,也無法從 Hash 索引中直接完成查詢,還是要通過訪問表中的實際數據進行相應的比較,並得到相應的結果。
(5)Hash 索引遇到大量Hash值相等的情況后性能並不一定就會比B-Tree索引高。
對於選擇性比較低的索引鍵,如果創建 Hash 索引,那么將會存在大量記錄指針信息存於同一個 Hash 值相關聯。這樣要定位某一條記錄時就會非常麻煩,會浪費多次表數據的訪問,而造成整體性能低下。
實現
問題描述:設計哈希表實現電話號碼查詢系統,實現下列功能:
(1) 假定每個記錄有下列數據項:電話號碼、用戶名、地址。
(2) 一是從數據文件old.txt(自己現行建好)中讀入各項記錄,二是由系統隨機產生各記錄,並且把記錄保存到new.txt文件中以及顯示到屏幕上,記錄條數不要少於30,然后分別以電話號碼和用戶名為關鍵字建立哈希表。
(3) 分別采用偽隨機探測再散列法和再哈希法解決沖突。
(4) 查找並顯示給定電話號碼的記錄;查找並顯示給定用戶名的記錄。
(5) 將沒有查找的結果保存到結果文件Out.txt中,顯示查找結果前,要有提示語句。
// MyHashTable.cpp : 定義控制台應用程序的入口點。 ////設計哈希表實現電話號碼查詢系統 //說明:一是從文件old.txt中讀取的數據自己在程序運行前建立, // 二是由系統隨機生成數據,在程序運行由隨機數產生器生成,並且將產生的記錄保存到 new.txt文件。 //存在的問題:使用隨機產生的文件,在顯示時出現亂碼 #include "stdafx.h" #include<fstream>//文件流 #include<iostream> #include <string> using namespace std; const int D[] = {3,5,8,11,13,14,19,21};//預定再隨機數 const int HASH_MAXSIZE = 50;//哈希表長度 //記錄信息類型 class DataInfo { public: DataInfo();//默認構造函數 friend ostream& operator<<(ostream& out, const DataInfo& dataInfo); //重載輸出操作符 //friend class HashTable; //private: string name;//姓名 string phone;//電話號碼 string address;//地址 char sign;//沖突的標志位,'1'表示沖突,'0'表示無沖突 }; DataInfo::DataInfo():name(""), phone(""), address(""), sign('0') { } ostream& operator<<(ostream& out, const DataInfo& dataInfo) //重載輸出操作符 { cout << "姓名:" << dataInfo.name << " 電話:" << dataInfo.phone << " 地址:" << dataInfo.address << endl; return out; } //存放記錄的哈希表類型 class HashTable { public: HashTable();//默認構造函數 ~HashTable();//析構函數 int Random(int key, int i);// 偽隨機數探測再散列法處理沖突 void Hashname(DataInfo *dataInfo);//以名字為關鍵字建立哈希表 int Rehash(int key, string str);// 再哈希法處理沖突 注意處理沖突還有鏈地址法等 void Hashphone(DataInfo *dataInfo);//以電話為關鍵字建立哈希表 void Hash(char *fname, int n);// 建立哈希表 //fname 是數據儲存的文件的名稱,用於輸入數據,n是用戶選擇的查找方式 int Findname(string name);// 根據姓名查找哈希表中的記錄對應的關鍵碼 int Findphone(string phone);// 根據電話查找哈希表中的記錄對應的關鍵碼 void Outhash(int key);// 輸出哈希表中關鍵字碼對應的一條記錄 void Outfile(string name, int key);// 在沒有找到時輸出未找到的記錄 void Rafile();// 隨機生成文件,並將文件保存在 new.txt文檔中 void WriteToOldTxt();//在運行前先寫入數據 //private: DataInfo *value[HASH_MAXSIZE]; int length;//哈希表長度 }; HashTable::HashTable():length(0)//默認構造函數 { //memset(value, NULL, HASH_MAXSIZE*sizeof(DataInfo*)); for (int i=0; i<HASH_MAXSIZE; i++) { value[i] = new DataInfo(); } } HashTable::~HashTable()//析構函數 { delete[] *value; } void HashTable::WriteToOldTxt() { ofstream openfile("old.txt"); if (openfile.fail()) { cout << "文件打開錯誤!" << endl; exit(1); } string oldname; string oldphone; string oldaddress; for (int i=0; i<30; i++) { cout << "請輸入第" << i+1 << "條記錄:" << endl; cin >> oldname ; cin >> oldphone; cin >> oldaddress; openfile << oldname << " " << oldphone << " " << oldaddress << "," << endl; } openfile.close(); } int HashTable::Random(int key, int i)// 偽隨機數探測再散列法處理沖突 {//key是沖突時的哈希表關鍵碼,i是沖突的次數,N是哈希表長度 //成功處理沖突返回新的關鍵碼,未進行沖突處理則返回-1 int h; if(value[key]->sign == '1')//有沖突 { h = (key + D[i]) % HASH_MAXSIZE; return h; } return -1; } void HashTable::Hashname(DataInfo *dataInfo)//以名字為關鍵字建立哈希表 {//利用除留取余法建立以名字為關鍵字建立的哈希函數,在發生沖突時調用Random函數處理沖突 int i = 0; int key = 0; for (int t=0; dataInfo->name[t]!='\0'; t++) { key = key + dataInfo->name[t]; } key = key % 42; while(value[key]->sign == '1')//有沖突 { key = Random(key, i++);//處理沖突 } if(key == -1) exit(1);//無沖突 length++;//當前數據個數加 value[key]->name = dataInfo->name; value[key]->address = dataInfo->address; value[key]->phone = dataInfo->phone; value[key]->sign = '1';//表示該位置有值 //cout << value[key]->name << " " << value[key]->phone << " " << value[key]->address << endl; } int HashTable::Rehash(int key, string str)// 再哈希法處理沖突 {//再哈希時使用的是折疊法建立哈希函數 int h; int num1 = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0'); int num2 = (str[4] - '0') * 1000 + (str[5] - '0') * 100 + (str[6] - '0') * 10 + (str[7] - '0'); int num3 = (str[8] - '0') * 100 + (str[9] - '0') * 10 + (str[10] - '0'); h = num1 + num2 + num3; h = (h + key) % HASH_MAXSIZE; return h; } void HashTable::Hashphone(DataInfo *dataInfo)//以電話為關鍵字建立哈希表 {//利用除留取余法建立以電話為關鍵字建立的哈希函數,在發生沖突時調用Rehash函數處理沖突 int key = 0; int t; for(t=0; dataInfo->phone[t] != '\0'; t++) { key = key + dataInfo->phone[t]; } key = key % 42; while(value[key]->sign == '1')//有沖突 { key = Rehash(key, dataInfo->phone); } length++;//當前數據個數加 value[key]->name = dataInfo->name; value[key]->address = dataInfo->address; value[key]->phone = dataInfo->phone; value[key]->sign = '1';//表示該位置有值 } void HashTable::Outfile(string name, int key)//在沒有找到時輸出未找到的記錄 { ofstream fout; if((key == -1)||(value[key]->sign == '0'))//判斷哈希表中沒有記錄 { fout.open("out.txt",ios::app);//打開文件 if(fout.fail()) { cout << "文件打開失敗!" << endl; exit(1); } fout << name << endl;//將名字寫入文件,有個問題,每次寫入的時候總是將原來的內容替換了 fout.close(); } } void HashTable::Outhash(int key)//輸出哈希表中關鍵字碼對應的記錄 { if((key==-1)||(value[key]->sign=='0')) cout << "沒有找到這條記錄!" << endl; else { for(unsigned int i=0; value[key]->name[i]!='\0'; i++) { cout << value[key]->name[i]; } for(unsigned int i=0; i<10; i++) { cout << " "; } cout << value[key]->phone; for(int i=0; i<10; i++) { cout << " "; } cout << value[key]->address << endl; } } void HashTable::Rafile()//隨機生成文件,並將文件保存在new.txt文檔中 { ofstream fout; fout.open("new.txt");//打開文件,等待寫入 if(fout.fail()) { cout << "文件打開失敗!" << endl; exit(1); } for(int j=0; j<30; j++) { string name = ""; for(int i=0; i<20; i++)//隨機生成長個字的名字 { name += rand() % 26 + 'a';//名字是由個字母組成 } fout << name << " ";//將名字寫入文件 string phone = ""; for(int i=0; i<11; i++)//隨機生成長位的電話號碼 { phone += rand() % 10 + '0';//電話號碼是純數字 } fout << phone << " ";//將電話號碼寫入文件 string address = ""; for(int i=0; i<29; i++)//隨機生成長個字的名字 { address += rand() % 26 + 'a';//地址是由個字母組成 } address += ','; fout << address << endl;//將地址寫入文件 } fout.close(); } void HashTable::Hash(char *fname, int n)//建立哈希表 //fname是數據儲存的文件的名稱,用於輸入數據,n是用戶選擇的查找方式 //函數輸入數據,並根據選擇調用Hashname或Hashphone函數進行哈希表的建立 { ifstream fin; int i; fin.open(fname);//讀文件流對象 if(fin.fail()) { cout << "文件打開失敗!" << endl; exit(1); } while(!fin.eof())//按行讀入數據 { DataInfo *dataInfo = new DataInfo(); char* str = new char[100]; fin.getline(str, 100, '\n');//讀取一行數據 if(str[0] == '*')//判斷數據結束 { break; } i = 0;//記錄字符串數組的下標 //a-z:97-122 A-Z:65-90 //本程序的姓名和地址都使用小寫字母 while((str[i] < 97) || (str[i] > 122))//讀入名字 { i++; } for(; str[i]!=' '; i++) { dataInfo->name += str[i]; } while(str[i] == ' ') { i++; } for(int j=0; str[i]!=' '; j++,i++)//讀入電話號碼 { dataInfo->phone += str[i]; } while(str[i] == ' ') { i++; } for(int j=0; str[i]!=','; j++,i++)//讀入地址 { dataInfo->address += str[i]; } if(n == 1) { Hashname(dataInfo); } else { Hashphone(dataInfo);//以電話為關鍵字 } delete []str; delete dataInfo; } fin.close(); } int HashTable::Findname(string name)//根據姓名查找哈希表中的記錄對應的關鍵碼 { int i = 0; int j = 1; int t; int key = 0; for(key=0, t=0; name[t] != '\0'; t++) { key = key + name[t]; } key = key % 42; while((value[key]->sign == '1') && (value[key]->name != name)) { key = Random(key, i++); j++; if(j >= length) return -1; } return key; } int HashTable::Findphone(string phone)//根據電話查找哈希表中的記錄對應的關鍵碼 { int key = 0; int t; for(t=0; phone[t] != '\0' ; t++) { key = key + phone[t]; } key = key % 42; int j = 1; while((value[key]->sign == '1') && (value[key]->phone != phone)) { key = Rehash(key, phone); j++; if(j >= length) { return -1; } } return key; } void main() { //WriteToOldTxt(); int k; int ch; char *Fname; HashTable *ht = new HashTable; while(1) { system("cls");//cls命令清除屏幕上所有的文字 cout << "歡迎使用本系統!" << endl << endl; cout << "請選擇數據" << endl; cout << "1.使用已有數據文件" << endl; cout << "2.隨機生成數據文件" << endl; cout << "0.結束" << endl; cout << "輸入相應序號選擇功能:"; cin >> k; switch(k) { case 0: return; case 1: Fname = "old.txt";//從數據文件old.txt(自己現行建好)中讀入各項記錄 break; case 2: ht->Rafile(); Fname = "new.txt";//由系統隨機產生各記錄,並且把記錄保存到new.txt文件中 break; default: cout << "輸入序號有誤,退出程序。" << endl; return; } do { system("cls"); cout << " 請選擇查找方式" << endl; cout << "1.通過姓名查找" << endl; cout << "2.通過電話查找" << endl; cout << "輸入相應序號選擇功能:"; cin >> ch; if((ch != 1) && (ch != 2)) cout << "輸入序號有誤!" << endl; }while((ch != 1) && (ch != 2)); ht->Hash(Fname, ch); while(ch == 1) { int choice; cout << endl << "請選擇功能" << endl; cout << "1.輸入姓名查找數據" << endl; cout << "2.顯示哈希表" << endl; cout << "0.退出"<<endl; cout << "輸入相應序號選擇功能:"; cin >> choice; switch(choice) { case 1: {//注意此處應該加上大括號 int key1; string name; cout << "請輸入姓名:"; cin >> name; key1 = ht->Findname(name); ht->Outfile(name, key1); ht->Outhash(key1); } break; case 2: { for(int i=0; i<HASH_MAXSIZE; i++) { if(ht->value[i]->sign!='0') { ht->Outhash(i); } } } break; default: cout << endl << "您的輸入有誤!" << endl; } if(choice == 0) { return; } } while(ch == 2) { int choice; cout << endl << "請選擇功能" << endl; cout << "1.輸入電話查找數據" << endl; cout << "2.顯示哈希表"<<endl; cout << "0.退出"<<endl; cout << "輸入相應序號選擇功能:"; cin >> choice; switch(choice) { case 1: { int key2; string phone; cout << "請輸入11位的電話號碼:"; do { cin >> phone; if(phone.length() != 11) { cout << "電話號碼應為11位!\n請重新輸入:"; } }while(phone.length() != 11); key2 = ht->Findphone(phone); ht->Outfile(phone, key2); ht->Outhash(key2); } break; case 2: { for(int i=0; i<HASH_MAXSIZE; i++) { if(ht->value[i]->sign != '0') { ht->Outhash(i); } } } break; default: cout << endl << "您的輸入有誤!" << endl; } if(choice == 0) { return; } } while((ch != 1) && (ch != 2)) { cout << "您的輸入有誤!請輸入相應需要選擇功能:"; } } system("pause"); }
代碼實現來源:
http://blog.csdn.net/htyurencaotang/article/details/7881427
原理說明來源:
http://www.tuicool.com/articles/BvI3Ir http://blog.csdn.net/nju_yaho/article/details/7402208 http://blog.csdn.net/duan19920101/article/details/51579136 http://blog.sina.com.cn/s/blog_6776884e0100pko1.html http://blog.csdn.net/v_july_v/article/details/6256463