算法之散列表

散列表——最有用的基本數(shù)據(jù)結(jié)構(gòu)之一,用途廣泛。

散列表的內(nèi)部機(jī)制:實(shí)現(xiàn)、沖突和散列函數(shù)。

假設(shè)你在一家雜貨店上班。有顧客來買東西時(shí),你得在一個(gè)本子中查找價(jià)格。如果本子的內(nèi)容不是按字母順序排列的,你可能為查找蘋果 (apple)的價(jià)格而瀏覽每一行,這需要很長(zhǎng)的時(shí)間。

散列函數(shù)

無論你給它什么數(shù)據(jù),它都還你一個(gè)數(shù)字。散列函數(shù)將輸入映射到數(shù)字

實(shí)散列函數(shù)必須滿足一些要求。它必須是一致的。
最理想的情況是,將不同的輸入映射到不同的數(shù)字。

散列表 (hash table)的數(shù)據(jù)結(jié)構(gòu)。種包含額外邏輯的數(shù)據(jù)結(jié)構(gòu)。

數(shù)組和鏈表都被直接映射到內(nèi)存,但散列表更復(fù)雜,它使用散列函數(shù)來確定元素的存儲(chǔ)位置。

散列表由組成。

將散列表用于查找

假設(shè)你要?jiǎng)?chuàng)建一個(gè)類似這樣的電話簿,將姓名映射到電話號(hào)碼。該電話簿需要提供如下功能。添加聯(lián)系人及其電話號(hào)碼。通過輸入聯(lián)系人來獲悉其電話號(hào)碼。

phone_book = dict()
phone_book["jenny"] = 8675309
phone_book["emergency"] = 911
print phone_book["jenny"]
#8675309  

如果要求你使用數(shù)組來創(chuàng)建電話簿,你將如何做呢?

散列表被用于大海撈針式的查找。訪問網(wǎng)站時(shí),計(jì)算機(jī)必須將adit.io轉(zhuǎn)換為IP地址。無論你訪問哪個(gè)網(wǎng)站,其網(wǎng)址都必須轉(zhuǎn)換為IP地址。這不是將網(wǎng)址映射到IP地址嗎?好像非常適合使用散列表啰!這個(gè)過程被稱為DNS解析 (DNS resolution),散列表是提供這種功能的方式之一。

防止重復(fù)

假設(shè)你負(fù)責(zé)管理一個(gè)投票站。顯然,每人只能投一票,但如何避免重復(fù)投票呢?有人來投票時(shí),你詢問他的全名,并將其與已投票者名單進(jìn)行比對(duì)。如果名字在名單中,就說明這個(gè)人投過票了,因此將他拒之門外!

voted = {}
def check_voter(name):  
  if voted.get(name):    
    print "kick them out!"  
  else:    
    voted[name] = True    
    print "let them vote!"

將散列表用作緩存

假設(shè)你訪問網(wǎng)站facebook.com。
(1) 你向Facebook的服務(wù)器發(fā)出請(qǐng)求。
(2) 服務(wù)器做些處理,生成一個(gè)網(wǎng)頁并將其發(fā)送給你。
(3) 你獲得一個(gè)網(wǎng)頁。

緩存的工作原理:網(wǎng)站將數(shù)據(jù)記住,而不再重新計(jì)算。
緩存是一種常用的加速方式,所有大型網(wǎng)站都使用緩存,而緩存的數(shù)據(jù)則存儲(chǔ)在散列表中!

當(dāng)你訪問Facebook的頁面時(shí),它首先檢查散列表中是否存儲(chǔ)了該頁面。

cache = {}
def get_page(url):  
  if cache.get(url):    
    return cache[url]  # 返回緩存的數(shù)據(jù)  
  else:    
    data = get_data_from_server(url)    
    cache[url] = data  # 先將數(shù)據(jù)保存到緩存中    
    return data

沖突

給兩個(gè)鍵分配的位置相同,這種情況被稱為沖突(collision):

如果兩個(gè)鍵映射到了同一個(gè)位置,就在這個(gè)位置存儲(chǔ)一個(gè)鏈表。

散列函數(shù)將所有的鍵都映射到一個(gè)位置,而最理想的情況是,散列函數(shù)將鍵均勻地映射到散列表的不同位置。

著無論散列表包含一個(gè)元素還是10億個(gè)元素,從其中獲取數(shù)據(jù)所需的時(shí)間都相同。

而要避免沖突,需要有:

  • 較低的填裝因子;
  • 良好的散列函數(shù)。

填裝因子

散列表的填裝因子很容易計(jì)算。散列表使用數(shù)組來存儲(chǔ)數(shù)據(jù),因此你需要計(jì)算數(shù)組中被占用的位置數(shù)。
填裝因子度量的是散列表中有多少位置是空的。

填裝因子大于1意味著商品數(shù)量超過了數(shù)組的位置數(shù)。一旦填裝因子開始增大,你就需要在散列表中添加位置,這被稱為調(diào)整長(zhǎng)度

填裝因子越低,發(fā)生沖突的可能性越小,散列表的性能越高。

一個(gè)不錯(cuò)的經(jīng)驗(yàn)規(guī)則是:一旦填裝因子大于0.7,就調(diào)整散列表的長(zhǎng)度。


良好的散列函數(shù)

良好的散列函數(shù)讓數(shù)組中的值呈均勻分布。糟糕的散列函數(shù)讓值扎堆,導(dǎo)致大量的沖突。

什么樣的散列函數(shù)是良好的呢?你根本不用操心——天塌下來有高個(gè)子頂著。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容