天天看點

splay詳解(pascal&C++版)

        這是我的第一篇博文,由于被splay坑得太慘,是以毅然決定以此開博。         蜘蛛快來:伸展樹 解釋splay的文章滿大街都是,但用pascal的畢竟少,是以這是用pascal代碼來解釋的(C++代碼在最後)         知道BST的請自動跳到第6段         要學splay,首先要知道BST(二叉排序樹)的概念  它或是一棵空樹;或者是具有下列性質的二叉樹: (1)若左子樹不空,則左子樹上所有結點的值均小于它的根結點的值; (2)若右子樹不空,則右子樹上所有結點的值均大于它的根結點的值; (3)左、右子樹也分别為二叉排序樹;

splay詳解(pascal&C++版)

BST可以簡單地用遞歸實作,下面是插入節點的操作:

procedure ins(p,k:longint);
begin
 if p<a[k] then
    if next[k].l=0 then
      begin
        inc(tot);
        next[k].l:=tot;
        a[tot]:=p;
      end else
        ins(p,next[k].l)else
        if next[k].r=0 then
        begin
          inc(tot);
          next[k].r:=tot;
          a[tot]:=p;
       end else
         ins(p,next[k].r);
end;
           

        不難看出,裸的BST很容易被卡,雖然期望複雜度是O(log n),但對付退化成一條鍊的資料就變成O(n).         是以,平衡樹(BBST)應運而生 BBST有treap、splay、AVL、RBT、SBT等等         這裡隻講splay。伸展樹不像AVL,它不保證嚴格的平衡,但是程式設計複雜度大大降低,效率有點似乎萎。但功能很強大,幾乎能實作其他平衡樹的一切功能,是成本效益很高的東西。 基本概念1:旋轉 ①ZIG與ZAG

splay詳解(pascal&amp;C++版)

       若B是根節點左節點,則對其ZIG,把B拎起來,拉到根的位置,讓A變成B的孩子,發現B有3個孩子了,就把E作為A的左兒子

splay詳解(pascal&amp;C++版)

      顯然,這樣不會違反BST的性質,ZAG就是ZIG的反演。  ②Zig-Zig與Zag-Zag           若節點x的父節點y不是根節點,且x與y同時是各自父節點的左孩子,則進行ZIG-ZIG           若都是右孩子,則進行ZAG-ZAG           Zig-Zig:先Zig【y】節點,再Zig【x】節點

splay詳解(pascal&amp;C++版)

         Zag-Zag:先Zag【y】節點,再Zag【x】節點         建議讀者自己畫一畫,了解一下。 ③Zig-Zag 與Zag-Zig         若節點x的父節點y不是根節點,x與y中一個是其父節點的左孩子而另一個是其父節點的右孩子,則進行此操作。         這裡的Zig和Zag與Zig-Zig及Zag-Zag裡的不同,這裡的旋轉都是對【x】節點進行的。

splay詳解(pascal&amp;C++版)

       有人讀到這裡可能會問,為什麼要定義先旋轉y,再旋轉x的雙旋呢?,都隻對x進行旋轉操作不是更好麼?而且都隻對x旋轉對其他節點相對高度改變小,不正符合了splay把常通路節點提上來的初衷麼?我也有這樣的疑問,但是經過無數資料的測試,定義如是雙旋比隻對x旋轉優了50%,這個Tarjan會證,我不會…… 基本概念②:伸展(splay)         這個概念容易了解,就是對每次被查找、插入等操作的節點用上述方法旋轉到根的位置。 代碼:定義 father數組——》存儲該節點的父節點 son數組——》son[x,1]表示x節點的左兒子,son[x,2]表示x節點的右兒子 Data數組——》存儲節點的值 value數組——》存儲該節點的值出現了幾次 count數組——》count[x]表示以x為根的子樹結點數量 其實用記錄類型寫會更漂亮和友善,但是這樣存儲有些地方可以壓縮代碼,雖然大多數人不喜歡,調試也麻煩,然而為了培養讀者自己寫代碼的能力……(好吧其實是我不想改了) Code:旋轉操作

procedure Rotate(x,w:longint);inline;//x是要旋轉的節點,w=1左旋,w=2右旋
var y:longint;
begin
 y:=father[x];
 count[y]:=count[y]-count[x]+count[son[x,w]];
 count[x]:=count[x]-count[son[x,w]]+count[y];
 son[y,3-w]:=son[x,w];//若右旋,将其父節點的左兒子設定為目前節點的右兒子,相反就……
 if son[x,w]<>0 then father[son[x,w]]:=y;//設定目前節點兒子的父節點,相反也是……
 father[x]:=father[y];
 if father[y]<>0 then
   if y=son[father[y],1] then son[father[y],1]:=x else son[father[y],2]:=x;
   //修改x與其旋轉後父節點的關聯
 father[y]:=x;son[x,w]:=y;//設定旋轉後x與y的關系
end;
           

伸展操作

procedure splay(x:longint);inline;//伸展操作無需多解釋,細心即可
var y:longint;
begin
 while father[x]<>0 do
   begin
     y:=father[x];
     if father[y]=0 then
         if x=son[y,1]then rotate(x,2)//ZIG
                      else rotate(x,1)//ZAG
       else
         if y=son[father[y],1] then
           if x=son[y,1]
             then begin rotate(y,2);rotate(x,2)end//ZIG-ZIG
             else begin rotate(x,1);rotate(x,2)end//ZAG-ZIG
         else
           if x=son[y,2]
             then begin rotate(y,1);rotate(x,1)end//ZAG-ZAG
             else begin rotate(x,2);rotate(x,1)end//ZIG-ZAG
   end;
   root:=x;//x成為根
end;
           

查找

function search(x,w:longint):longint;inline;//在以x為根的子樹中,w為要查詢的數,傳回節點編号
begin
 while data[x]<>w do
   begin
         if w=data[x] then exit(x);//找到就退出
     if w<data[x] then//這裡操作與BST一樣
       begin
         if son[x,1]=0 then break;
         x:=son[x,1];
       end else
       begin
         if son[x,2]=0 then break;
         x:=son[x,2];
       end
   end;
 exit(x);
end;
           

插入

procedure insert(w:longint);inline;
var k,kk:longint;flag:boolean;
begin
 if tot=0 then//tot記錄目前樹中的節點總數
   begin
     inc(tot);
     father[1]:=0;count[1]:=1;data[1]:=w;root:=1;value[1]:=1;//root是根的編号
     exit;
   end;
 k:=search(root,w);
 if data[k]=w then//如果該數值已存在于樹中,就隻要……
     begin
      inc(value[k]);kk:=k;
      flag:=true;
     end else
      begin//否則建立節點
        inc(tot);
        data[tot]:=w;father[tot]:=k;count[tot]:=1;value[tot]:=1;
        if data[k]>w then son[k,1]:=tot else son[k,2]:=tot;
        flag:=false;
      end;
 while k<>0 do
 begin
   inc(count[k]);//更新count值,自己yy一下
   k:=father[k];
 end;
 if flag then splay(kk)else splay(tot);//flag決定伸展哪個節點
end;
           

求極值(類似于查找,自己yy即可)

function Extreme(x,w:longint):longint;inline;//x是要通路子樹的根,w=1求max,w=2求min
const lala:array[1..2]of longint=(maxlongint,-maxlongint);
var k:longint;
begin
 k:=search(x,lala[w]);
 Extreme:=data[k];
 splay(k);
end;
           

删除(核心思想即伸展欲删節點,合并左右子樹)

procedure delete(x:longint);inline;//x是要删除的【數值】
var k,y:longint;
begin
 k:=search(root,x);
 if data[k]<>x then splay(k)//如果此數不在樹中,伸展k

 else
 begin
   splay(k);

   if value[k]>1 then begin dec(value[k]);dec(count[k]);end else
   if son[k,1]=0 then
       begin
         y:=son[k,2];
                 son[k,2]:=0;count[k]:=0;data[k]:=0;value[k]:=0;
                 root:=y;father[root]:=0;
       end else
       begin
         father[son[k,1]]:=0;//切斷左子樹與根的關聯
         y:=Extreme(son[k,1],1);//左子樹中的max
         son[root,2]:=son[k,2];//左右子樹合并
         count[root]:=count[root]+count[son[k,2]];
         if son[root,2]<>0 then father[son[root,2]]:=root;
                 data[k]:=0;son[k,1]:=0;son[k,2]:=0;value[k]:=0;
       end
 end
end;//有些賦為0的操作其實可以省略
           

求前驅後繼

function pred(x:longint):longint;inline;//求前驅
var k:longint;
begin
 k:=search(root,x);splay(k);
 if data[k]<x then exit(data[k]);
 exit(Extreme(son[k,1],1));
end;
function succ(x:longint):longint;inline;//求後繼
var k:longint;
begin
 k:=search(root,x);splay(k);
 if data[k]>x then exit(data[k]);
 exit(Extreme(son[k,2],2));
end;
           

求第k極值

function kth(x,w:longint):longint;inline;//w=1為求第x小值,w=2為求第x大值
var i:longint;
begin
 i:=root;
 while not((x>=count[son[i,w]]+1)and(x<=count[son[i,w]]+value[i]))and (i<>0)do
   begin
     if x>count[son[i,w]]+value[i] then
       begin
         x:=x-count[son[i,w]]-value[i];
         i:=son[i,3-w];
       end
       else i:=son[i,w];
   end;
   kth:=i;
   splay(i);
end;
           

求x是第幾大

function findnum(x:longint):longint;inline;
var K:longint;
begin
 k:=search(root,x);splay(k);
 root:=k;
 exit(count[son[k,1]]+1);
end;
           

我能想到的基本操作大概就這些,最後推薦一道模闆題 in wikioi in BZOJ in tyvj 這題的code就是把上面的過程函數拼起來就行。 然後下面是C++的完整代碼

#include<cstdio>
#include<iostream>
#include <cstdlib>

using namespace std;

int n,root,i,tot,opt,x;
int father[100000],count[100000],data[100000],value[100000];
int son[100000][3];

inline void Rotate(int x,int w)
{
	int y;
	y=father[x];
	count[y]=count[y]-count[x]+count[son[x][w]];
	count[x]=count[x]-count[son[x][w]]+count[y];
	son[y][3-w]=son[x][w];
	if (son[x][w]) father[son[x][w]]=y;
	father[x]=father[y];
	if (father[y])
		if (y==son[father[y]][1]) son[father[y]][1]=x;
			else son[father[y]][2]=x;
	father[y]=x;
	son[x][w]=y;
}

inline void splay(int x)
{
	int y;
	while (father[x])
	{
		y=father[x];
		if (!father[y])
			if (x==son[y][1]) Rotate(x,2);
			else Rotate(x,1);
		else
			if (y==son[father[y]][1])
				if (x==son[y][1])
				{
					Rotate(y,2);Rotate(x,2);
				}
				else
				{
					Rotate(x,1);Rotate(x,2);
				}
				else
				if (x==son[y][2])
				{
					Rotate(y,1);Rotate(x,1);
				}
				else
				{
					Rotate(x,2);Rotate(x,1);
				}
	}
	root=x;
}

inline int search(int x,int w)
{
	while (data[x]!=w)
	{
		if (w==data[x]) return x;
		if (w<data[x])
		{
			if (!son[x][1]) break;
			x=son[x][1];
		}
		else
		{
			if (son[x][2]==0) break;
			x=son[x][2];
		}
	}
	return x;
}

inline void insert(int w)
{
	int k,kk;bool flag;
	if (!tot)
	{
		tot=1;
		father[1]=0;count[1]=1;data[1]=w;root=1;value[1]=1;
		return;
	}
	k=search(root,w);
	if (data[k]==w)
	{
		value[k]++;kk=k;
		flag=true;
	}
	else
	{
		tot++;
		data[tot]=w;
		father[tot]=k;
		count[tot]=1;
		value[tot]=1;
		if (data[k]>w) son[k][1]=tot;else son[k][2]=tot;
		flag=0;
	}
	while (k)
	{
		count[k]++;
		k=father[k];
	}
	if (flag) splay(kk);else splay(tot);
}

inline int Extreme(int x,int w)
{
	const int lala[3]={0,2147483647,-2147483647};
	int k,tmp;
	k=search(x,lala[w]);
	tmp=data[k];
	splay(k);
	return tmp;
}

inline void del(int x)
{
	int k,y;
	k=search(root,x);
	if (data[k]!=x) splay(k);else
	{
		splay(k);
		if (value[k]>1)
		{
			value[k]--;
			count[k]--;
		}
		else
		if (!son[k][1])
		{
			y=son[k][2];
			son[k][2]=0;
			count[k]=0;
			data[k]=0;
			value[k]=0;
			root=y;
			father[root]=0;
		}
		else
		{
			father[son[k][1]]=0;
			y=Extreme(son[k][1],1);
			son[root][2]=son[k][2];
			count[root]=count[root]+count[son[k][2]];
			if (son[root][2]!=0) father[son[root][2]]=root;
				data[k]=0;son[k][1];son[k][2]=0;
				value[k]=0;
		}
	}
}

inline int pred(int x)
{
	int k;
	k=search(root,x);
	splay(k);
	if (data[k]<x) return data[k];
	return Extreme(son[k][1],1);
}

inline int succ(int x)
{
	int k;
	k=search(root,x);
	splay(k);
	if (data[k]>x) return data[k];
	return Extreme(son[k][2],2);

}

inline int kth(int x,int w)
{
	int i,tmp;
	i=root;
	while (!((x>=count[son[i][w]]+1)&&(x<=count[son[i][w]]+value[i]))&&(i!=0))
	{
		if (x>count[son[i][w]]+value[i])
		{
			x=x-count[son[i][w]]-value[i];
			i=son[i][3-w];
		}
		else
		i=son[i][w];
	}
	tmp=i;
	splay(i);
	return tmp;
}

inline int findnum(int x)
{
	int k;
	k=search(root,x);splay(k);
	root=k;
	return count[son[k][1]]+1;
}

int main()
{
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		if (i==3)
		i=3;
		scanf("%d%d",&opt,&x);
		switch(opt)
		{
			case 1:insert(x);break;
			case 2:del(x);break;
			case 3:printf("%d\n",findnum(x));break;
			case 4:printf("%d\n",data[kth(x,1)]);break;
			case 5:printf("%d\n",pred(x));break;
			case 6:printf("%d\n",succ(x));break;
			default:break;
		}
	}
	return 0;
}
           

~~~~~~~~~~~~~~感謝閱讀~~~~~~~~~~~~~~