1. 程式人生 > >演算法導論思考題13-4 treap-樹堆

演算法導論思考題13-4 treap-樹堆

treap樹,是一種以節點的值和附加的優先順序來實現的搜尋樹。正如演算法導論中所介紹的那樣。

”如果將一個n個元素的集合插入到一顆二叉搜尋樹中,所得到的樹可能極不平衡,從而導致某些平均查詢長度變長。然後有引用了隨機構造的二叉搜尋樹是趨於平衡的。這裡我存在一個疑問。先不管他們是如何使用數學證明隨機的二叉搜尋樹是趨於平衡的。我想問的是真的是這樣嗎?我能否找出反例?加入我完全按照順序插入1-100共100個節點,明顯是極不平衡的搜尋樹。但是這能算是反例嗎?要明白這點,需要先明白這裡反例和命題直接的關係,反例是相對於某個全稱命題的概念。這裡命題:“隨機構造的二叉搜尋樹是趨於平衡的”是否是全稱命題呢?。這裡是否包含或者隱含了”對於所有的“這幾個字?。明顯沒有,隱含的呢?對於所有的

隨機構造的二叉搜尋樹是趨於平衡的”是否是全稱命題。這樣也是不對的,隨機構造的和所有的看起來是矛盾的。看起來這並不是能夠舉出反例的東西,因為這個命題應該不是全稱命題。所以舉例1-100順序插入100個節點所造成的搜尋樹不能稱為反例。但很明確的是這是一個命題。因為命題是有真假的。從命題的角度來說並沒有給予我有用的資訊。

回到這個命題本身,先認為其為真,但也符合常識。書中提出了問題:如果沒法同時得到所有的元素,應該怎樣處理呢?更加具體的描述則為,如果一次收到一個元素,是否任然能用它們來隨機建立一顆二叉搜尋樹呢?。

我的回答是:應該能吧,

我進一步提問:如果能的話,我如何做才能在如果一次收到一個元素的情況下,任然能用它們來隨機建立一顆二叉搜尋樹呢?

再一次提問:隨機建立一顆二叉搜尋樹我需要一些什麼東西呢?

回答:隨機建立搜尋樹應該是需要隨機樹的吧。

再提問:隨機數如何與二叉樹聯絡起來呢?

再提問:由於我們的比較用的鍵值明顯是一次一個元素,那就是順序插入,我如何打亂他們的順序呢?如何結合隨機數呢?

回答:大概可以為每一個元素插入時候提供一個隨機數,我們一開始不直接插入這個隨機數n,而是等這個搜尋樹T經過n次插入函式的呼叫的時候我在實際插入這個元素到搜尋樹中去。通過控制n的大小範圍,在實際插入到搜尋樹T中之前,先儲存在緩衝區中去。但假如我這時候又要搜尋或者刪除操作穿插而來,這時候我就對快取區的元素的等待次數進行排序,小的值先插入,大的後。這樣子清空快取區,然後就可以安然的進行刪除之類的操作了。事先知道答案的我明顯發現這樣的做法雖然也保證的節點的隨機插入,但是明顯劣於書上的做法。

但是在節點中加入一個隨機數域也是可以肯定的了。我的做法還是脫離不了隨機插入的本質,表面上是順序插入,其實內部實現還是隨機插入。如果非要按照確定的順序插入那必然不是簡單的二叉樹插入,必然還要去改變什麼。需要制定一個規則,或者某種性質,就像紅黑樹5大性質一樣,並且在插入中始終維護這個性質。

到這裡似乎就是思考的極限了,書上結合最小堆的性質我是想不出來為什麼這樣做?但是看書中的內容,它讓我理解的方式似乎是再說這和我之前提出來的解決辦法是很有關係的。我應該讓獲得最小隨機數的數值作為根結點,當成第一個插入的節點。只有這樣就能達到和書上的方法一樣的效果了。當然題目a)證明在優先順序不重複的情況下的使用堆的規則來維護搜尋樹可以達到可隨機插入相同的效果。也就是說插入本身就是相同結果的不同做法。核心理念是::按照隨機分配的不重複隨機數大小先後插入。

這大概也就是第一題a.就要證明這樣子的東西吧,這並不是需要證明的問題,而是一開始就以這個為目標而創造出樹堆的吧。證明就免了,懶得去做。

的確優先順序最小的節點必然應該第一個插入作為樹根,那麼從順序插入的角度來看,保持最小堆的性質也能保證優先順序最小的節點永遠在樹根處。對於子樹明顯也是相同的。保持最小堆性質其實不能直接保證這是隨機插入的搜尋樹,但是如果這個最小堆搜尋樹和真正的隨機插入搜尋樹是完全一樣的話,這樣就保證了新增最小堆性質的做法。類似於我之前的想法。表面上是順序插入,本質和隨機插入是等效的。很好的做法。

我在看看問題b,已經具體到了插入如何維護了嗎?。但是我現在還在沉溺於使用最小堆來達到和隨機插入同樣的效果這樣的處理方法中。簡單,高效。感覺十分類似於divide and conquer 和遞迴這兩個處理問題的方法。從隨機抽取插入轉化到每次選取隨機數優先順序最小的數的節點插入,再將問題分解為優先順序最小的節點作為樹根,然後就會有小於這個節點的所有節點和大於這個節點的所有節點集合這兩部分,分而治之,並利用遞迴。分別對這兩個集合使用隨機抽取插入方法來構造隨機搜尋樹,再一次轉化為每一次選取最小的隨機數優先順序的結點作為子樹的根結點。。。。以此類推,通過轉化問題在加上遞迴加上分治法完成了隨機搜尋樹的構建。因為每次所呼叫的函式是一個隨機選取節點構建搜尋樹的函式,但問題經過等效轉換,變為依次選取優先順序最小的節點插入這樣也能生成隨機搜尋樹。

繼續問題b,證明treap的期望高度是c1*lgn 到c2*lgn之間。因為樹堆與隨機插入搜尋樹等效,隨機插入搜尋樹之前已經在上一章有了證明,他的平均搜尋深度為lg(n)

問題c,虛擬碼略。在treap-insert中,插入一個元素後可能會出現插入節點x和其父節點y之間不滿足最小堆的性質。這裡每一次旋轉前都有一個前提條件是始終滿足的,也是旋轉操作始終維護著的。那就是對於任意一對父子節點parent,child來說。child子樹中所有除child以外的節點的priority都大於parent節點的priority。這樣以來很容易看出,parent節點不論是左旋右旋,都不會違背這條性質。

問題d。treap-insert的執行時間。等效於平均深度。這同問題b

問題e。證明x在插入期間執行的旋轉中次數等於C+D。

x在插入期間的旋轉次數 = 左旋次數+ 右旋次數

對於插入維護,明顯可知不論是左旋還是右旋,其目的是讓x成為父節點,提高x節點的高度,使x成為旋轉後的子樹根節點。左旋的特性明顯只改變x節點的左子樹,右旋改變右子樹。左旋所改變的具體東西就是為x的左子樹增加一個右脊柱長度,而右子樹也同理。還要注意的是即使左旋右旋交替進行,左旋右旋不相互影響,也就是左旋只改變左子樹,右旋只改變x節點的右子樹。並且x剛插入時候是葉節點。故而有

 左旋次數  = 左子樹右脊柱長度 = C

右旋次數 = 右子樹左脊柱長度 = D

從而:x在插入期間的旋轉次數 = 左旋次數+ 右旋次數 = C + D

對於問題f我有個疑問。雖然Xik = 1  -> y.priority > x.priority且y.key < x.key。但這似乎無法反過來。如果反過來只能確定y在x的左子樹中,有如何能確定y一點在x的左子樹的右脊柱中呢?發現是中文翻譯的問題,回到原版書籍可以發現原文。


這是再說Xik = 1   等價於 y.priority > x.priority且y.key < x.key和第二個條件同時滿足的情況下。

那麼我重新證明一下,當Xik = 1時,y.priority > x.priority且y.key < x.key是明顯的。再看第二個:對任意的z.key 屬於特定範圍,有y.priority >z.priority。確實,如果節點y在x的左子樹的右脊柱中,那麼z一定在y的右子樹中。

反過來再證。已知 y.priority > x.priority且y.key < x.key並且第二個條件也滿足那麼可以斷定

任意的滿足條件2的節點z必然不可能是y或者x的祖先節點,(祖先節點priority比如更小)

-> 節點y和節點x之間必然有一個公共祖先節點w。由於y.key < x.key,而w又是x和y的公共祖先,所以w.key大於等於而x.key小於等於y.key。這裡好像又再次出現了矛盾,因為如果出現了有兩個節點之間key值相等的情況那麼就會造成反過來推理不成立。反例如下:


對於節點x和節點y之間明顯滿足x.key = 100 > y.key = 50.x.priority = 1 < y.priority  = 4。並且條件二也滿足,但這時候y不在x左子樹的右脊柱上。所以除了priority不能重複以外,還要加上key值也不能重複才能嚴格滿足數學證明的結果,否則也只可能是接近這一結果。

回到f的證明現在因為不存在key值相等的節點,w.key大於等於而x.key小於等於y.key變成了所以y.key > w.key >= x.key。這是w屬於條件2中的z的一員,但已經明確要求了不能等於,除非w節點就是x節點。這樣就證明x是y的祖先,y在x的左子樹內。而且由於條件2中的節點z.key值都大於節點y.key且z.priority > y.priority。這麼就表面y的所有到x之前祖先都是右孩子。故而y在x的左子樹的右邊脊柱中。

再看問題g。這裡發現問題e中已經假設關鍵字為1,2,3,,,,n。發火難怪,鍵值個個不相等,而且數量固定。

之前在問題a中已經證明,只要每個key值對應的優先順序不變,那麼這些節點無論什麼樣的插入順序都無所謂。這也就意味著這n的節點之間的priority值的大小排序關係覺得了這顆搜尋樹。

問題g是證明一個概率。證明節點y在x的左子樹的右邊脊柱中的概率為某個值。上面一行已經說明了,優先順序和鍵值關係對搜尋樹形狀的影響。一共有n個節點(e中做的假設),其值為1,2,,,i,,k,,n。那麼所能產生的二叉樹一共有n!種。這是十分明確的。其中節點x和節點y滿足y在x的左子樹的右邊脊柱中這樣的關係的搜尋樹有多少種呢?根據問題F中已經證明的問題。突然發現問題應該再次簡化一下。根據f中的證明,f是否滿足可以解釋為x.key  > y.key ,x.priority< y.priority 以及條件二。嘛其實就是把f證明內容照抄一遍。從這裡我們看出來節點按照鍵值排序和節點按照priority排序兩個排序序列覺得了整個搜尋樹。當然key序列相同情況下就由priority排序序列覺得搜尋樹形態,這裡就是這種情況。priority排序序列一共有n!種,其中只要滿足問題F的序列就是我們所要求的序列。問題F是否滿足只取決於鍵值為x.key,x.key+1,,,y.key這k-i+1個結點的priority順序有關。因為我們不去關心所有n的節點的priority序列,我們只關注這個序列中的鍵值為y.key,y.key+1,,,x.key這k-i+1個結點的子序列。當在全節點priorit序列中這k-i+1個節點中x節點在子序列最右邊,y節點在子序列最左端這樣的priority序列就會滿足問題F,從而節點y在x的左子樹的右邊脊柱中。這樣就從n個節點中抽取滿足條件的k-j+1個節點來考慮就行了,因為只有這些節點的關係才是我需要考慮的。這麼理解就直接得出了問題g.

問題h:這裡圍繞著插入節點x的左子樹而言,明顯就是問題g的簡單變化,數值i根據key從1到 k-1變化求和,在做一次轉換變為h中的形式。不過有一點值得說,就是這k-1個節點中出來x和y節點。每個節點處於節點x的左子樹的右邊脊柱中的概率是相同的。所以直接把他們加起來。

問題i:這裡圍繞著插入節點x的右子樹而言,與h不一樣的是h中以節點x為末端節點,數值i和k中k = x.key,i等於key值為1-k-1的任意值。

我這裡反過來令i等於x.key 而k值從x.key + 1 到n之間變化,求和。轉換一下k-i用j來代替,就完成了求和。

問題j:Ec + Ed <2很明顯。

接下來再實現一個刪除操作。

就用紅黑樹的刪除方式程式碼都一樣,除了不需要刪除修復。輕鬆加愉快。

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<time.h>
#include<unordered_set>
#defineTREAP_NODETREAP
//#defineROOT(T)(T->left)
typedefstructnode{
structnode*left;
structnode*right;
structnode*parent;
intpriority;
intdata;
}*TREAP;
voidleft_rotate(TREAP_NODET,TREAP_NODEx)
{
structnode*y=x->right;
if(y==T)return;
x->right=y->left;
if(y->left!=T){
y->left->parent=x;
}
y->left=x;
y->parent=x->parent;
if(x==y->parent->left){
y->parent->left=y;
}else{
y->parent->right=y;
}
x->parent=y;
}
voidright_rotate(TREAP_NODET,TREAP_NODEx)
{
structnode*y=x->left;
if(y==T)return;
x->left=y->right;
if(x->left!=T){
x->left->parent=x;
}
y->right=x;
y->parent=x->parent;
if(x->parent->left==x){
x->parent->left=y;
}else{
x->parent->right=y;
}
x->parent=y;
}
std::unordered_set<int>_set;
voidinit_priority()
{
_set.clear();
srand((unsigned)time(0));
}
intmalloc_a_priority()
{
intvalue=rand();
while(_set.find(value)!=_set.end())
{
value=rand();
}
_set.insert(value);
returnvalue;
}
voidtreap_insert(TREAP_NODET,TREAP_NODEz)
{
TREAP_NODEy=T;
TREAP_NODEx=T->left;
while(x!=T)
{
y=x;
if(z->data<x->data)
{
x=x->left;
}else{
x=x->right;
}
}
z->parent=y;
if(y==T){
T->left=z;
}elseif(z->data<y->data){
y->left=z;
}else{
y->right=z;
}
z->left=T;
z->right=T;
z->priority=malloc_a_priority();
while(y!=T&&y->priority>z->priority)
{
if(z==y->left)
right_rotate(T,y);
else
left_rotate(T,y);
y=z->parent;
}
}
voidrb_transplant(TREAP_NODET,TREAP_NODEu,TREAP_NODEv)
{
if(u==u->parent->left){
u->parent->left=v;
}elseu->parent->right=v;
v->parent=u->parent;
}
TREAP_NODEtree_minimum(TREAP_NODET,TREAP_NODEx)
{
TREAP_NODEp=x;
while(p->left!=T){
p=p->left;
}
returnp;
}
voidtreap_delete(TREAP_NODET,TREAP_NODEz)
{
TREAP_NODEy=z;
y=z;
TREAP_NODEx;
if(z->left==T)
{
x=z->right;
rb_transplant(T,z,x);
}elseif(z->right==T)
{
x=z->left;
rb_transplant(T,z,x);
}else{
y=tree_minimum(T,z->right);
x=y->right;
if(y->parent==z)
x->parent=y;
else{
rb_transplant(T,y,y->right);
y->right=z->right;
y->right->parent=y;
}
rb_transplant(T,z,y);
y->priority=z->priority;
y->left=z->left;
y->left->parent=y;
}
}
intget_hight(TREAP_NODET,TREAP_NODEz)
{
inti=0;
while(z!=T){
i++;
z=z->parent;
}
returni;
}
intcheck(TREAP_NODET,TREAP_NODEz)
{
if(z==T){
return1;
}
if(z==0){
puts("z==0");
return0;
}
if(z->left!=T){
if(z->left->priority<z->priority){
puts("priorityerror");
return0;
}
if(!check(T,z->left))return0;
}
if(z->right!=T){
if(z->right->priority<z->priority)return0;
if(!check(T,z->right))return0;
}
return1;
}
voidprint(TREAP_NODET,TREAP_NODEN)
{
if(N!=T){
printf("data=%dpriority=%d",N->data,N->priority);
if(N->left!=T)
printf("N->left->data=%d",N->left->data);
if(N->right!=T)
printf("N->right->data=%d",N->right->data);
puts("");
print(T,N->left);
print(T,N->right);
}
}
intmain()
{
structnodetree;
tree.left=&tree;
tree.right=&tree;
tree.parent=0;
tree.data=0;
init_priority();
tree.priority=malloc_a_priority();
TREAP_NODEin[10000],T=&tree;
//freopen(".//data1.txt","r",stdin);
//freopen(".//out11.txt","w",stdout);
for(inti=0;i<10000;i++)
{
in[i]=(TREAP_NODE)malloc(sizeof(structnode));
in[i]->data=rand()%100000;
//scanf("%d",&in[i]->data);
//printf("%d",in[i]->data);
treap_insert(T,in[i]);
//printf("%dnumber=%d\n",i,in[i]->data);
//print(T,T->left);
if(!check(T,T->left)){
puts("error");
return0;
}
//puts("-----------------------------------------------------------------");
//if(i==5000)
//print(&T,T.left);
}
intmax_hight=0;
longlongall_hight=0;
for(inti=0;i<10000;i++)
{
inttemp=get_hight(T,in[i]);
max_hight=max_hight<temp?temp:max_hight;
all_hight+=temp;
}
longlonga=all_hight/10000;
if(!check(T,T->left))
puts("error");
printf("max=%davg=%d\n",max_hight,a);
for(inti=0;i<10000;i++)
{
//printf("i=%d\n",i);
treap_delete(T,in[i]);
if(!check(T,T->left)){
puts("error");
return0;
}
}
return0;
}
多次測試發現10000個隨機數最大深度多在30-34之間,平均深度16-17;相對於紅黑樹而說差了很多。紅黑樹在測試時候1000個節點最大深度多以16居多,偶然出現17的深度,平均節點深度多為12。並且黑高不是8就是9。

相關推薦

演算法導論思考題13-4 treap-

treap樹,是一種以節點的值和附加的優先順序來實現的搜尋樹。正如演算法導論中所介紹的那樣。 ”如果將一個n個元素的集合插入到一顆二叉搜尋樹中,所得到的樹可能極不平衡,從而導致某些平均查詢長度變長。然後有引用了隨機構造的二叉搜尋樹是趨於平衡的。這裡我存在一個疑問。先不管他們

演算法導論13章 紅黑(圖文詳細解說)

1、二叉查詢樹的不足 二叉查詢樹的基本操作包括搜尋、插入、刪除、取最大和最小值等都能夠在O(h)(h為樹的高度)時間複雜度內實現,因此能在期望時間O(lgn)下實現,但是二叉查詢樹的平衡性在這些操作中並沒有得到維護,其高度可能會變得很高,當其高度較高時,二叉查詢樹的效能就未

演算法導論 思考題 4-3

a. 利用主方法可得,T(n)=θ(n的log34次方) b. n/f(n)=lgn,不能應用主方法     共log3n+1層,每層代價n/lgn/(3的i次方),最後一層共n個θ(1)的結點,代價為θ(n)     T(n)=∑n/lgn/(3的i次方)+θ(n)  

演算法導論13.1習題

前三題考察對紅黑樹5條性質的理解,比較簡單。4-7題很有啟發性。 13.1-4 可以把從樹根到葉子的路徑想象成一根枝條,按照紅黑樹屬性5的要求,當完全收縮時,每根枝條上黑色節點數目是相同的,當完全伸展開時,會從兩個黑色節點之間抽出一個紅色節點。 這種設計的妙處是,對同一個根下的兩根枝條a,b(a長於

演算法導論chapter12 二叉搜尋課後習題答案詳解

英文答案https://wenku.baidu.com/view/000b5347b9f3f90f76c61b9b.html 12.1 定義 構建方法:12.1-1,2 遍歷:12.1-3,4   12.1-1 對於關鍵字集合 { 1,4,

演算法導論 之 平衡二叉 - 建立 插入 查詢 銷燬 - 遞迴 C語言

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

演算法導論 之 平衡二叉 - 刪除 - 遞迴 C語言

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

演算法導論》7.4-5:用插入排序對快速排序進行優化

 當陣列幾乎有序時,插入排序很快。當快速排序分割到一定小的模組後再對整個陣列進行插入排序,來實現對快速排序的優化。確定分割到多大時再進行插入排序合適? #include<stdio.h> #include<stdlib.h> #include&l

最長遞增子序列問題(演算法導論作業15.4-5、15.4-6)

問題描述 對於長度為n的序列S[1...n],找出長度最大的子序列,其子序列的每個元素均遞增。 15.4-5、時間複雜度O(n^2) 剛看到這題時,想到了個投機取巧的方法。因為書中此節介紹了LCS(最長公共子序列)演算法,於是可以直接將這個序列排序O(nlogn),然後

演算法導論 之 平衡二叉

/****************************************************************************** **函式名稱: avl_lr_balance **功 能: LR型平衡化處理 - 先左旋轉 再向右旋轉(內部介面) **輸入引數: *

演算法導論 第18章 B

一、定義 1、B樹 B樹是為磁碟或其它直接存取輔助儲存裝置而設計的一種平衡查詢樹,主要特點是降低磁碟I/O操作次數。 B樹以自然的方式推廣二叉查詢樹。 B樹的分支因子由磁碟特性所決定。  2、B數的資料結構 int n:當前儲存在結點x中的關鍵字數 key[N]:n個關鍵,

Treap()圖解與實現

前面我們介紹了AVL樹,伸展樹,它們都是二叉搜尋樹,二叉搜尋樹的主要問題就是其結構與資料相關,樹的深度可能會很大,Treap樹就是一種解決二叉搜尋樹可能深度過大的另一種資料結構。 Treap=Tree+Heap。Treap本身是一棵二叉搜尋樹,它的左子樹和右子樹

演算法導論】求二叉的葉子數和深度

/**********************************************\ 函式功能:計算葉子節點個數 輸入: 二叉樹的根節點 輸出: 葉子節點個數 \**********************************************/ int countleaf(

演算法導論2.1-4 考慮n位二進位制整數相加起來的問題

考慮把兩個n位二進位制整數加起來的問題,這兩個整數分別儲存在兩個n元陣列A和B中。這兩個整數的和應按二進位制形式儲存在一個(n+1)元陣列C中。使用java程式碼實現;實現程式碼如下:引數A和B為相同長度n的整形陣列,該函式返回一個長度為n+1的陣列 private s

演算法導論習題練習——紅黑的插入和刪除

題目 13.3-2 將關鍵字 41、38、31、12、19、8 連續地插入一棵初始化為空的紅黑樹之後,試畫出該結果樹。 Solution: 13.4-3 在練習13.3-2中,我們將關鍵字41

演算法導論】二叉排序

具體程式實現如下: /**************************************\ 函式功能:在二叉排序樹中刪除節點 輸入: 二叉排序樹的根節點、要刪除的節點的內容 輸出: 二叉排序樹的根節點 \*************************************

演算法導論26.1-4

26.1-4 問題描述 設f為網路的一個流,設α為一個實數,則αf稱為標量流積,該標量流積是一個從V×V到R的一個函式,其定義如下: (αf)(u,v)=α⋅f(u,v) 證明:網路中的流形成

演算法導論 5.3-4

Armstrong教授建議使用下列過程來產生均勻隨機排列:PERMUTE-BY-CYCLIC(A)1 n <- length[A]2 offset <- RANDOM(1,n)3 for i <- 1 to n4 do dest <- i + offset5 if dest >

演算法導論 思考題 2-2

題目: 氣泡排序演算法的正確性 解答: a)還需要證明什麼? 不等式證明了,終止條件也證明了,缺啥? 證明子陣列是原陣列的一部分,也就是說,A′[i] i=1∼n 可以構成原陣列 b

演算法導論學習筆記—紅黑最全的Java實現

紅黑樹(red-black-tree)是許多“平衡”搜尋樹的一種,它可以保證在最壞情況下基本動態集合操作的時間複雜度為O(lgn)。除遍歷外,其餘的方法的時間複雜度都為O(lgn),如INSERT, SEARCH, MAXIMUM, MINIMUM, DELETE等。本章