1. 程式人生 > >【省選】演算法總結——平衡樹

【省選】演算法總結——平衡樹

130409總結——平衡樹


其實到現在為止Treap還沒寫成功過(網上是指標,就照著指標寫。。。。球了唄。。。)

不過後來還好,SBT是陣列的,理解後想改成指標(看起來多好看的),是在無力啊。。。

想想還是算了,就陣列吧,不容易出錯

1.二叉查詢樹(二叉搜尋樹)BST

1.1基本思想

其實思想很簡單,就是建立一顆二叉樹,左子樹都小於根,右子樹都大於根,來實現快速查詢(其實很慢。。。)

1.2插入

插入X,如果X<當前節點的值(key),就把X插入左子樹,否則插入右子樹(等於也在右子樹,方便且不容易出錯)

1.3查詢

如果要查詢一個數X,如果X<當前key,就在左子樹找,否則就在當前節點或者右子樹找

弊端

前面說這個似快非快是有道理的,比如一個遞增序列,X1插入跟,X2進入右子樹,X3進入右子樹的右子樹,。。。。,這樣這棵樹就退化成一條鏈了!

所以我們需要實現平衡!實現嚴格log2N

2.隨機二叉查詢樹Treap

顧名思義,Treap= Tree + heap,所以也稱作“樹堆”

前面說了BST退化的原因在於樹的根固定了,兒子有可能一直往一邊跑,那我們可不可以通過變換把樹平衡呢?先看下圖


我們可以通過把23dia起來實現樹的平衡,如下圖


Treap的思想就是給每個節點多維護一個附加資訊fix,給它一個隨機值,然後在保證key為二叉查詢樹的基礎上再給fix建立一個小(大)根堆,由於隨機數是大致平均的,所以我們也可以實現大致的平衡,雖然不是嚴格log2

N,但也可以保證快速了,還有就是程式碼比接下來要說的SBT

操作都差不多。。。。。

3.SizeBalanced Tree

3.1基本思想

Treap差不多,都是想辦法讓樹平衡,不過Treap額外維護了一個沒用的fix,並且還要靠RP,萬一臨考前RP不好就球了

SBT的核心思想就是Maintain函式,它不借助額外的資訊,我們說樹不平衡就是說某個子樹的節點數(size)多了,所以我們就可以藉助這個size來使這個樹左右平衡!

3.2儲存結構

可以開結構體,但是寫出來程式碼很長(後面題目裡面有一份是結構體的,很蛋疼。。。。)

所以還是開陣列方便些

int S[N],L[N],R[N],A[N];//分別表示size,左兒子,右兒子,和當前key


3.3Maintain函式

為了描述方便用S[]表示sizeL[]表示左子樹,R[]表示右子樹

Maintain分兩類

  1. 調整右子樹,就比較S[R[R[p]]]S[L[p]],如果前者大,就把p左旋,樹就平衡了;否則就再比較S[R[L[p]]]S[L[p]],如果前者大,就先把R[p]右旋,再把p左旋,整棵樹就平衡了;否則return

  2. 調整左子樹就與上面完全相反

通過上面的分析我們可以得知,核心思想就是把Size大的儘量向上走

void maintain(int &p,bool flag) //flag為true表示調整右子樹
{  
	if(flag)  
		{  
		if(S[R[R[p]]]>S[L[p]]) rotate_left(p);  
		else if(S[R[L[p]]]>S[L[p]])  
		{  
			rotate_right(R[p]);  
			rotate_left(p);  
		}  
		else return;  
	}  
	else{  
		if(S[L[L[p]]]>S[R[p]]) rotate_right(p);  
		else if(S[L[R[p]]]>S[R[p]])  
		{  
			rotate_left(L[p]);  
			rotate_right(p);  
		}  
		else return;  
	}  
	maintain(L[p],0);  
	maintain(R[p],1);  
	maintain(p,1);  
	maintain(p,0);  
}


核心模組完了其他就好寫了

3.4左旋右旋

void rotate_left(int &x)  
{  
	int y=R[x];  
	R[x]=L[y];  
	L[y]=x;  
	S[y]=S[x];  
	S[x]=S[L[x]]+S[R[x]]+1;  
	x=y;  
}  

void rotate_right(int &x)  
{  
	int y=L[x];  
	L[x]=R[y];  
	R[y]=x;  
	S[y]=S[x];  
	S[x]=S[L[x]]+S[R[x]]+1;  
	x=y;  
}  

3.5插入

void insert(int &p,int k)  
{  
	if(!p) {p=++total;init(p,k);}  
	else{  
		S[p]++;  
		if(k<A[p]) insert(L[p],k);  
		else insert(R[p],k);  
		maintain(p,k>=A[p]);  
	}  
}  


3.6K大(整個區間)

int kth(int &p,int k)  
{  
	int tmp=S[L[p]]+1;  
	if(k==tmp) return A[p];  
	else if(k<tmp) return kth(L[p],k);  
	else return kth(R[p],k-tmp);  
}  


3.7找最值

顯然,最值肯定出現在整棵樹最左(右)

int getmin()  
{  
	int x;  
	for(x=root ;L[x];x= L[x]);  
	return A[x];  
} 
int getmax()  
{  
	int x;  
	for(x=root ;R[x];x=R[x]);  
	return A[x];  
}  


3.8刪除

刪除操作要是題目要求而寫的很不同,所以就不寫了

差不多就這些了。。。

目前只做過一道SBT題目(儲存方式結構體和陣列都有)