已知一個從小到大已排序的元素序列,如何插入到stl set中最快。
1. stl set內部結構
我們知道stl set內部是用紅黑樹來實現的。紅黑樹是一種平衡二叉查找樹,它有以下4個用來平衡的條件:
- 每個節點要麼是紅色,要麼是黑色
- 根節點爲黑色
- 紅節點的子節點一定是黑色
- 任一個節點至頁節點的任何路徑上,黑節點的個數相等
所以當插入一個新的非根節點時,它一定要是紅色,是爲了不破壞條件4。所以最難滿足的就是條件3。爲了滿足這個條件3,沒插入一個紅色節點後,都需要進行樹的平衡調整。
回到題目中,要是插入速度最快,就得儘量減少RB樹的平衡調整次數或花銷。
RB樹條件有兩種條件方式,一種就是簡單的將父輩節點的顏色進行調整,譬如紅色變成黑色,黑色變成紅色;另一種就是旋轉並調整顏色,有時不止一次旋轉。簡單的顏色調整是必須的,減少不了。因爲默認都是以紅色來插入一個新節點,不可能所有的節點都是紅色。因此,我們要做的就是儘量減少旋轉的次數,甚至根本不要讓它進行旋轉來調整。
2. 按照什麼順序插入
假如排序的數組元素序列是: 0, 1, 2, 3, 4, 5, 6, 7, 8。共9個元素。
如果瞭解二叉查找樹的特性的話,一個比較穩定的樹,或者說是一個比較方便以後查找元素的二叉樹,應該滿足左右子樹元素個數儘量對稱的佈局。RB樹既然是一個平衡二叉查找樹,那麼它也應該滿足這樣的優勢,可以讓之後的查找比較迅速(不然也不會採用RB樹作爲set的內部實現了)。我們期望的樹的佈局應該是下面這樣的:
這裏容易有一個誤區,就是遞歸的插入中間元素,譬如插入4之後,再遞歸插入左半部分的中間元素,之後是右半部分的中間。其實這樣是不對的。插入4之後,插入紅色節點2,之後是紅色節點0/1,這時就得進行右旋轉調整了,因爲0/1的父節點是紅色節點。所以我們必須對稱的插入左右中值元素,以保持RB樹的平衡。
3. 算法驗證及實現
上面插入的順序到底是不是我們的一廂情願呢?我們來驗證一下,還是以前面9個數字序列:0, 1, 2, 3, 4, 5, 6, 7, 8。根據我們前面推導的插入序列,它們應該按照這樣的方式插入:4, 2, 6, 1, 3, 5, 7, 0, 8。
a) 插入節點4,標記爲黑,因爲它是根節點。
紅色節點1作爲節點2的左子節點插入,由於父節點2是紅色,而且叔叔節點也是紅色。根據RB樹的調整規則,我們只需要將父輩節點2和節點6反轉爲黑色,將祖父節點4反轉爲紅色。但是最後又得將根節點4標記爲黑色(滿足規則2)。
有了前面1的插入,導致現在節點2和節點6都是黑色,節點3,5,7將被直接插入,不做任何調整。
紅色節點0作爲節點1的左子節點插入。由於節點1和節點3都爲紅色,類似於當時節點1的插入,將節點1和節點3反轉爲黑色,將節點2反轉爲紅色。節點8類似於節點1。
高亮標記的那個樹就是RB樹內部的結構。
從節點4到節點8的插入,期間只有顏色調整,沒有任何旋轉調整。因此滿足我們之前的需求。
下面就是序列插入時,RB樹內部結構變化圖:
一個基於BFS的Python代碼的實現(借用隊列):
#insert all elements from l (type is list) to s (type is set)
def insert_set(l,s):
sorted_l = sorted(l)
import Queue
q = Queue.deque()
begin, end = 0, len(l)
q.append((begin,end))
while len(q):
(begin, end) = q.popleft()
if begin >= end:
continue
mid = (begin + end) /2
print mid
s.add(l[mid])
q.append((begin,mid))
q.append((mid+1,end))
if __name__ == '__main__':
l = [0,1,2,3,4,5,6,7,8]
s = set([])
insert_set(l,s)