標籤:

C++ 如何實現平衡的名次樹?

如何最方便的實現,對一棵平衡二叉樹,進行動態的插入,刪除,選擇第k小的key,查詢一個key的rank?

除了用Treap的模板,能不能用C++ STL中的數據結構或者某些變形來實現這個功能?


GCC的Policy-based data structures里自帶: Chapter 22. Policy-Based Data Structures

#include &
#include &
#include &
#include &

using namespace std;

using order_statistic_tree = __gnu_pbds::tree&< int, __gnu_pbds::null_type, less&,
__gnu_pbds::rb_tree_tag,
__gnu_pbds::tree_order_statistics_node_update&>;

int main() {
order_statistic_tree t;
vector& vec { 1, 3, 5, 7, 9, 11, 2 };
for (int x : vec)
t.insert(x);

cout &<&< "order of 7: " &<&< t.order_of_key(7) &<&< endl; cout &<&< "order of 8: " &<&< t.order_of_key(8) &<&< endl; cout &<&< "order of 1: " &<&< t.order_of_key(1) &<&< endl; cout &<&< "key of rank 5: " &<&< *t.find_by_order(5) &<&< endl; return 0; }

輸出

order of 7: 4
order of 8: 5
order of 1: 0
key of rank 5: 9

另外PBDS里有很多其他有趣的東西,例如thin heap,pairing heap等,不妨看看。


據我所知,不能用STL。


@菜魚ftfish 你說的對

正確答案,使用Order statistic tree

簡單介紹一下:

要讓一棵平衡二叉樹變成一顆order statistic tree,每個節點需要多存儲一個信息:

就是這個當前節點下以此節點為根的子樹的節點數目

演算法原理是這樣的,(非常簡單)

function Select(t, k)
// Returns the k"th element (zero-indexed) of the elements in t
r ← size[left[t]]
if k = r
return key[t]
else if k &< r return Select(left[t], k) else return Select(right[t], k - (r + 1))

如果左子樹剛好k個節點呢,這說明根節點就是我們要找的值,因為根據BST的性質,它的左子樹都比它小,又有k-1個,所以根正好是第k小。

如果左子樹的節點數多於k個呢,說明這個第k小肯定在左邊,因為根據BST的性質,左子樹都比根小,那麼我們遞歸的查找左子樹

如果左子樹的節點數不夠k呢,說明這個第k小肯定在右邊,因為根據BST的性質,左子樹那些節點都要比右子樹的小,所以呢肯定在右邊,就是右邊第 k-r+1小,那麼我們遞歸的查找右子樹。

這樣第k小只需O(logn)就可以找到了。

然後插入/查找/刪除這些都是普通的紅黑樹/AVL咯。

然後才發現題主問了rank(汗)

所謂的rank就是樹節點的index唄

order stats tree 一樣能做:

function Rank(T, x)
// Returns the position of x (one-indexed) in the linear sorted list of elements of the tree T
r ← size[left[x]] + 1
y ← x
while y ≠ T.root
if y = right[y.p]
r ← r + size[left[y.p]] + 1
y ← y.p
return r

求x的rank就是算一個節點前面有多少個節點

如果這個節點就是整個根節點,那麼就是他左子樹的個數

如果這個節點不是根節點,那麼如果它位於左邊子樹上,我們不用管

如果它位於右邊子樹上,那麼我們要統計它父節點左邊子樹有多少個,如果父節點還在一顆右子樹上我們還要這樣算下去,直到根節點

原答案(慢多了)

----------------------------------------------------------------------------------------

第k小問題用堆唄。

刪除第K小:

用斐波那楔堆,取k-1次最小值建一個新斐波那楔堆,然後刪掉第k個在合併,總共時間複雜度度是 k次取最小 O(klogn) + k-1次 insert:O(k-1) + 1次合併 O(1) = O(klog n+k) = O(klog n) (均攤) 最差k=n O(nlogn)

返回第K小:上面那個稍微改改就有了。。。

樹節點裡可以再存個到堆的refer,這樣刪除樹中節點的時候就免除從堆中搜索的問題了,利用樹進行高效的搜索。

刪除:樹查找O(logn) + 斐波那楔堆刪除O(logn) + 樹刪除(假設stl map) O(logn) 所以還是O(logn)

插入:斐波那楔堆插入O(1) +樹插入O(logn)

查找:樹查找O(logn)


我只知道redis里是用跳錶實現的


感覺multiset可以解決這個問題


@菜魚ftfish 提到的pbds是科學的做法。我在搞OI的時候還知道一個不那麼科學的做法(方法非原創,代碼是我寫的):

template&
struct RBtree{
#define l _M_left
#define r _M_right
#define p _M_parent
#define node _Rb_tree_node_base
#define key _M_value_field.first
#define size _M_value_field.second
typedef _Rb_tree_node& &> Node; map& M;
void fix_size(node *it){
int it_size=static_cast&(it)-&>size;it_size=1;
if(it-&>l)it_size+=static_cast&(it-&>l)-&>size;
if(it-&>r)it_size+=static_cast&(it-&>r)-&>size;
}
void fix_all(node *it,node *end){
for (;;it=it-&>p){
if (it-&>l)fix_size(it-&>l);if (it-&>r)fix_size(it-&>r);
if (it-&>p==end){fix_size(it);break;}
}
}
void insert(const T x){
pair&::iterator,bool&> fit=M.insert(make_pair(x,0));
if(!fit.second)return;
fix_all(fit.first._M_node,M.end()._M_node);
}
int findkth(int k){
node *x=get_root();
while (k){
int sizel=x-&>l?static_cast&(x-&>l)-&>size:0;
if (k==sizel+1)break;
if (k&<=sizel)x=x-&>l;
else k-=sizel+1,x=x-&>r;
}
return static_cast&(x)-&>key;
}
node *get_root(){
node *it=M.begin()._M_node;
while(it-&>p!=M.end()._M_node)it=it-&>p;
return it;
}
void print(){print_node(get_root(),"");}
void print_node(const node *it,string str){
if(!it){cout&<&(it)-&>key;
cout&<&<"("&<&(it)-&>size&<&<")"&<&l,str+" ");print_node(it-&>r,str+" ");
}
#undef l
#undef r
#undef p
#undef node
#undef key
#undef size
};
RBtree& a;

原理是手動給STL的紅黑樹增加維護size的功能。

對 @Howard Curry 的觀點我是基本贊同的。但是這個(較為麻煩)的做法算是繞過了封裝問題。

樓上提到了樹狀數組。補一個求第k小O(log n)的做法:

int findkth(int k){
int ans=0,cnt=0;
for (int i=20;i&>=0;--i){
ans+=1&<&=n||cnt+c[ans]&>=k)ans-=1&<&


樹狀數組。空間O(n),n是你的數據範圍。

插入刪除都是O(nlogn)的。

選擇第k小,用二分,O(logn * logn).

查詢名次,O(logn)。

唯一限制是key的範圍。太大就GG。


不能,二叉樹維持平衡的關鍵在於各種旋轉操作。而旋轉時二叉樹節點的rank 值也要對應變化,然而STL中這個旋轉操作並不會暴露給你。即使你給每個節點封裝一個rank域也徒勞無功。


推薦閱讀:

為什麼常見的O(n)時間的找凸多邊形內最大內接三角形的那個演算法是錯誤的?
通過脈搏波PPG波形推導血壓(主要是SBP)有沒有可能性,結合其它例如年齡、體重等參數呢?
演算法 第四版(algorithms 4th edition ) 這本書有配套的習題答案嗎?
有可能利用監控實時識別每一個出現的人么?
怎麼列動態規劃遞推方程?

TAG:演算法 | STL | C |