1. 程式人生 > >資料結構:雜湊(hashing)

資料結構:雜湊(hashing)

雜湊方法

把關鍵碼值對映到陣列中的位置來訪問記錄這個過程稱為雜湊(hashing)
把關鍵碼值對映到位置的函式稱為雜湊函式(hashing function),通常用 h 表示。
存放記錄的陣列稱為散列表(hash table),用 HT 表示。
散列表中的一個位置稱為一個槽(slot)。
散列表 HT 中的數目用變數 M 表示,槽從 0 到 M-1標號。
雜湊方法通常不適用於允許多條記錄有相同關鍵碼值的應用程式。雜湊方法一般不適用於範圍檢索。
對於一個雜湊函式 h 和兩個關鍵碼值k1 、 k2,如果 h(k1) =B= h(k2) ,其中 B 為表中的一個槽,那麼就說 k1 和 k2

對於 B 在雜湊函式 h 下有衝突。
對於衝突,有開雜湊和閉雜湊的解決方式。

雜湊函式

從技術上來說,任何能把所有可能關鍵碼值對映到散列表槽中的函式都是雜湊函式。
下面介紹一個常用的雜湊函式

平方取中方法

平方取中方法是一個用於數值的雜湊方法。對關鍵碼計算平方,取中間的幾位,雜湊到相應的位置。
這樣計算的原因是因為關鍵碼的大多數位或所有位對結果都有貢獻。
比如,基數為 10 的四位關鍵碼,雜湊到一個長度為 100 的散列表中。假如關鍵碼為 4567 ,則

4567 * 4567 = 20857489

取的中間兩位是 57 。(位數等於 ln100 )

摺疊方法

這是一個用於字串的雜湊方法,計算的方法是將字串的 ASCII 值累加起來,對 M 求模。

int h(char* str)
{
    int sum, i;
    for(sum = i = 0; str[i] != '\0'; ++i)
        sum += static_cast<int>(str[i]);
    return sum % M;
}

這個方法有一個不好的地方就是,假如 sum 的值比 M 小,則會產生比較差的分佈。

將字串解釋為無符號整型和的雜湊方法

由於找不到這個方法的名字,就這麼叫吧。
這種雜湊方法的計算方式是,將字串解釋為一個個大小為 4 的無符號整型,求和,然後求模。

int sfold(char* str)
{
    unsigned
int sum = 0; unsigned int* intKey = static_cast<decltype(intKey)>(str); int len = strlen(str)/4; for(int i =0;i<len;++i) sum += intKey[i]; int extra = strlen(str)-len*4; char temp[4]; intKey = static_cast<decltype(intKey)>(temp);//or reinterpret_cast? intKey[0] = 0; for(int i =0;i<extra;++i) temp[i] = str[len*4+i]; sum += intKey[0]; return sum%M; }

使用無符號整數的目的是為了避免求模時結果會是負數。
在就算的過程中,如果數字過大,則會發生溢位。但是作為雜湊函式,並沒有關係。(意思就是,讓它溢位吧)

開雜湊方法

儘管雜湊函式的目的是儘量減少衝突,但是有些衝突是不可避免的。
解決衝突的技術可以分為兩類:開雜湊方法(open hashing,也稱單鏈方法,separate chaining)和閉雜湊方法(closed hashing,也稱開地址方法,open addressing)
開雜湊方法解決衝突是將衝突記錄在表外,而閉雜湊方法是將衝突記錄在表內的另一個空槽。
雖然很簡潔的圖,但是應該能夠表達意思吧

看起來就好像很好實現的樣子,於是我就是實現了一下。
在我的實現中 M 為11,雜湊函式如下:

    int h(const Key& k) const
    {
        return k*7%M;
    }

實話說,這並不是一個好的雜湊函式。

閉雜湊方法

閉雜湊方法將所有記錄都直接儲存在散列表中。
閉雜湊方法有集中雜湊方式:桶式雜湊、線性探查、二次探查、雙雜湊方法。

桶式雜湊

桶式雜湊是把雜湊的槽分成多個桶(bucket)。
把散列表中的 M 個槽分成 B 個桶,每個桶中包含 M/B 個槽。
雜湊函式把每一條記錄分配到某個桶的第一個槽中。如果這個槽已經被佔用,那麼就順序地沿著桶查詢,直到找到一個空槽。
如果沒有空槽了,那麼就將該條記錄分配到一個具有無限容量的溢位桶中。
這裡寫圖片描述
插入順序是

9 30 27 4 8

有6個槽,3個桶的散列表,雜湊函式是

int h(int i)
{
    return i%B;
}

我想這樣應該表達明確了吧。
桶式雜湊的一個簡單變體是,先把關鍵碼雜湊到槽中。當該槽滿時,再把關鍵碼雜湊到同一個桶的其他槽中。如果還沒有空槽,就雜湊到溢位桶中。
這裡寫圖片描述
這時雜湊函式是:

int h(int i)
{
    return i%M;
}

線性探查

線性探查是比較常用的雜湊函式,它不使用桶式雜湊的方法,而是允許記錄儲存在散列表中的任何一個空槽中。
衝突解決策略是產生一組有可能放置該記錄的槽,第一個槽就是該關鍵碼的基槽。如果基槽被佔用了,那麼就會尋找下一個槽,直到記錄被存放。
這組槽就稱為衝突解決策略產生的探查序列(probe sequence)。
探查序列是由探查函式(probe function)的 p 函式生成的。
注意,探查函式返回的是相對於初始位置的偏移量,而不是散列表的一個槽。
線性探查的函式類似如下:

int p(int k, int i)
{
    return a*i+b;
}

其中 i 是第幾次的探查引數, k 是關鍵碼,a 和 b 是常數。
使用的時候如下:

return (h(k)+p(k, i))%M;

為了是探查列走遍所有的槽,a 必須與 M互素。

線性探查會導致:基本聚集(primary clustering)
考慮順序插入:

9 30 27 4 8

加入使用最基本的探查函式 return i; M為6
那麼 9 的探查序列:

3 4 5 0 1 2

而 27 的探查序列也是如此。
到插入的記錄多了,就是大部分地聚集到一起。
把記錄聚集到一起的傾向就是基本聚集了。
好的探查函式應該是使得它們的探查序列岔開。
而解決基本聚集問題就是使用二次探查或偽隨機探查。

二次探查

二次探查的函式如下:

int p(int k, int i)
{
    return a*i*i+b*i+c;
}

其中 a, b, c 為常數。
二次探查的缺陷在於,在某些特定的情況下,只有特定的槽被探查到。
考慮 M 為 3,p(k, i) = i*i;
那麼雜湊到槽 0 的只會探查到 0, 1 ,而不會探查到 2。
然而,這樣的情況是可以在低開銷的基礎上探查得到較好的結果。

當散列表長度為素數,以及探查函式為 p(k, i) = i*i 時,至少能夠訪問到表中一半的槽。
如果散列表長為 2 的指數,並且探查函式為 p(k, i) = (i*i+i)/2 , 那麼表中所有槽都能被探查序列訪問到。

偽隨機探查

在偽隨機探查中,探查序列中的第 i 個槽是 (h(k) + ri) mod M ,ri是 1 到 M-1 之間的數的隨機序列。
所有的插入和檢索都使用相同的偽隨機序列。

儘管二次探查和偽隨機探查能夠解決基本聚集問題,然而如果雜湊函式在某個基槽聚集,依然會保持聚集。這個問題稱為二次聚集(secondary clustering)
解決二次聚集問題可以使用雙雜湊方法

雙雜湊方法

雙雜湊方法的形式:

int p(int k, int i)
{
    return i*h2(k);
}

h2 是第二個雜湊函式
好的雙雜湊實現方法應當保證所有探查序列常數都與表 M 長度互素。
其中一種方法是設定 M 為素數,而h2 返回 1<=h2<=M-1 之間的值。
另外一種方法是給定一個 m 值,設定 M = 2m ,然後讓 h2 返回 1 到 2m 之間的一個奇數值。

閉雜湊方法結論

每個新插入操作產生的額外查詢代價將在散列表接近半滿時急劇增加。
如果還考慮到訪問模式,在理想情況下,記錄應當沿著探查序列按照訪問頻率排序。

刪除

刪除記錄的時候,要考慮到兩點:

  1. 刪除不應當影響後面的檢索。
  2. 刪除的槽應當可以為後來的插入操作所使用。

通過在刪除的槽中放置一個特殊的標記就可以解決這個問題。這個標記稱為墓碑(tombstone)。

如果不想使用墓碑的標記,還可以在刪除的時候進行一次區域性的重組,或者定期重組散列表。

本文實現了開雜湊方法和閉雜湊方法中的一種。
程式碼在本人的 github 上可以找到。
我的github
–END–

相關推薦

資料結構hashing

雜湊方法 把關鍵碼值對映到陣列中的位置來訪問記錄這個過程稱為雜湊(hashing) 把關鍵碼值對映到位置的函式稱為雜湊函式(hashing function),通常用 h 表示。 存放記錄的陣列稱為散列表(hash table),用 HT 表示。

資料結構Hash Table

雜湊表定義 雜湊表是一種根據關鍵碼去尋找值的資料對映結構,該結構通過把關鍵碼對映的位置去尋找存放值的地方。 本質是一個數組,陣列中每一個元素稱為一個箱子(bin),箱子中存放的是鍵值對。 雜湊表的儲存過程如下: 根據 key 計算出它的雜湊值 h。 假設箱子的個數

資料結構散列表

轉自:http://blog.chinaunix.net/uid-26548237-id-3480645.html 一、散列表相關概念     雜湊技術是在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個儲存位置f(key)。

Redis儲存結構Hash

實用場景:分散式鎖 Redis雜湊/雜湊(Hashes)是鍵值對的集合。Redis雜湊/雜湊是字串欄位和字串值之間的對映。 因此,它們用於表示物件。 HDEL HEXISTS HGET HGETALL HINCRBY HINCRBYFLOAT HKEY

資料結構表以及衝突的解決方案

前言 基於先前的學習計劃,最近打算深入學習Java的集合類,首先要研究的就是HashMap,在學習HashMap前,我花了幾天時間溫習了一下類中用到的資料結構 (雜湊表,二叉樹),並決定把所學的知識記錄寫成文章,本文講述的就是關於雜湊表的知識。 什麼是雜湊表 在之前的部落格文章裡,我們簡單介紹了資料結構的幾種

基本資料結構連結串列list

那個單向連結串列程式樓主寫的很不錯,學習了,但是實際應用執行後,還是發現幾個問題1,第一個是最嚴重的問題,Delete函式中的temp變數並不是用new來分配的,但是後面卻用delete來撤銷,這樣在執行時是報錯的。在insert和insertHead函式中用new來分配的node變數,最後卻沒有用delet

資料結構單鏈表輸出連結串列值最大的節點

/********************************************************* **************新增加功能:輸出連結串列中值的最大節點*********

資料結構迴圈佇列設定一個標誌域後的入佇列和出佇列的演算法

如果希望迴圈佇列中的元素都能得到利用,則需設定一個標誌域tag,並以tag的值為0或1來區分,尾指標和頭指標值相同時的佇列狀態是"空"還是"滿"。試編寫與此結構相應的入佇列和出佇列的演算法。 本題的迴圈佇列CTagQu

Linux 核心資料結構點陣圖Bitmap

https://github.com/0xAX/linux-insides/blob/master/DataStructures/bitmap.md Data Structures in the Linux Kernel Bit arrays and bit op

淺談演算法和資料結構

在前面的系列文章中,依次介紹了基於無序列表的順序查詢,基於有序陣列的二分查詢,平衡查詢樹,以及紅黑樹,下圖是它們在平均以及最差情況下的時間複雜度: 可以看到在時間複雜度上,紅黑樹在平均情況下插入,查詢以及刪除上都達到了lgN的時間複雜度。 那麼有沒

資料結構基礎之查詢

轉自:http://www.cnblogs.com/edisonchou/p/4706253.html   查詢(下):雜湊表 雜湊(雜湊)技術既是一種儲存方法,也是一種查詢方法。然而它與線性表、樹、圖等結構不同的是,前面幾種結構,資料元素之間都存在某種邏輯關係,可以用連線圖示

[C++]資料結構散列表函式構造、處理衝突

        關鍵字{12,25, 38, 15, 16, 29, 78, 67, 56, 21, 22, 47 } , 對應後位置是 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}。 不過這種方法很容易產生衝突(如果關鍵字餘數大部分相同)。一般地,散列表長為m, 通常p

Hash資料結構,使用C語言實現s。傻瓜也能

雜湊資料結構是一種非常簡單,實用的資料結構。原理是將資料通過一定的hash函式規則,然後儲存起來。使查詢的時間複雜度近似於O(1)。進而大大節省了程式的執行時間。 雜湊表的原理如圖 原來的資料可以直接通過雜湊函式儲存起來,這樣在搜尋的時候,等於每一個數據都有了自己的特定查詢號碼,

資料結構HASH

前言    當我們在程式設計過程中,往往需要對線性表進行查詢操作。在順序表中查詢時,需要從表頭開始,依次遍歷比較a[i]與key的值是否相等,直到相等才返回索引i;在有序表中查詢時,我們經常使用的是二分查詢,通過比較key與a[i]的大小來折半查詢,直到相等時

linux核心分析--核心中使用的資料結構表hlist

前言: 1.基本概念: 散列表(Hash table,也叫雜湊表),是根據關鍵碼值(Key value)而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中一個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做散列表。 2. 常用的構造雜湊函式的方法

2.8 ruby的資料結構--Hash

1、雜湊(Hash) 雜湊也是儲存物件的一個集合,雜湊裡面的元素是以"key" => “value”(鍵值對)這樣的形式存在的,元素是沒有順序的,雜湊的鍵可以是任意物件,鍵必須的唯一的,鍵通常用符號(Symbol)表示。 雜湊的建立有兩種形式,兩種形式都是一樣的,最常使用第

c語言資料結構實現-表/hashtable/hashbucket

一、需求 以“key-value”的形式進行插入、查詢、刪除,是否可以考慮犧牲空間換時間的做法? 二、相關知識 雜湊表(Hashtable)又稱為“雜湊表”,Hashtable是會根據索引鍵的雜湊程式程式碼組織成的索引鍵(Key)和值(Value)配對的集合。Hashtab

資料結構基礎之圖最短路徑

轉自:http://www.cnblogs.com/edisonchou/p/4691020.html   圖(下):最短路徑 圖的最重要的應用之一就是在交通運輸和通訊網路中尋找最短路徑。例如在交通網路中經常會遇到這樣的問題:兩地之間是否有公路可通;在有多條公路可通的情況下,哪

資料結構基礎之圖最小生成樹演算法

轉自:http://www.cnblogs.com/edisonchou/p/4681602.html   圖(中):最小生成樹演算法 圖的“多對多”特性使得圖在結構設計和演算法實現上較為困難,這時就需要根據具體應用將圖轉換為不同的樹來簡化問題的求解。 一、生成樹與最小生成

資料結構基礎之圖圖的遍歷演算法

轉自:http://www.cnblogs.com/edisonchou/p/4676876.html   圖(中):圖的遍歷演算法 上一篇我們瞭解了圖的基本概念、術語以及儲存結構,還對鄰接表結構進行了模擬實現。本篇我們來了解一下圖的遍歷,和樹的遍歷類似,從圖的某一頂點出發訪問