動態hash表一

轉載自:http://hi.baidu.com/calrincalrin/item/dd826f76cffc935d0d0a07c5

動態hash方法之一

動態hash方法之一

本文資料在百度文庫有文檔,如果覺得麻煩,可以下下來仔細閱讀,會花費你一個幣!算是對我工作的支持吧,呵呵!

鏈接:

本文將介紹三種動態hash方法。

      散列是一個非常有用的、非常基礎的數據結構,在數據的查找方面尤其重要,應用的非常廣泛。然而,任何事物都有兩面性,散列也存在缺點,即數據的局部集中性會使散列的性能急劇下降,且越集中,性能越低。

      數據集中,即搜索鍵在通過hash函數運算後,得到同一個結果,指向同一個桶,這時便產生了數據衝突。

      通常解決數據衝突的方法有:拉鍊法(open hashing)和開地址法(open addressing)。拉鍊法我們用的非常多,即存在衝突時,簡單的將元素鏈在當前桶的最後元素的尾部。開放地址法有線性探測再散列、二次線性探測再散列、再hash等方法。

      以上介紹的解決衝突的方法,存在一個前提:hash表(又稱散列表)的桶的數目保持不變,即hash表在初始化時指定一個數,以後在使用的過程中,只允許在其中添加、刪除、查找元素等操作,而不允許改變桶的數目。

      在實際的應用中,當hash表較小,元素個數不多時,採用以上方法完全可以應付。但是,一旦元素較多,或數據存在一定的偏斜性(數據集中分佈在某個桶上)時,以上方法不足以解決這一問題。我們引入一種稱之爲動態散列的方法:在hash表的元素增長的同時,動態的調整hash桶的數目。

      動態hash不需要對hash表中所有元素進行再次插入操作(重組),而是在原來基礎上,進行動態的桶擴展。有多種方法可以實現:多hash表、可擴展的動態散列和線性散列,下面分別介紹之,方法由簡單到複雜。

        多hash表:顧名思義,即採用多個hash表的方式擴展原hash表。這種方式不復雜,且理解起來也較簡單,是三者中最簡單的一種。

      通常,當一個hash表衝突較多時,需要考慮採用動態hash方式,來減小後續操作繼續在該桶上的衝突,減輕該桶負擔,最簡單且最容易想到的就是採用多hash表的方式。如下圖,有一個簡單的hash結構:


簡單起見,假定(1)hash函數採用模5,即hash(i)=i%5;(2)每個桶中最多隻可放4個元素。

      在以上基礎上,向hash表中插入5,由於桶a存在空閒,直接存入。接着向hash表插入值3,由於d桶中已滿,無空閒位置,此時在建立一個hash表,結果如下圖

 

通過圖示,一目瞭然,原來的一個hash表,變爲現在的兩個hash表。如需要,該“分裂”可繼續進行。

      需要注意的是:採用這種方式,多個hash表公用一個hash函數,且目錄項的個數也隨之增多,分別指向對應的桶。實際上,這時存在兩個不同的目錄項,分別指向各自的桶。

      執行插入、查找、刪除操作時,均需先求得hash值x。插入時,得到當前的hash表的個數,並分別取得各個目錄項的x位置上的目錄項,若其中某個項指向的桶存在空閒位置,則插入之。同時,在插入時,可保持多個hash表在某個目錄項上桶中元素的個數近似相等。若不存在空閒位置,則簡單的進行“分裂”,新建一個hash表,如上圖所示。

      查找時,由於某個記錄值可能存在當前hash結構的多個表中,因此需同時在多個目錄項的同一位置上進行查找操作,等待所有的查找結束後,方可判定該元素是否存在。由於該種結構需進行多次查找,當表元素非常多時,爲提高效率,在多處理器上可採用多線程,併發執行查找操作。

      刪除操作,與上述過程基本類似,不贅述。需要注意的是,若刪除操作導致某個hash表元素爲空,這時可將該表從結構中剔除。

      這種解決hash衝突的方法,優點是:思想簡單,實現起來也不復雜。由於一存在桶滿的情況就另分配一個hash表,因此佔用內存空間較大;當數據較集中時,桶利用率會很低。

 

 

 可擴展的動態散列:引入一個僅存儲桶指針的目錄數組,用翻倍的目錄項數來取代翻倍的桶的數目,且每次只分裂有溢出的桶,從而減小翻倍的代價。這裏需要幾個參數:H表示hash函數、D表示全局位深度、L表示桶的局部深度。還是來看個例子,參照這個圖,你會更明瞭。

 

圖中,每個目錄項有一個指向桶的指針,爲介紹方便,我們假定(1)每個桶只可存放4個元素;(2)每個桶中存放的元素j*表示H(i)=j,爲方便起見,只圖示了j的值,並以’ *’標註。當前有4個目錄項,目錄項的編號從00~11,用兩個位即可表示所有的目錄項,因此全局位深度D=2;所有的桶目前最多隻可放4個元素,因此所有桶的局部位深度爲L=2。

      在上圖的基礎上,我們插入數據d1和d2,且假定,經過hash函數求值後分別得到H(d1)=13;H(d2)=20。因爲13=1101,因全局位深度爲2,故選用最後兩位01,找到編號爲01的目錄項,從而找到其指向的桶b,由於該桶還有空間,可直接存入數據。因20=10100,全局位深度爲2,選用最後兩位00,選定第一個目錄項,這時我們發現其指向的桶a中已經放滿了數據,於是該桶進行分裂,分裂的桶的局部位深度從2變爲3,若這個數據比全局位深度還大,則全局位深度也等於該數,並進行目錄項的翻倍操作。分裂的桶中的所有數據,需進行局部的重組。下圖列出了分裂後的hash表的情況。

對a桶進行分裂後,得到兩個桶a1和a2,其局部深度加1。由於局部深度大於全局位深度,因此目錄數組進行翻倍,從4變爲8,且目錄編號擴展一位(如圖)。桶a分裂爲a1桶和a2桶,分別設置指針。對原來a桶中的所有元素進行重組操作,32和16的後三位均爲000,於是放入a1桶,4和12的後三位均爲100,於是放入a2桶。對目錄項數組中其它未賦值的目錄項,進行賦值,使指針指向對應的桶。至此,插入操作完畢。可以看到,有多個目錄指向同一個桶。

      對於查找操作,步驟如下:

1、對於需要查找的x,hash(x) = y

2、根據當前hash表的全局位深度,決定對y取其後D位,位數不夠用0填充

3、找到對應的目錄項,從而找到對應的桶,在桶中逐一進行比較。

 

      對於刪除操作,和查找操作類似,先定位元素,刪除之。若刪除時發現桶爲空,則可以考慮將該桶與其兄弟桶進行合併,並使局部位深度減1。

      可擴展散列的好處在於可動態進行桶的增長,且增長的同時,用目錄項的翻倍的較小的代價換取桶數翻倍的傳統做法,效率得到提升。然而,它也存在一定問題:(1)當散列的數據分佈不均或偏斜較大時,會使得目錄項的數目很大,數據桶的利用率很低;(2)目錄的增長速度,是指數級增長,擴展較快。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章