給定若干非負整數,範圍是 1~10000,編寫程式使得查詢乙個數的時間複雜度為 o(1)。
int data[
10001];
void
insert
(int key)
intsearch
(int key)
上述**的基本思想是將每個輸入的key
,存放在陣列的key
這個位置。
我們假設陣列下標為a
,對應位置的值為key
,則上述**的思想轉換成計算式是:a == key
;
雜湊表設計初衷也是為了實現訪問資料操作複雜度為 o(1),因此,很容易知道,雜湊表必定是基於陣列的,因為只有陣列具有隨機訪問
的特性。
因此,我們不能簡單的像開頭**那樣儲存,因為雜湊表儲存的 key 可能是任意資料,而陣列的下標必定是非負整數。這個時候,需要找到乙個方法,將任意資料都能轉換成乙個非負整數。
這個方法就是雜湊演算法(hash演算法)
,雜湊演算法有多種多樣的實現方式,這裡我們不深入**,只要知道雜湊演算法的作用是將任意資料轉換成一組固定長度的二進位制串,例如,md5演算法
可以將任意資料轉換成乙個128位
的資料。
我們用hash(key)
,來表示任意資料值key
的雜湊值,現在我們得到了任意資料的乙個特殊值,即雜湊值,如何將我們的雜湊表(底層就是乙個陣列)中的每個位置與雜湊值一一對應起來呢?最簡單的做法是雜湊值對長度取餘。即雜湊函式為hash(key) % length
例如,現在我們設計了乙個雜湊表,底層陣列是data[length]
,雜湊函式是hash(key) % length
,則資料x
儲存的位置是data[hash(x) % length]
。
前面我們講到雜湊演算法可以計算出任意資料對應的乙個雜湊值,由於雜湊值是固定長度的,而資料是無限多的,因此,肯定存在不同的資料具有相同的雜湊值。
鴿巢原理,有 10 只鴿子,9 個巢的情況下, 所有鴿子進入巢穴,則一定存在兩隻鴿子在同乙個巢中我們的雜湊表是具有一定長度的,當資料多到一定程度時,可能出現計算出雜湊值後,該值所對應的雜湊表中的位置已經有資料了,這個時候將如何應對呢?
開放定址法
如圖,當插入的資料計算得到的位置已經有資料時,即視為雜湊衝突,開放定址法的基本思想是,當遇到雜湊衝突時,則向後位移(到末尾後從 0 位置開始繼續尋找)直到找到空位置則存放資料。
當雜湊衝突很嚴重時,雜湊表的查詢效率也會嚴重降低。
應對措施:
擴大雜湊表容量(擴容後已經在表中的資料的雜湊位置也相對改變)
改變雜湊策略,如二次探測法
(往後尋找時每次移動兩個位置),多重雜湊
(第乙個雜湊函式的值衝突,用第二個雜湊函式計算)
改變雜湊函式(簡單的對長度取餘容易引起雜湊衝突)
鍊錶法遇到衝突時,直接將資料放在該位置的鍊錶中;
鍊錶法也存在雜湊衝突過多後,查詢效率下降的問題。
解決措施:
每條鍊錶超過一定數量後,轉換成紅黑樹。
擴容(同樣會導致原來的資料雜湊位置改變的問題)
改變雜湊函式
前面我們說到降低雜湊衝突的解決方法之一是擴大雜湊表容量,這裡引入乙個概念:裝載因子
裝載因子 = 填入表中的元素個數 / 雜湊表的長度
裝載因子越大,說明雜湊衝突越嚴重。
因此當裝載因子超過一定程度後,我們需要對雜湊表進行擴容,以便減小雜湊衝突,提高查詢效率。
問題:當雜湊表擴容後,原先的資料在擴容後的雜湊表中的雜湊位置已經改變,當資料量很大時如何妥善處理?
資料量不大的情況下,很容易就能想到的解決方法,類似陣列擴容時,只需將原來的資料搬運到新陣列中,這裡,新的雜湊表產生後,將原來的資料重新計算雜湊位置搬運到新雜湊表中。
但是在資料量大的情況下,插入資料觸發擴容時,該次插入操作響應時間會很長。
因此我們考慮擴容時同時存在新舊雜湊表,插入資料時存入新雜湊表,查詢時,先查詢舊雜湊表,再查詢新雜湊表,每一次的操作都從舊雜湊表搬運乙個資料到新雜湊表。像這樣將搬運資料的操作分散在每一步操作中,對效能影響不大。
同樣的,如果是對記憶體使用要求高的,在資料減少到一定程度時,進行縮容,具體操作類似擴容
hashmap 底層採用了雜湊表加紅黑樹的結構。
雜湊衝突採用鍊錶法解決。
雜湊衝突很嚴重時,每條鍊錶以紅黑樹的形式儲存,查詢效率穩定在 o(log n) 以內。
hashmap 的雜湊函式見我的另一篇部落格:
linkedhashmap,從名字上看加上了 linked 並不是指這個雜湊表是用鍊錶法解決衝突的,因為 hashmap 本身就是採用鍊錶法解決衝突,所以,這裡的 linked 是什麼意思呢?
從雜湊表的儲存方式看,雜湊表中的資料並不是按照插入順序來的,而是雜亂無章的,而 linkedhashmap 卻能夠按照資料的訪問順序來輸出。思考一下,它是怎麼做到的呢?
答案是,在雜湊表的基礎上結合雙向鍊錶實現:
雙向鍊錶保留了資料的插入順序
hashmap
m =newlinkedhashmap
<
>()
;m.put(3,
11);m.
put(1,
12);m.
put(5,
23);m.
put(2,
22);for
(map.entry e : m.
entryset()
)
上述**中,輸出順序是3152
,由雙向鍊錶維護資料插入時的順序。
linkedhashmap 也支援按照訪問順序輸出:1235
// 10 是初始大小,0.75 是裝載因子,true 是表示按照訪問時間排序
hashmap
m =newlinkedhashmap
<
>(10
,0.75f
,true);
m.put(3
,11);
m.put(1
,12);
m.put(5
,23);
m.put(2
,22);
m.put(3
,26);
m.get(5
);for(map.entry e : m.
entryset()
)
雜湊函式及其應用
雜湊,英文hash,也有直接音譯為 雜湊 的,就是把任意長度的輸入 又叫做預對映,pre image 通過雜湊演算法,變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來確定唯一的輸入值。簡單...
雜湊表(雜湊表)及其實現
雜湊表 hash table,也叫雜湊表 是根據關鍵碼值 key value 而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中乙個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做雜湊表。例如 給定表m,存在函式f key 對任意給定的關鍵字值key,代入函...
雜湊表(雜湊表)原理詳解
雜湊表 hash table,也叫雜湊表 是 根據關鍵碼值 key value 而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中乙個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做 雜湊函式 存放記錄的陣列叫做 雜湊表。或者 把任意長度的輸入 又叫做預對映,pre image 通過雜...