日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

《算法導(dǎo)論》讀書筆記7 (散列表)

 碧海山城 2010-05-05

《算法導(dǎo)論》第三部分 數(shù)據(jù)結(jié)構(gòu) 略過 隊列 鏈表,我們到了 散列表

散列表是最常用的數(shù)據(jù)結(jié)構(gòu)之一,特別是 ruby js等動態(tài)語言在語法層次上對它進(jìn)行了支持。只是在java中,有那么點(diǎn)點(diǎn)繞(每次使用的時候,心里會疙瘩一下,不知道你們有沒有這種感覺)。

本章真是糾結(jié),因?yàn)榭吹枚囊郧岸伎催^,不懂的現(xiàn)在還是看不懂。 還好看不懂的部分都是加星號的。

散列表是這樣一個東西:它能以key為索引保存東西, 然后在需要的時候嗖的一下就能找出來,灰常地快:)

 

 get方法要在常量時間內(nèi)找到需要的東西。O(1)  <--- 要這樣的復(fù)雜度

先不管這些,但至少HashTable看起來像這樣:

 

上面的代碼當(dāng)然是通不過測試的(PS: 請先忘記java.util.HashTable)

要想get足夠快,那么最好是跟據(jù) key 直接計算出存儲位置, 然后就能一下子找到啦。

差不多像這樣:

 

運(yùn)行測試,數(shù)組越界, 因?yàn)槲覀冞€未實(shí)現(xiàn) hash 這個方法。

hash 的作用是把關(guān)鍵字等可能地散列到 table 中去

除法散列,乘法散列,等等。

先試一個除法散列

 

capacity 不應(yīng)該是 2 的冪, 否則的話值為hashCode的低 k 位, 高位就會浪費(fèi)掉,可能會造成很多碰撞

可以選擇2的整數(shù)冪不大接近的質(zhì)數(shù)。

現(xiàn)在運(yùn)行測試,是通過滴:)

但是等等, 有時候我們需要這樣:

Java代碼 復(fù)制代碼

  

我們需要重構(gòu)代碼,把key也給保存起來。

首先添加一個結(jié)構(gòu), 保存key 和value

重構(gòu)put

Java代碼 復(fù)制代碼
  1. public void put(String key, Object value) {   
  2.     int hash = hash(key.hashCode());   
  3.     if (table[hash] == null || table[hash].getKey().equals(key)) {   
  4.         table[hash] = new Entry(key, value);   
  5.     } else{   
  6.         throw new RuntimeException("撞車?yán)?,怎么辦?");   
  7.     }   
  8. }  

 

重構(gòu)get

 

 可以看到,測試又通過了:)

再看乘法散列

 

用常數(shù)(A) 乘hashCode  取小數(shù) 再乘capacity

Knuth認(rèn)為 黃金分割數(shù) 是比較理想的值((根號5 - 1) / 2 ~ 0.618), 股民朋友們一定認(rèn)識

乘法散列 的優(yōu)點(diǎn)是:

對 capacity 沒有什么特別的要求, 一般選擇它為 2 的整數(shù)冪。

因?yàn)檫@樣可以使用移位代替乘法計算。

然后黃金分割數(shù) A 如果可以表示成 2654435769  / (2 ^32)

那就可以簡化成:

              ((hashCode * 2654435769) 
                  & ((1 << 32) - 1) )   // 只保留 32 位
               >> (32 - p)

重購代碼試試看:

首先,數(shù)組空間大小為 2 ^ p

然后:

測試還是通過滴。

下面, 讓我們加多一點(diǎn)元素,搞壞它。

運(yùn)行測試,失敗, 可以看到控制臺只輸出到 108

RuntimeException,   撞車了怎么辦?

可以采用鏈接法,開放尋址法搞定

先來 鏈接法

首先重構(gòu)Entry, 把自己串起來

 

同時也添加了一個 setValue 方法, 這樣更容易在鏈表中“更新元素”

然后重構(gòu)put

 

可以看到,測試正常運(yùn)行:)

但是隨著散列表中的元素越來越多,碰撞機(jī)率也越來越大,最好當(dāng)元素數(shù)量達(dá)到一定量時,自動擴(kuò)充容量,這樣才能保證其優(yōu)異的查找性能。

但是我們先看看,現(xiàn)在的散列表, 運(yùn)行test3時,碰撞幾率是多少。

為此,我們重構(gòu), 發(fā)生碰撞時, 統(tǒng)計次數(shù)。

 

測試:

 

輸出:0.309

總的容量為 1024, 有1000個元素, 其中309個是發(fā)生碰撞。事故挺嚴(yán)重的。

下面我們重構(gòu)HashTable類, 讓其每次達(dá)到容量的 0.75(裝載因子) 就擴(kuò)充容量:)

 

首先, 我們的初始化容量為 16個(1 << 4), 然后 load factor 為0.75

 

然后在put 前檢查一下, 如有必要 resize

寫個測試:

這個時候,同樣是添加到1000個, loadFactor 此時為 0.08

我們的散列表初始大小為16,  添加到1000個,要進(jìn)行若干次 resize, resize開銷比較大。

我們可以重構(gòu)代碼, 構(gòu)造函數(shù)中指定容量大小,以避免不必要的resize開銷。

但這里不做了,因?yàn)楝F(xiàn)在只是為了說明算法, 但是使用 java.util.HashMap時,就曉得了。

解決碰撞還有開放尋址法

也是灰常容易滴, 我們添加兩個方法, put2, 和 get2, 實(shí)現(xiàn)看看。

使用最簡單的 線性探查

 

同樣,寫一個測試:

線性探查比較容易實(shí)現(xiàn), 但是容易造成“堆在一起”的問題, 書中稱為:一次群集

可以采用二次探查, 或雙重散列,更好地避免這種現(xiàn)象

//----------

下面看看java.util.HashMap的實(shí)現(xiàn),更好地了解散列表。

先看 put:

代碼中 hash 和 indexFor addEntry 是我們關(guān)心的地方。

此外: HashMap 允許使用值為 null 的key

有一個 if 語句:

先看看 hash值是否相等, 再判斷equals

這也給出了我們重寫equals和 hash的原則: 如果你重寫了equals, 那么你一定要重寫 hashCode, 如果兩個對象equals,那么hashCode也一定要相等, 否則在HashMap等容器中將不能正確工作。參看《Effective Java》

再來看看 hash 和 indexFor  (中文注釋是我加的)

 

hash 根據(jù) 原h(huán)ashCode產(chǎn)生更好的散列值, 因?yàn)閠able的容量大小剛好為2的整數(shù)冪, 所以必須這樣做,否則hash code的高位將浪費(fèi)(取模時) --- 見上面 除法散列

indexFor: 等于 h % length,

所以,HashMap 采用 改進(jìn)的除法散列

再看看 addEntry

table 也成倍擴(kuò)展的

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多