冰楓論壇

 找回密碼
 立即註冊
搜索
查看: 1762|回覆: 0
打印 上一主題 下一主題

[心得] C++實作無序容器的方法

[複製鏈接]

3

主題

0

好友

3

積分

新手上路

Rank: 1

UID
109481
帖子
3
主題
3
精華
0
積分
3
楓幣
14
威望
3
存款
0
贊助金額
0
推廣
0
GP
3
閱讀權限
10
性別
保密
在線時間
0 小時
註冊時間
2015-8-7
最後登入
2015-8-16
跳轉到指定樓層
1
發表於 2015-8-16 23:26:12 |只看該作者 |倒序瀏覽
此文是翻譯文章

網頁版
http://gamehevenhome.blogspot.tw/2015/08/c.html


C++不叫Hash Table,而是取了一個華麗名字叫無序容器(unordered  associative
container)。從2011開始c++提供了四種無序容器。

unordered_set  ,   unordered_map              (不可接受重複的元素)
unordered_multiset  , unordered_multimap (可接受重複的元素)
一個常用的方法是採用單向連結。

在這種狀況下,每個桶子內部都是一個指標,指向一個單向序列。如果Hash演算法設計的
好,每個鏈結都很短,插入跟刪除效率會接近O(1)。如圖所示, b1,b3只有一個元素,b2
有兩個元素。簡單方便,但是在C++不合用,因為規範要求容器必須可以遍歷,必須提供
一個iterator,可以讓 iterator從頭到尾掃過一遍。那要如何從b1跳到b2?最明顯的方
法是繼續掃描陣列,直到下一個有裝東西的桶子。但這不可用,因為陣列愈大,掃描愈
慢。偏偏規範明定++i;效率要保持O(1)。為了符合規範,只好把所有元素都串在一起。

我們來看一些Hash Table常用的結構。加上一個模仿Boost.MutiIndex的設計。先假設所
有元素都不一樣。
(unordered_multiset  , unordered_multimap以後再討論)

假設有N筆資料,陣列有B個桶子。

Dinkumware函式庫
這是微軟Visual C++的實現方法。請看圖。

(請注意,桶子的順序不一定要跟元素順序一樣。圖片只是為了繪圖方便)

就跟你想的一樣,所有元素都串在一起。使用雙向連結。現在可以用iterator雙向訪問,
代價是要多一個指標。好處是刪除元素非常容易。陣列裡面每個桶 子都有兩個指標,指
向鏈結的頭跟尾。

插入元素的方法:
1.使用Hash先找到桶子。(常數時間)
2.元素如果重複要放棄插入,所以要掃描桶子裡面所有元素。(桶子大小線性時間)
3.新元素插入在鏈結的頭。(常數時間)
4.調整桶子裡面的兩個指標。(常數時間)

刪除元素的方法:
1.使用Hash先找到桶子。(常數時間)
2.刪除鏈結一個元素,調整前後指標。(常數時間)
3.調整桶子裡面的兩個指標。(常數時間)
4.操作效率是O(1),記憶體消耗,每個元素要兩個指標,每個桶子也要兩個指標。

2N+2B
看起來很好,但其實Dinkumware的方法對於刪除元素,有一個嚴重的瑕疵。

使用Hash先找到桶子。(常數時間)
如果Hash函式會丟出例外,今天刪除動作就失敗了。偏偏規範又講明刪除保證要成功,而
且不可以丟出任何例外。所以Dinkumware方法其實不符合規 範。

Boost.Unordered, libc++ , libstdec++ -V3函式庫
Boost.Unordered, libc++函式庫使用類似的資料結構。但設計成單向鏈結。


所有元素用單向鏈結串在一起。
因為刪除動作不可以拋出異常。刪除的時候不可以呼叫Hash函式,所以只好把Hash後的數
字也存起來。
因為是單向鏈結,為了刪除方便。桶子裡面的指標不是指向第一筆元素,而是第一筆的前
一個。所以圖片中的b2指標指向前一個的b1。

刪除元素的方法:
1.因為節點裡面已經有Hash值,可直接定位到桶子。(常數時間,不會丟出異常)
2.鏈結掃過一遍。(桶子大小線性時間)
3.刪除鏈結一個元素,調整前後指標。(常數時間)
4.調整桶子裡面的指標。(常數時間)

插入刪除都是O(1)。滿足規範。記憶消耗如下

2N+1B
(我們假設Hash的數字,占用大小跟一個指標一樣)

libstdec++ -V3提供了一個最佳化設計。如果Hash函式確定不會丟出異常。(有使用
nonexcpt)。函式庫會標記成fast模式。節點裡面不會儲存Hash數 字。我覺得這設計有點
危險。代碼裡面使用__is_fast_hash type來標記,基本型態通通設定為true。使用者自
訂Hash函式預設是false,除非函式有用nonexcept,才會變成true。

不考慮最佳化的特殊機制,2N+1B似乎已經是好的設計了。但事實上還有更好的方法,不
需要再增加任何記憶體。

簿記式的資料結構
Boost 1.56 Boost.MutiIndex裡面使用了一種全新的方式。雙向鏈結改用環狀的方式。

定一個節點叫做X,下一個節點叫做Xn,前一個節點叫做Xp。環形鏈結代表

X = Xnp = Xpn
這個條件永遠成立。
連結往前再往後一定會回到自己。
連結往後再往前一定會回到自己。

我們現在看一下完整的示意圖


把陣列桶子也加進來,做一個小改動。

如果元素是桶子的第一個元素,Xp改成指向桶子自己。

可以導出一些規則。
如果Xpn != X,代表X是這個桶子的第一個元素
如果Xnp != X,代表X是這個桶子的最後一個元素
下一個元素一定可以用Xn取得。
前一個元素比較麻煩,如果是桶子的第一個元素,前一個元素就是Xpn,其他元素直接用
Xp存取即可。
所有動作都是常數時間,我們可以把它還是當作環形鏈結。只是往前移動需要特殊處理。

刪除元素的方法:
1.從雙向連結刪除元素,調整前後指標。(常數時間)
2.調整桶子裡面的指標。(常數時間)

刪除元素不需要把一個桶子都翻遍,就算遇到差勁的Hash演算法也沒差。記憶體不用增加
。還是2N+1B。
收藏收藏0 推0 噓0


把本文推薦給朋友或其他網站上,每次被點擊增加您在本站積分: 1鑰匙
複製連結並發給好友,以賺取推廣點數
簡單兩步驟,註冊、分享網址,即可獲得獎勵! 一起推廣文章換商品、賺$$
高級模式
B Color Image Link Quote Code Smilies |上傳

廣告刊登意見回饋關於我們管群招募本站規範DMCA隱私權政策

Copyright © 2011-2024 冰楓論壇, All rights reserved

免責聲明:本網站是以即時上載留言的方式運作,本站對所有留言的真實性、完整性及立場等,不負任何法律責任。

而一切留言之言論只代表留言者個人意見,並非本網站之立場,用戶不應信賴內容,並應自行判斷內容之真實性。

小黑屋|手機版|冰楓論壇

GMT+8, 2024-12-22 22:44

回頂部