二、線性探測法散列表:用大小爲M的數組保存N個鍵值對。
1、特點:
1)、M>N。
2)、需要依靠數組中的空位解決碰撞衝突。
基於這種策略的所有方法統稱爲開放地址散列表。
開放地址散列表中最簡單的方法叫做線性探測法:碰撞發生時,直接檢查散列表中的下一個位置。
2、線性探測會產生三種結果:
1)、命中:該位置的鍵和被查找的鍵相同;
2)、未命中:該位置鍵爲空(該位置沒有使用,故被查找的鍵在散列中不存在)
3)、繼續查找:該位置鍵不爲空,且與被查找的鍵不同。
3、查找步驟:
1)、用散列函數找到該鍵在數組中的索引;
2)、檢查其中的鍵和被查找的鍵是否相同;
3)、鍵不同,則索引遞增(到大數組結尾時折回數組開頭),繼續查找。
4)、終止查找條件:找到該鍵或遇到一個空元素。
4、開放地址散列表的核心思想:
與其將內存用作鏈表,不如將內存作爲散列的空元素。這些空元素可以作爲查找結束的標誌。
5、刪除操作:
由於線性探測散列表搜索遇到key爲null時就結束,因而刪除線性探測散列表中的一個鍵時,不能將該鍵設置爲null。如果設置爲null,則在該位置之後的元素將無法被查找。
python的dict類型就是使用開放地址散列表,它將每個(key,val)的組合成爲一個entry。其結構是:
[dictobject.h]
typedefstruct{
Py_ssize_tme_hash;
PyObject*me_key;
PyObject*me_value;
}PyDictEntry;
其中me_hash字段存儲的是me_key的散列值,利用一個字段記錄這個散列值可以避免每次查詢時都要重新計算一遍散列值。
在PyDictObject對象發生變化時,entry會在不同狀態間轉換。entry有三種狀態:Unused態、Active態和Dummy態。
當一個entry的me_key和me_value都是NULL時,entry處於Unused態。Unused態表明目前該entry中並沒有存儲(key,value)對,而且在此之前,也沒有存儲過它們。每一個entry在初始化時都會處於這種狀態,而且只有在Unused態下,entry的me_key字段纔會爲NULL。
當entry中存儲了一個(key,val)對時,entry便轉換到了Active態。在Active態下,me_key和me_value都不能爲NULL。
當entry中存儲的(key,value)對被刪除後,entry的狀態不能直接從Active態轉換爲Unused態,否則將導致衝突探測鏈的中斷。相反,entry中的me_key將指向dummy對象,entry進入Dummy態,這就是python的“僞刪除”技術。當python沿着某條衝突鏈搜索時,如果發現一個entry處於Dummy態,說明目前該entry雖然是無效的,但是其後的entry可能是有效的,應該繼續搜索,這樣就保證了衝突探測鏈的連續性。
6、鍵簇:
線性探測的平均成本取決於元素插入數組後聚集成的一組連續的條目,稱爲鍵簇。當新key的hash值與已經插入散列表中元素的hash值衝突時,線性探測要探測從該hash值向後組成的鍵簇的所有位置,因而,短小的鍵簇才能保證較高的效率。由於均勻性假設,數組每個位置都有相同的可能被插入一個新的鍵,因而長鍵簇更長的可能性比短鍵簇更大。
7、調整數組的大小:
命題M(此處只取其結論)表明:當散列表快滿時,需要探測的次數是巨大的,當散列表使用率小於1/2時,探測的預計次數只在1.5到2.5之間。因而,在實際應用時應考慮動態調整散列表數組的大小,確保數組的使用率永遠都不會超過1/2。