1. 程式人生 > 實用技巧 >平衡樹之Treap

平衡樹之Treap

二叉搜尋樹

性質:一個節點x左子樹所有點的關鍵字都比x的關鍵字小,右子樹所有點的關鍵字都比x的關鍵字大

treap

“樹堆” “Tree+Heap”

性質:每個點隨機分配一個權值,使treap同時滿足堆性質和二叉搜尋樹性質

複雜度:期望O(logn)

設每個節點的關鍵字是key,隨機權值是rand

如果v是u的左兒子,則key[v]<key[u]

如果v是u的右兒子,則key[v]>key[u]

如果v是u的子節點,則rand[u]>rand[v]

treap維護權值的時候一般會把相同的權值放在同一個節點上

所以一個treap節點需要維護以下資訊:

1.左右兒子

2.關鍵字

3.關鍵字出現次數

4.堆隨機值

5.節點大小(即子樹大小)

旋轉

平衡二叉搜尋樹主要通過旋轉來保持樹的平衡,即保證複雜度

旋轉有單旋和雙旋,treap只需要單旋,這一點比較簡單

void lturn(int &k)//treap的旋轉 
{
	int t=tr[k].r;
	tr[k].r=tr[t].l;
	tr[t].l=k;
	tr[t].size=tr[k].size;
	update(k);
	k=t;
}

void rturn(int &k)
{
	int t=tr[k].l;
	tr[k].l=tr[t].r;
	tr[t].r=k;
	tr[t].size=tr[k].size;
	update(k);
	k=t;
}

treap的插入

先給這個節點分配一個隨機的堆權值

然後把這個節點按照BST的規則插入到一個葉子上:

從根節點開始,逐個判斷當前節點的值與插入值的大小關係。如果插入值小於當前節點值,則遞迴至左兒子;大於則遞迴至右兒子;

然後通過旋轉來調整,使得treap滿足堆性質

void insert(int &k,int x)//treap的插入 
{
	if(!k)
	{
		size++;
		k=size;
		tr[k].size=tr[k].w=1;
		tr[k].v=x;
		tr[k].rnd=rand();
		return ;
	}
	tr[k].size++;
	if(tr[k].v==x)
		tr[k].w++;
	else if(x>tr[k].v)
	{
		insert(tr[k].r,x);
		if(tr[tr[k].r].rnd<tr[k].rnd)
			lturn(k);
	}
	else
	{
		insert(tr[k].l,x);
		if(tr[tr[k].l].rnd<tr[k].rnd)
			rturn(k);
	}
}

treap的刪除

和普通的BST刪除一樣:

如果插入值小於當前節點值,則遞迴至左兒子;大於則遞迴至右兒子

若當前節點數值的出現次數大於1,則減一(通常將同一個權值縮掉)

若當前節點數值的出現次數等於1:

若當前節點沒有左兒子與右兒子,則直接刪除該節點(置為0);

若當前節點沒有左兒子或右兒子,則將左兒子或右兒子替代該節點

若當前節點有左兒子與右兒子,則不斷旋轉當前節點,並走到當前節點新的對應位置,直到沒有左兒子和右兒子為止。

void del(int &k,int x)//treap的刪除
{
	if(!k)
		return;
	if(tr[k]==x)
	{
		if(tr[k].w>1)//若不止相同值的個數有多個,刪去一個 
		{
			tr[k].w--;
			tr[k].size--;
			return ;	
		}
		if(tr[k].l*tr[k].r==0)//有一個兒子為空 
			k=tr[k].l+tr[k].r;
		else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)
			rturn(k),del(k,x);
		else
			lturn(k),del(k,x);
	}
	else if(x>tr[k].v)
		tr[k].size--,del(tr[k].r,x);
	else
		tr[k].size--,del(tr[k].l,x);	
} 

treap的查詢

遞迴到葉子節點,一路維護資訊即可

int query_rank(int k,int x)//treap的查詢
{
	if(!k)
		return 0;
	if(tr[k].v==x)	
		return tr[tr[k].l].size+1;
	else if(x>tr[k].v)
		return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r+x);
	else
		return query_rank(tr[k].l,x); 
} 

int query_num(int k,int x)
{
	if(!k)
		return 0;
	if(x<=tr[tr[k].l].size)
		return query_num(tr[k].l,x);
	else if(x>tr[tr[k].l].size+tr[k].w)
		return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
	else
		return tr[k].v;
}

treap還可以支援維護序列時的分裂合併,這裡不詳細講了

#include<iostream>
#include<cstdio>

struct data()
{
	int l;
	int r;
	int v;
	int size;
	int rnd;
	int w;
}tr[100005];

int n,size,root,ans;

void update(int k)//更新節點資訊 
{
	tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}

void lturn(int &k)//treap的旋轉 
{
	int t=tr[k].r;
	tr[k].r=tr[t].l;
	tr[t].l=k;
	tr[t].size=tr[k].size;
	update(k);
	k=t;
}

void rturn(int &k)
{
	int t=tr[k].l;
	tr[k].l=tr[t].r;
	tr[t].r=k;
	tr[t].size=tr[k].size;
	update(k);
	k=t;
}

void insert(int &k,int x)//treap的插入 
{
	if(!k)
	{
		size++;
		k=size;
		tr[k].size=tr[k].w=1;
		tr[k].v=x;
		tr[k].rnd=rand();
		return ;
	}
	tr[k].size++;
	if(tr[k].v==x)
		tr[k].w++;
	else if(x>tr[k].v)
	{
		insert(tr[k].r,x);
		if(tr[tr[k].r].rnd<tr[k].rnd)
			lturn(k);
	}
	else
	{
		insert(tr[k].l,x);
		if(tr[tr[k].l].rnd<tr[k].rnd)
			rturn(k);
	}
}

void del(int &k,int x)//treap的刪除
{
	if(!k)
		return;
	if(tr[k]==x)
	{
		if(tr[k].w>1)//若不止相同值的個數有多個,刪去一個 
		{
			tr[k].w--;
			tr[k].size--;
			return ;	
		}
		if(tr[k].l*tr[k].r==0)//有一個兒子為空 
			k=tr[k].l+tr[k].r;
		else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)
			rturn(k),del(k,x);
		else
			lturn(k),del(k,x);
	}
	else if(x>tr[k].v)
		tr[k].size--,del(tr[k].r,x);
	else
		tr[k].size--,del(tr[k].l,x);	
} 

int query_rank(int k,int x)//treap的查詢
{
	if(!k)
		return 0;
	if(tr[k].v==x)	
		return tr[tr[k].l].size+1;
	else if(x>tr[k].v)
		return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r+x);
	else
		return query_rank(tr[k].l,x); 
} 

int query_num(int k,int x)
{
	if(!k)
		return 0;
	if(x<=tr[tr[k].l].size)
		return query_num(tr[k].l,x);
	else if(x>tr[tr[k].l].size+tr[k].w)
		return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
	else
		return tr[k].v;
}