hash-table基礎(chǔ)以及一些運(yùn)用例子

最近在復(fù)習(xí)算法和數(shù)據(jù)結(jié)構(gòu) 钟些,這章把hash表的概念和相關(guān)題目進(jìn)行匯總墩朦。 ? ? ? ? ? ??

一坯认、前言

1.1、哈希表和數(shù)組氓涣、以及鏈表的對(duì)比:

(1).數(shù)組的特點(diǎn):尋址容易牛哺,插入和刪除困難;數(shù)組存儲(chǔ)連續(xù),查找一個(gè)元素的時(shí)間復(fù)雜度為O(1)劳吠;

(2).鏈表的特點(diǎn):尋址困難引润,插入和刪除容易。鏈表存儲(chǔ)區(qū)是離散的痒玩,遍歷鏈表的元素的時(shí)間復(fù)雜度為O(N)淳附。

(3).hash-table是根據(jù)關(guān)鍵值(key-value)來(lái)直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)议慰,它結(jié)合了數(shù)組和鏈表的優(yōu)點(diǎn)。hash表的難點(diǎn)

在于設(shè)計(jì)hash函數(shù)奴曙,以及解決沖突别凹。這里我們會(huì)在后面提及;

1.2洽糟、一個(gè)hash表運(yùn)用的的直觀理解(內(nèi)容取自教材書)

這里是一些聯(lián)系人的信息炉菲,如果要存儲(chǔ)這些信息你會(huì)怎么做?我們比較直觀的想法是脊框,設(shè)計(jì)一個(gè)結(jié)構(gòu)體颁督,用鏈表來(lái)存儲(chǔ)。結(jié)構(gòu)體里面包含一個(gè)char型數(shù)組存放名字浇雹,char字符串存放電話號(hào)碼,和一個(gè)結(jié)構(gòu)體指針用來(lái)存放下個(gè)結(jié)構(gòu)體的地址屿讽。

[cpp]view plaincopy

張三?13980593357

李四?15828662334

王五?13409821234

張帥?13890583472

當(dāng)要查找”王五 15828662334“這條記錄是否在這張鏈表中時(shí)昭灵,可能會(huì)從鏈表的頭結(jié)點(diǎn)開(kāi)始遍歷,依次將每個(gè)結(jié)點(diǎn)中的姓名同”李四“進(jìn)行比較伐谈,直到查找成功或者失敗為止烂完,這種做法的時(shí)間復(fù)雜度為O(n)。即使采用二叉排序樹(shù)進(jìn)行存儲(chǔ)诵棵,也最多為O(logn)抠蚣。假設(shè)能夠通過(guò)”王五“這個(gè)信息直接獲取到該記錄在表中的存儲(chǔ)位置,就能省掉中間關(guān)鍵字比較的這個(gè)環(huán)節(jié)履澳,復(fù)雜度直接降到O(1)嘶窄。Hash表就能夠達(dá)到這樣的效果。

Hash表采用一個(gè)映射函數(shù) f : key —> address 將關(guān)鍵字映射到該記錄在表中的存儲(chǔ)位置距贷,從而在想要查找該記錄時(shí)柄冲,可以直接根據(jù)關(guān)鍵字和映射關(guān)系計(jì)算出該記錄在表中的存儲(chǔ)位置,通常情況下忠蝗,這種映射關(guān)系稱作為Hash函數(shù)现横,而通過(guò)Hash函數(shù)和關(guān)鍵字計(jì)算出來(lái)的存儲(chǔ)位置(注意這里的存儲(chǔ)位置只是表中的存儲(chǔ)位置,并不是實(shí)際的物理地址)稱作為Hash地址阁最。比如上述例子中戒祠,假如聯(lián)系人信息采用Hash表存儲(chǔ),則當(dāng)想要找到“李四”的信息時(shí)速种,直接根據(jù)“李四”和Hash函數(shù)計(jì)算出Hash地址即可姜盈。

二、hash函數(shù)的設(shè)計(jì)

1哟旗、整數(shù)的hash函數(shù)設(shè)計(jì)

常見(jiàn)的hash函數(shù)有三種贩据,分別是:直接取余法栋操、乘積取整法、平方取中法饱亮。下面一一介紹:

1.1矾芙、直接取余法

直接取余法根據(jù)字面意思我們就能理解到,它的基本實(shí)現(xiàn)是用關(guān)鍵字直接除以散列表的大小(我們一般取跟元素個(gè)數(shù)最

接近的質(zhì)數(shù)作為散列表的大小)近上。如果知道Hash表的最大長(zhǎng)度為m剔宪,可以取不大于m的最大質(zhì)數(shù)p,然后對(duì)關(guān)鍵字進(jìn)

行取余運(yùn)算壹无,h(key)=key%p葱绒。很多的書上認(rèn)為,哈希表的大小最好是選擇一個(gè)大的質(zhì)數(shù)斗锭,并且最好不要和2的整數(shù)冪接近地淀。最不好的選擇是哈希表的大小恰好是2的整數(shù)冪。

這里可以這么認(rèn)為:計(jì)算機(jī)是用二進(jìn)制存儲(chǔ)的岖是,當(dāng)一個(gè)二進(jìn)制數(shù)除以一個(gè)2的整數(shù)冪的時(shí)候帮毁,結(jié)果就是這個(gè)二進(jìn)制數(shù)的后幾位,前面的位都丟失了豺撑,也就意味著丟失了一部分信息烈疚,進(jìn)而導(dǎo)致哈希表中的元素分布不均勻。為了避免產(chǎn)生沖突聪轿,我們可以采用加爷肝、乘法、移位等等運(yùn)算關(guān)系來(lái)進(jìn)行處理陆错,然后再取余數(shù)灯抛,獲得哈希地址。

下面是個(gè)例子危号。

[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、乘積取整法

關(guān)鍵字k乘以一個(gè)在(0,1)中的實(shí)數(shù)(最好是無(wú)理數(shù))外莲,得到一個(gè)(0,1)之間的實(shí)數(shù)猪半;取出其小數(shù)部分,乘以m偷线,再取整數(shù)部分磨确,即得K在Hash表中的位置。

1.3、平方取中法

對(duì)關(guān)鍵字進(jìn)行平方運(yùn)算,然后取結(jié)果的中間幾位作為Hash地址鸯隅。假如有以下關(guān)鍵字序列{421卒蘸,423,436}庞钢,平

方之后的結(jié)果為{177241晕窑,178929粘优,190096}骗炉,那么可以取{72照宝,89,00}作為Hash地址句葵。

2厕鹃、字符串的hash函數(shù)設(shè)計(jì)

我們一般是通過(guò)某種算法,以把一個(gè)字符串"壓縮" 成一個(gè)整數(shù)乍丈。當(dāng)然剂碴,一個(gè)32位整數(shù)是無(wú)法對(duì)應(yīng)回一個(gè)字符串的,但在程序中轻专,兩個(gè)字符串計(jì)算出的Hash值相等的可能非常小忆矛。下面我介紹幾個(gè)經(jīng)典的字符串hash函數(shù)設(shè)計(jì)。

2.1"One-Way Hash"算法

這個(gè)算法是Blizzard的創(chuàng)作请垛,是一個(gè)非常高效的把字符串轉(zhuǎn)換成整數(shù)的算法洪碳,舉個(gè)例子,字符串"unitneutralacritter.grp"叼屠,通過(guò)這個(gè)算法得到的結(jié)果是0xA26067F3。

[cpp]view plaincopy

unsignedlongHashString(char*lpszFileName,?unsignedlongdwHashType)

{

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

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

intch;

while(*key?!=?0)

{

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

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

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

}

returnseed1;

}

運(yùn)用上面的函數(shù)就可以把字符串轉(zhuǎn)化為整數(shù)绞铃,接下來(lái)我們用這個(gè)整數(shù)就可以通過(guò)hash函數(shù)產(chǎn)生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

}

其他的字符串轉(zhuǎn)換成整數(shù)算法,可以查閱相關(guān)書籍儿捧,這不再深入分析荚坞。

三、hash沖突的解決方法

1菲盾、拉鏈法

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

左邊很明顯是個(gè)數(shù)組懒鉴,數(shù)組的每個(gè)成員包括一個(gè)指針诡挂,指向一個(gè)鏈表的頭,當(dāng)然這個(gè)鏈表可能為空临谱,也可能元素很多璃俗。我們根據(jù)元素的一些特征把元素分配到不同的鏈表中去,也是根據(jù)這些特征悉默,找到正確的鏈表城豁,再?gòu)逆湵碇姓页鲞@個(gè)元素。

這里給個(gè)例子:設(shè)有 m = 5 抄课, H(K) = K mod 5 唱星,關(guān)鍵字值序例 5 雳旅, 21 , 17 间聊, 9 攒盈, 15 , 36 甸饱, 41 沦童, 24 ,按外鏈地址法所建立的哈希表如下圖所示:

2叹话、開(kāi)放定址法

用開(kāi)放定址法解決沖突的做法是:當(dāng)沖突發(fā)生時(shí)偷遗,使用某種探查(亦稱探測(cè))技術(shù)在散列表中形成一個(gè)探查(測(cè))序列。沿此序列逐個(gè)單元地查找驼壶,直到找到給定 的關(guān)鍵字氏豌,或者碰到一個(gè)開(kāi)放的地址(即該地址單元為空)為止(若要插入,在探查到開(kāi)放的地址热凹,則可將待插入的新結(jié)點(diǎn)存人該地址單元)泵喘。查找時(shí)探查到開(kāi)放的 地址則表明表中無(wú)待查的關(guān)鍵字,即查找失敗般妙。

注意:

①用開(kāi)放定址法建立散列表時(shí)纪铺,建表前須將表中所有單元(更嚴(yán)格地說(shuō),是指單元中存儲(chǔ)的關(guān)鍵字)置空碟渺。

②空單元的表示與具體的應(yīng)用相關(guān)鲜锚。

按照形成探查序列的方法不同,可將開(kāi)放定址法區(qū)分為線性探查法苫拍、線性補(bǔ)償探測(cè)法芜繁、隨機(jī)探測(cè)等。

2.1绒极、線性探查法(Linear Probing)

該方法的基本思想是:

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

d垄提,d+l榔袋,d+2,…塔淤,m-1摘昌,0,1高蜂,…聪黎,d-

即:探查時(shí)從地址d開(kāi)始,首先探查T[d],然后依次探查T[d+1]稿饰,…锦秒,直到T[m-1],此后又循環(huán)到T[0]喉镰,T[1]旅择,…,直到探查到T[d-1]為止侣姆。

探查過(guò)程終止于三種情況:

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

(2)若當(dāng)前探查的單元中含有key柱蟀,則查找成功,但對(duì)于插入意味著失斞晾鳌长已;

(3)若探查到T[d-1]時(shí)仍未發(fā)現(xiàn)空單元也未找到key,則無(wú)論是查找還是插入均意味著失敗(此時(shí)表滿)昼牛。

利用開(kāi)放地址法的一般形式术瓮,線性探查法的探查序列為:

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

用線性探測(cè)法處理沖突,思路清晰贰健,算法簡(jiǎn)單胞四,但存在下列缺點(diǎn):

① 處理溢出需另編程序。一般可另外設(shè)立一個(gè)溢出表伶椿,專門用來(lái)存放上述哈希表中放不下的記錄撬讽。此溢出表最簡(jiǎn)

單的結(jié)構(gòu)是順序表,查找方法可用順序查找悬垃。

② 按上述算法建立起來(lái)的哈希表,刪除工作非常困難甘苍。假如要從哈希表 HT 中刪除一個(gè)記錄尝蠕,按理應(yīng)將這個(gè)記錄所

在位置置為空,但我們不能這樣做载庭,而只能標(biāo)上已被刪除的標(biāo)記看彼,否則,將會(huì)影響以后的查找囚聚。

③ 線性探測(cè)法很容易產(chǎn)生堆聚現(xiàn)象靖榕。所謂堆聚現(xiàn)象,就是存入哈希表的記錄在表中連成一片顽铸。按照線性探測(cè)法處

理沖突茁计,如果生成哈希地址的連續(xù)序列愈長(zhǎng) ( 即不同關(guān)鍵字值的哈希地址相鄰在一起愈長(zhǎng) ) ,則當(dāng)新的記錄加入該

表時(shí)谓松,與這個(gè)序列發(fā)生沖突的可能性愈大星压。因此践剂,哈希地址的較長(zhǎng)連續(xù)序列比較短連續(xù)序列生長(zhǎng)得快,這就意味

著娜膘,一旦出現(xiàn)堆聚 ( 伴隨著沖突 ) 逊脯,就將引起進(jìn)一步的堆聚。

2.2竣贪、線性補(bǔ)償探測(cè)法

線性補(bǔ)償探測(cè)法的基本思想是:

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

m 是互質(zhì)的演怎,以便能探測(cè)到哈希表中的所有單元匕争。

【例】 PDP-11 小型計(jì)算機(jī)中的匯編程序所用的符合表,就采用此方法來(lái)解決沖突颤枪,所用表長(zhǎng) m = 1321 汗捡,選用

Q = 25 。

2.3畏纲、隨機(jī)探測(cè)

隨機(jī)探測(cè)的基本思想是:

將線性探測(cè)的步長(zhǎng)從常數(shù)改為隨機(jī)數(shù)扇住,即令: j = (j + RN) % m ,其中 RN 是一個(gè)隨機(jī)數(shù)盗胀。在實(shí)際程序中應(yīng)預(yù)先

用隨機(jī)數(shù)發(fā)生器產(chǎn)生一個(gè)隨機(jī)序列艘蹋,將此序列作為依次探測(cè)的步長(zhǎng)。這樣就能使不同的關(guān)鍵字具有不同的探測(cè)次

序票灰,從而可以避 免或減少堆聚女阀。基于與線性探測(cè)法相同的理由屑迂,在線性補(bǔ)償探測(cè)法和隨機(jī)探測(cè)法中浸策,刪除一個(gè)記

錄后也要打上刪除標(biāo)記。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惹盼,一起剝皮案震驚了整個(gè)濱河市庸汗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌手报,老刑警劉巖蚯舱,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異掩蛤,居然都是意外死亡枉昏,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門揍鸟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兄裂,“玉大人,你說(shuō)我怎么就攤上這事∨尘剑” “怎么了前翎?”我有些...
    開(kāi)封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)畅涂。 經(jīng)常有香客問(wèn)我港华,道長(zhǎng),這世上最難降的妖魔是什么午衰? 我笑而不...
    開(kāi)封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任立宜,我火速辦了婚禮,結(jié)果婚禮上臊岸,老公的妹妹穿的比我還像新娘橙数。我一直安慰自己,他們只是感情好帅戒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布灯帮。 她就那樣靜靜地躺著,像睡著了一般逻住。 火紅的嫁衣襯著肌膚如雪钟哥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天瞎访,我揣著相機(jī)與錄音腻贰,去河邊找鬼。 笑死扒秸,一個(gè)胖子當(dāng)著我的面吹牛播演,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伴奥,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼写烤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拾徙?” 一聲冷哼從身側(cè)響起顶霞,我...
    開(kāi)封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锣吼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蓝厌,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡玄叠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拓提。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片读恃。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寺惫,到底是詐尸還是另有隱情疹吃,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布西雀,位于F島的核電站萨驶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏艇肴。R本人自食惡果不足惜腔呜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望再悼。 院中可真熱鬧核畴,春花似錦、人聲如沸冲九。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)莺奸。三九已至丑孩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間憾筏,已是汗流浹背嚎杨。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氧腰,地道東北人枫浙。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像古拴,于是被迫代替她去往敵國(guó)和親箩帚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一黄痪、散列的概念 散列方法的主要思想是根據(jù)結(jié)點(diǎn)的關(guān)鍵碼值來(lái)確定其存儲(chǔ)地址:以關(guān)鍵碼值K為自變量紧帕,通過(guò)一定的函數(shù)關(guān)系h...
    SeanMa閱讀 64,121評(píng)論 1 30
  • Map 是一種很常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)是嗜,用于存儲(chǔ)一些無(wú)序的鍵值對(duì)。在主流的編程語(yǔ)言中挺尾,默認(rèn)就自帶它的實(shí)現(xiàn)鹅搪。C、C++ 中的...
    一縷殤流化隱半邊冰霜閱讀 9,266評(píng)論 23 67
  • 第一章 Nginx簡(jiǎn)介 Nginx是什么 沒(méi)有聽(tīng)過(guò)Nginx遭铺?那么一定聽(tīng)過(guò)它的“同行”Apache吧丽柿!Ngi...
    JokerW閱讀 32,688評(píng)論 24 1,002
  • 散列表,它是基于快速存取的角度設(shè)計(jì)的恢准,也是一種典型的“空間換時(shí)間”的做法。顧名思義甫题,該數(shù)據(jù)結(jié)構(gòu)可以理解為一個(gè)線性表...
    yeying12321閱讀 3,691評(píng)論 0 6
  • 兩個(gè)舍友比腳臭坠非。 一個(gè)說(shuō):“我把鞋子脫掉敏沉,這里的人就全跑了!” 另一個(gè)冷笑:“我脫下鞋子麻顶,這屋里的...
    遇見(jiàn)自然閱讀 379評(píng)論 1 2