前言:閑來蛋疼,周末在家陪老婆,中午還要親自操廚,操廚之前,加深下對hash的理解,寫下此篇。
本文:本篇文章主要類比了海量搜尋過程。從txt檔案擷取大量資料資訊(類比海量),建立hash表,然後輸入關鍵字(字串),能迅速定位要找的value。其實就是搜尋某個特定的字串。具體會貼出代碼,和驗證圖。整個過程類比了海量尋找的過程,能切實感覺到hash 海量處理的優勢。
先貼幾張運行:
圖 1
稍微解釋一下:結合圖1 中的第一行, 第359次,指發生映射的順序數;992處,指雜湊數組中的位置,即hashTable[992];存入的字串,為我們雜湊映射以後把原字串存起來,以便尋找時比較;value,指映射後要定位的值,比如:我們要尋找字串出現的機率,或是字串出現的次數等等,在程式裡相當於留了一個借口。注意:所有的資料從txt裡讀取,而txt裡的的資料由程式類比產生,採用rand()函數。具體見後面的程式源碼或下載後面貼出的工程檔案的。我產生的是5000個,通過修改宏參數,可以更多,非常貼近真實。
圖 2
圖 2 最後一行,顯示了要尋找字串的位置。如,藍色部分。
網上先搜尋了一番,發現要麼就是理論一堆,要麼就是貼出關鍵代碼,讓我等菜鳥始終沒法體會海量的涵義。
本文章分為三大部分。第一部分:hash的基本知識,第二部分,hash映射的建立和衝突的解決,具體代碼實現
hash 的主要作用就是快速尋找,其時間複雜度是O(1),是最好的尋找演算法。對於hash的入門層級可參考我的一篇其他博文 資料結構-練習2 雜湊表。hash的最基本理解就是:根據要尋找的字串的ASCII(經過變換處理),通過雜湊函數,轉換成與其儲存位置的下標,這樣在hash表建立以後,我們就可以根據字串本身,以相同的雜湊函數映射方式迅速定位到其位置,然後尋找出相關資訊。
雜湊函數的構造就成為了最重要的任務之一,常見的方法有:直接取址法,平方取中法,除留餘數法,數字分析法,摺疊法,隨機數法。
本文章的例子採用除留餘數法:
基本原理為:H(key)=key%MOD,MOD 為小於表長的一個數,本例子為1024.
結合代碼具體分析(SDBM法):
int SDBMHash(char* str) { int hash = 0; while(*str!='\0') {
//相當於:65599*hash+*str++,這就是映射函數,通過字串裡每個字元的ASCIIl累計形成 hash = *str++ + (hash << 6) + (hash << 16) - hash; } return (hash & 0x7FFFFFFF); }
最後別忘了求餘數:key=key%HashTableLen;//HashTableLen=1024,key就是SDBMHash的傳回值。
衝突解決是第二個重要的過程,基本方法包括:鏈表法,開放地址法,再雜湊,開闢公用溢出區。
本文章例子採用常見的鏈表法:
,左邊為:第一次獲得的key的位置,顯然,兩個不同的字串很可能有相同的key,這時我們在後面加個鏈表格儲存體一下。
代碼如:
p=&HashT[key];while(p->next!=NULL) p=p->next;{p->next=new hashNode();p->next->used=true;strcpy(p->next->key,str);p->next->value=rand()%100;
}
貼完整個程式的代碼:
#include<iostream>#include<fstream>#include<string>using namespace std;const unsigned int NUM=5000;const int HashTableLen=1024;int count=0;//用於記錄hash映射時,發生的次數,驗證作用struct hashNode {bool used;hashNode* next;char key[28];unsigned int value;hashNode(){used=false;next=NULL;}hashNode(char* KEY,unsigned int VALUE){strcpy(key,KEY);value=VALUE;}}HashT[HashTableLen] ;// 雜湊表的長度為1024,所以取摸的時候用1024;unsigned int elfhash(char* s);//declare the functionvoid createStrTXT();void establishHaT();void FindStr(char*);int main(){ //for(int i=0;i<50000;++i) //產生txt,檔案,往裡面隨機寫入字串。 //createStrTXT(); establishHaT(); FindStr("qd`e_usigh]oscfwsnfreshayug"); //FindStr(); /* char str[30]; ifstream ifs("str.txt"); ifs>>str; */} int SDBMHash(char* str) { int hash = 0; while(*str!='\0') { hash = *str++ + (hash << 6) + (hash << 16) - hash; } return (hash & 0x7FFFFFFF); }unsigned int elfhash(char *s){ int hash = 0, x = 0; while (*s){ hash = (hash << 4) + (*s++); if(((x = hash) & 0xf0000000L) != 0){ hash ^= (x >> 24); hash &= ~x; } } return hash & 0x7fffffff;}void createStrTXT(){for(int i=0;i<NUM;++i){char temp[30]={'\n','\r',rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,rand()%31+91,'\0'}; char*str=temp; ofstream ofs("str.txt",ios::app); ofs<<str;}}void establishHaT()//採用鏈表法構造hash表{ hashNode * p; int key; char str[28]; ifstream ifs("str.txt"); while( ifs>>str) { key=SDBMHash(str); key=key%HashTableLen; if(HashT[key].used==false) { strcpy(HashT[key].key,str); HashT[key].used=true; HashT[key].value=rand()%100; count++; //偽隨機的目的是類比key-value 裡的value,裡面就是我們要尋找的值,e.g. 如果要尋找字串是否存 //在, printf("第%d次在 %d處發生了映射,存入的字串為:%s,value為:%d\n",count,key,str,HashT[key].value);//裡面就村字串本身;如果要查某字串出現的機率, //裡面就是機率值,如果尋找的是字串出現的次數,裡面存的就是次數,正如百度的那道尋找top 10的 //面試題,原理跟這一樣 } else//衝突處理 { p=&HashT[key];while(p->next!=NULL) p=p->next;{p->next=new hashNode();p->next->used=true;strcpy(p->next->key,str);p->next->value=rand()%100;count++; printf("第%d次在 %d處發生了映射,存入的字串為:%s,value為:%d\n",count,key,str,HashT[key].value); } } } }void FindStr(char* str){ hashNode * p; int key; key=SDBMHash(str); key=key%HashTableLen; if(HashT[key].used==false) { printf("此字串不存在"); } else { p=&HashT[key]; while(p!=NULL) { if(!strcmp(p->key,str)) { printf("在%d位置處找到了目的字串:%s",key,str); break; } else { p=p->next; } if(p==NULL) printf("SORRY!字串沒有匹配的"); } } } 說明:函數 FindStr(char*)主要是尋找特定的字串,原理與構造hash函數的過程一樣。注意的是:寫入函數參數中的字串,不應該含有轉義符,因為轉義符號,程式可能不認識,所以,在TXT裡選一個沒有轉移符號的作為參數,如本例。createStrTXT();主要是建立txt檔案,NUM是建立的字串的個數,注意的是,我建立時,寫入檔案採用了ios::app,所以每次運行程式,如果都createStrTXT(),txt裡的字元 串個數會每次都增加5000次。所以建立一次就可以,以後就登出。工程檔案:此處下載。需要一積分,不好意思,我想攢點分下東西,沒積分的 留下郵箱。我將速度發送。