乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

       碧海山城 2010-05-05

      《算法導論》第三部分 數(shù)據(jù)結構 略過 隊列 鏈表,我們到了 散列表

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

      本章真是糾結,因為看得懂的以前都看過,不懂的現(xiàn)在還是看不懂。 還好看不懂的部分都是加星號的。

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

       

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

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

       

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

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

      差不多像這樣:

       

      運行測試,數(shù)組越界, 因為我們還未實現(xiàn) hash 這個方法。

      hash 的作用是把關鍵字等可能地散列到 table 中去

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

      先試一個除法散列

       

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

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

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

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

      Java代碼 復制代碼

        

      我們需要重構代碼,把key也給保存起來。

      首先添加一個結構, 保存key 和value

      重構put

      Java代碼 復制代碼
      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("撞車啦,怎么辦?");   
      7.     }   
      8. }  

       

      重構get

       

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

      再看乘法散列

       

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

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

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

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

      因為這樣可以使用移位代替乘法計算。

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

      那就可以簡化成:

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

      重購代碼試試看:

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

      然后:

      測試還是通過滴。

      下面, 讓我們加多一點元素,搞壞它。

      運行測試,失敗, 可以看到控制臺只輸出到 108

      RuntimeException,   撞車了怎么辦?

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

      先來 鏈接法

      首先重構Entry, 把自己串起來

       

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

      然后重構put

       

      可以看到,測試正常運行:)

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

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

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

       

      測試:

       

      輸出:0.309

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

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

       

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

       

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

      寫個測試:

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

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

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

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

      解決碰撞還有開放尋址法

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

      使用最簡單的 線性探查

       

      同樣,寫一個測試:

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

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

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

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

      先看 put:

      代碼中 hash 和 indexFor addEntry 是我們關心的地方。

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

      有一個 if 語句:

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

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

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

       

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

      indexFor: 等于 h % length,

      所以,HashMap 采用 改進的除法散列

      再看看 addEntry

      table 也成倍擴展的

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多