hash-table基礎以及一些運用例子

最近在復習算法和數據結構 ,這章把hash表的概念和相關題目進行匯總。 ? ? ? ? ? ??

一、前言

1.1、哈希表和數組、以及鏈表的對比:

(1).數組的特點:尋址容易,插入和刪除困難;數組存儲連續,查找一個元素的時間復雜度為O(1);

(2).鏈表的特點:尋址困難,插入和刪除容易。鏈表存儲區是離散的,遍歷鏈表的元素的時間復雜度為O(N)。

(3).hash-table是根據關鍵值(key-value)來直接進行訪問的數據結構,它結合了數組和鏈表的優點。hash表的難點

在于設計hash函數,以及解決沖突。這里我們會在后面提及;

1.2、一個hash表運用的的直觀理解(內容取自教材書)

這里是一些聯系人的信息,如果要存儲這些信息你會怎么做?我們比較直觀的想法是,設計一個結構體,用鏈表來存儲。結構體里面包含一個char型數組存放名字,char字符串存放電話號碼,和一個結構體指針用來存放下個結構體的地址。

[cpp]view plaincopy

張三?13980593357

李四?15828662334

王五?13409821234

張帥?13890583472

當要查找”王五 15828662334“這條記錄是否在這張鏈表中時,可能會從鏈表的頭結點開始遍歷,依次將每個結點中的姓名同”李四“進行比較,直到查找成功或者失敗為止,這種做法的時間復雜度為O(n)。即使采用二叉排序樹進行存儲,也最多為O(logn)。假設能夠通過”王五“這個信息直接獲取到該記錄在表中的存儲位置,就能省掉中間關鍵字比較的這個環節,復雜度直接降到O(1)。Hash表就能夠達到這樣的效果。

Hash表采用一個映射函數 f : key —> address 將關鍵字映射到該記錄在表中的存儲位置,從而在想要查找該記錄時,可以直接根據關鍵字和映射關系計算出該記錄在表中的存儲位置,通常情況下,這種映射關系稱作為Hash函數,而通過Hash函數和關鍵字計算出來的存儲位置(注意這里的存儲位置只是表中的存儲位置,并不是實際的物理地址)稱作為Hash地址。比如上述例子中,假如聯系人信息采用Hash表存儲,則當想要找到“李四”的信息時,直接根據“李四”和Hash函數計算出Hash地址即可。

二、hash函數的設計

1、整數的hash函數設計

常見的hash函數有三種,分別是:直接取余法、乘積取整法、平方取中法。下面一一介紹:

1.1、直接取余法

直接取余法根據字面意思我們就能理解到,它的基本實現是用關鍵字直接除以散列表的大小(我們一般取跟元素個數最

接近的質數作為散列表的大小)。如果知道Hash表的最大長度為m,可以取不大于m的最大質數p,然后對關鍵字進

行取余運算,h(key)=key%p。很多的書上認為,哈希表的大小最好是選擇一個大的質數,并且最好不要和2的整數冪接近。最不好的選擇是哈希表的大小恰好是2的整數冪。

這里可以這么認為:計算機是用二進制存儲的,當一個二進制數除以一個2的整數冪的時候,結果就是這個二進制數的后幾位,前面的位都丟失了,也就意味著丟失了一部分信息,進而導致哈希表中的元素分布不均勻。為了避免產生沖突,我們可以采用加、乘法、移位等等運算關系來進行處理,然后再取余數,獲得哈希地址。

下面是個例子。

[cpp]view plaincopy

staticintadditiveHash(String?key,intprime)//prime為我們選取的hash表大小。

{

inthash,?i;

for(hash?=?key.length(),?i?=?0;?i?<?key.length();?i++)

?hash?+=?key.charAt(i);

return(hash?%?prime);

}

1.2、乘積取整法

關鍵字k乘以一個在(0,1)中的實數(最好是無理數),得到一個(0,1)之間的實數;取出其小數部分,乘以m,再取整數部分,即得K在Hash表中的位置。

1.3、平方取中法

對關鍵字進行平方運算,然后取結果的中間幾位作為Hash地址。假如有以下關鍵字序列{421,423,436},平

方之后的結果為{177241,178929,190096},那么可以取{72,89,00}作為Hash地址。

2、字符串的hash函數設計

我們一般是通過某種算法,以把一個字符串"壓縮" 成一個整數。當然,一個32位整數是無法對應回一個字符串的,但在程序中,兩個字符串計算出的Hash值相等的可能非常小。下面我介紹幾個經典的字符串hash函數設計。

2.1"One-Way Hash"算法

這個算法是Blizzard的創作,是一個非常高效的把字符串轉換成整數的算法,舉個例子,字符串"unitneutralacritter.grp",通過這個算法得到的結果是0xA26067F3。

[cpp]view plaincopy

unsignedlongHashString(char*lpszFileName,?unsignedlongdwHashType)

{

unsignedchar*key?=?(unsignedchar*)lpszFileName;

unsignedlongseed1?=?0x7FED7FED,?seed2?=?0xEEEEEEEE;

intch;

while(*key?!=?0)

{

ch?=?toupper(*key++);//toupper是轉換為大寫

seed1?=?cryptTable[(dwHashType?<<?8)?+?ch]?^?(seed1?+?seed2);

seed2?=?ch?+?seed1?+?seed2?+?(seed2?<<?5)?+?3;

}

returnseed1;

}

運用上面的函數就可以把字符串轉化為整數,接下來我們用這個整數就可以通過hash函數產生hash地址了。

[cpp]view plaincopy

intGetHashTablePos(char*lpszString,?SOMESTRUCTURE?*lpTable,intnTableSize)

{

intnHash?=?HashString(lpszString),?nHashPos?=?nHash?%?nTableSize;

if(lpTable[nHashPos].bExists?&&?!strcmp(lpTable[nHashPos].pString,?lpszString))

returnnHashPos;

else

return-1;//Error?value

}

其他的字符串轉換成整數算法,可以查閱相關書籍,這不再深入分析。

三、hash沖突的解決方法

1、拉鏈法

最常用的一種解決哈希沖突的方法,我們可以理解為“鏈表的數組”,如圖:

左邊很明顯是個數組,數組的每個成員包括一個指針,指向一個鏈表的頭,當然這個鏈表可能為空,也可能元素很多。我們根據元素的一些特征把元素分配到不同的鏈表中去,也是根據這些特征,找到正確的鏈表,再從鏈表中找出這個元素。

這里給個例子:設有 m = 5 , H(K) = K mod 5 ,關鍵字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外鏈地址法所建立的哈希表如下圖所示:

2、開放定址法

用開放定址法解決沖突的做法是:當沖突發生時,使用某種探查(亦稱探測)技術在散列表中形成一個探查(測)序列。沿此序列逐個單元地查找,直到找到給定 的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。查找時探查到開放的 地址則表明表中無待查的關鍵字,即查找失敗。

注意:

①用開放定址法建立散列表時,建表前須將表中所有單元(更嚴格地說,是指單元中存儲的關鍵字)置空。

②空單元的表示與具體的應用相關。

按照形成探查序列的方法不同,可將開放定址法區分為線性探查法、線性補償探測法、隨機探測等。

2.1、線性探查法(Linear Probing)

該方法的基本思想是:

將散列表T[0..m-1]看成是一個循環向量,若初始探查的地址為d(即h(key)=d),則最長的探查序列為

d,d+l,d+2,…,m-1,0,1,…,d-

即:探查時從地址d開始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循環到T[0],T[1],…,直到探查到T[d-1]為止。

探查過程終止于三種情況:

(1)若當前探查的單元為空,則表示查找失敗(若是插入則將key寫入其中);

(2)若當前探查的單元中含有key,則查找成功,但對于插入意味著失敗;

(3)若探查到T[d-1]時仍未發現空單元也未找到key,則無論是查找還是插入均意味著失敗(此時表滿)。

利用開放地址法的一般形式,線性探查法的探查序列為:

hi=(h(key)+i)%m 0≤i≤m-1//即di=i

用線性探測法處理沖突,思路清晰,算法簡單,但存在下列缺點:

① 處理溢出需另編程序。一般可另外設立一個溢出表,專門用來存放上述哈希表中放不下的記錄。此溢出表最簡

單的結構是順序表,查找方法可用順序查找。

② 按上述算法建立起來的哈希表,刪除工作非常困難。假如要從哈希表 HT 中刪除一個記錄,按理應將這個記錄所

在位置置為空,但我們不能這樣做,而只能標上已被刪除的標記,否則,將會影響以后的查找。

③ 線性探測法很容易產生堆聚現象。所謂堆聚現象,就是存入哈希表的記錄在表中連成一片。按照線性探測法處

理沖突,如果生成哈希地址的連續序列愈長 ( 即不同關鍵字值的哈希地址相鄰在一起愈長 ) ,則當新的記錄加入該

表時,與這個序列發生沖突的可能性愈大。因此,哈希地址的較長連續序列比較短連續序列生長得快,這就意味

著,一旦出現堆聚 ( 伴隨著沖突 ) ,就將引起進一步的堆聚。

2.2、線性補償探測法

線性補償探測法的基本思想是:

將線性探測的步長從 1 改為 Q ,即將上述算法中的 j = (j + 1) % m 改為: j = (j + Q) % m ,而且要求 Q 與

m 是互質的,以便能探測到哈希表中的所有單元。

【例】 PDP-11 小型計算機中的匯編程序所用的符合表,就采用此方法來解決沖突,所用表長 m = 1321 ,選用

Q = 25 。

2.3、隨機探測

隨機探測的基本思想是:

將線性探測的步長從常數改為隨機數,即令: j = (j + RN) % m ,其中 RN 是一個隨機數。在實際程序中應預先

用隨機數發生器產生一個隨機序列,將此序列作為依次探測的步長。這樣就能使不同的關鍵字具有不同的探測次

序,從而可以避 免或減少堆聚。基于與線性探測法相同的理由,在線性補償探測法和隨機探測法中,刪除一個記

錄后也要打上刪除標記。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 一、散列的概念 散列方法的主要思想是根據結點的關鍵碼值來確定其存儲地址:以關鍵碼值K為自變量,通過一定的函數關系h...
    SeanMa閱讀 64,254評論 1 30
  • Map 是一種很常見的數據結構,用于存儲一些無序的鍵值對。在主流的編程語言中,默認就自帶它的實現。C、C++ 中的...
    一縷殤流化隱半邊冰霜閱讀 9,299評論 23 67
  • 第一章 Nginx簡介 Nginx是什么 沒有聽過Nginx?那么一定聽過它的“同行”Apache吧!Ngi...
    JokerW閱讀 32,736評論 24 1,002
  • 散列表,它是基于快速存取的角度設計的,也是一種典型的“空間換時間”的做法。顧名思義,該數據結構可以理解為一個線性表...
    yeying12321閱讀 3,703評論 0 6
  • 兩個舍友比腳臭。 一個說:“我把鞋子脫掉,這里的人就全跑了!” 另一個冷笑:“我脫下鞋子,這屋里的...
    遇見自然閱讀 384評論 1 2