《STL原始碼剖析》之hashtable

2021-08-04 10:03:19 字數 3317 閱讀 4730

hashtable即雜湊表,也叫雜湊表,它對元素的插入、刪除和訪問操作具有常數時間複雜度的表現,這種表現不依賴於輸入元素的隨機性。

假如使用雜湊儲存資料,且該所有的資料是16-bits且不帶正負號,範圍是0~65535,那麼使用乙個array就可以滿足上述期望。具體操作:

上述操作時間複雜度均為常數時間,這種解法的額外負擔是array的空間和初始化時間。且這個解法存在兩種問題:

第乙個問題是實實在在存在的問題,難以解決;第二個問題可通過把字串轉換為asii來解決,但是仍可能會產生巨大的數字,比如"jjhou"的索引是:

這太不切實際了,更長的字串會導致更大的數字,這就回歸到第乙個問題:array的大小可能會巨大。

為解決上述array空間巨大的問題,我們使用雜湊函式把大數對映成小數,把某一元素對映成大小可接受的索引。但是會帶來新的問題:衝突

比如:x為任意整數,tablesize為array的大小,則x%tablesize會得到乙個整數,範圍在0~tablesize-1,恰可作為array的索引。例如:5%10 == 15%10 == 5,此時,兩個數被對映到相同的位置,引發了衝突,也即元素出現了碰撞。

解決衝突的方法有多種,常見的有線性探測二次探測(也叫平方探測)開鏈法(也叫分離鏈結法)等。不同做法導致的效率各不相同——與array的填滿程度有關。

我們仍使用hash function計算出某個元素的插入位置,可能出現以下兩種情況:

該位置空間可用,那麼插入即可,over;

該位置空間不可用.........瘋狂尋找可用位置吧

針對第二種情況,我們只需迴圈往下一一尋找,如果到達尾端,那麼就繞到頭部繼續尋找,直到找到乙個可用空間為止。只要array足夠大,那麼總能找到乙個安身立命之處,但是尋找的時間複雜度就很難說了,元素的搜尋操作和插入操作道理相同。

下面是乙個例子:

使用線性探測法,需要以下假設:

在此假設下,最壞情況是線性遍歷整個array,平均情況是遍歷一半array——這和雜湊表的常數時間天差地遠了,且第二種假設真的太天真...............

此外,線性探測還會導致另外乙個問題:主集團

!在上圖中,除非新元素經過hash function的計算之後直接落在位置#4~#7,否則#4~#7永遠不可能被運用,因為位置#3永遠是第一考慮,換句話說,新元素不論是8,9,0,1,2,3中的哪乙個,都會落在位置#3上,新元素如果是4或5,或6,或7,才會各自落在位置#4,或#5,或#6,或#7上。這裡很清楚的突顯乙個問題:平均插入成本的成長幅度,遠高於負載係數(元素的個數除以**的大小,介於0~1)的成長程度,這樣的現象在hasing過程中成為主集團。

二次探測帶來的疑問:

對於q1

通過把**的大小設為質數,而且永遠保持負載係數在0.5以下(也就是說超過0.5就重新配置並重新整理**),就可以確定每次插入乙個元素所需探測的次數不超過兩次

。對於q2,二次探測通過數**算消除耗時的乘法和除法

,如下:

因此,如果我們能夠以前乙個h值,來計算下乙個h值,就不需要執行二次方所需的乘法了。肅然還有乙個乘法,但那是乘以2,,可通過位移位快速完成;至於mod運算,也可證明並非真有需要(本處略)。對於q3,欲擴充**,首先必須找出下乙個新的而且夠大(大約兩倍)的質數,然後必須考慮重建**的成本——是的,

不可能原封不動地拷貝而已,我們必須檢驗舊**中的每乙個元素,計算其在新**中的位置,然後再插入到新的**中。

二次探測消除了主集團問題,卻帶來了次集團

問題!兩個元素經hash function計算出來的位置若相同,則插入時所探測的位置也相同,形成某種浪費。次集團可由復雜湊解決,總體來說二次探測仍然值得投資。

能和二次探測分庭抗禮的是開鏈法,sgi stl的hast table便是採用開鏈法。這種做法是在**(vector)中的每個元素中維護乙個list

,在鍊錶身上執行元素的插入、搜尋及刪除等操作是線性操作,但是如果list足夠短,速度還是夠快。

3-1 hashtable桶子與節點

hashtable使用vector作為底層**

,以便於動態擴充,vector中的元素成為桶子,桶子維護linked list,注意該list並不是stl的list或slist

,而是自行定義的,如下所示:

templatestruct __hashtable_node

;

sgi stl以開鏈法完成的hash table入下圖所示:

3-4-1 插入操作(insert)與**重整(resize)

3-4-2 判斷元素的落腳處(btk_num)

3-4-3 複製(copy_from)和整體刪除(clear)

——參考《stl原始碼剖析》

STL之deque原始碼剖析

deque是一種那個雙向開口的連續線性空間,其頭尾端做元素的插入和刪除效率比vector效率高很多。deque和vector的最大差異,一在於deque允許常數時間內對頭尾端進行元素插入或移除操作,二在於deque沒有所謂容量概念,因為它是動態地分段連續空間組合而成,隨時可以增加一段新的空間並鏈結起...

STL原始碼剖析之vector

向量vector 1.vector概述 vector的資料安排以及操作方式,與array非常相似。兩者的唯一差別在於空間的運用的靈活性。array是靜態空間,一旦配置了就不能改變 vector是動態空間,隨著元素的加入,它的內部機制會自行擴充空間以容納新元素。vector的實現技術,關鍵在於其對大小...

STL原始碼剖析之Iterator

typename一般來說用法比較簡單,在定義模板的時候宣告乙個類引數。template class demo 這個時候typename和class沒有任何區別。但是typename還有其他用法。乙個類除了有類的成員變數 成員函式之外,還可以有類的定義。template class demo 這種定義...