普通平衡樹學習筆記之Splay算法

前言

今天不容易有一天的自由學習時間,固然要用來「學習」。在此記錄一下今天學到的最基礎的平衡樹。c++

定義

平衡樹是二叉搜索樹和堆合併構成的數據結構,它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹。
這裏僅僅說明一下平衡樹中的\(Splay\)算法算法

進入正題

平衡樹中有許多種類:紅黑樹、\(AVL\)樹,伸展樹,\(Treap\)等等,可是\(Splay\)算法算是可用性很強的一種了。也就是說比較穩定。數據結構

\(Splay\)算法中,一個到處都要用到的東西就是旋轉,即將當前節點與其前邊一個節點依次旋轉到目標位置。因爲這個樹是一個二叉搜索樹,因此旋轉以後要保證性質不變。咱們就須要找到當前節點的父親和爺爺節點,而後先更新爺爺節點與當前節點之間的關係,而後將父親節點與該節點所屬關係的另外一個子樹連起來,最後再處理一下該節點和父親節點的關係。學習

咱們舉個例子(畢竟不太好理解)咱們設這三個點分別爲\(x,y,z\),從左往右分別是後一個的兒子,咱們先把圖畫一下(\(x\)的子節點我隨便用一堆東西表示):

在這裏\(x\)\(y\)的左節點。那麼咱們下一步就是把\(x\)變爲\(z\)的子節點,也就是把\(y\)換下來。這一步很簡單,變成了下邊這樣:
spa

而後就是關鍵了,由於把\(x\)旋轉上來,\(x\)也是有子節點的,因此咱們須要進一步處理。首先記錄一下\(x\)\(y\)節點的左仍是右兒子,在這個例子裏是左兒子,由於咱們不能破壞這個樹的順序和性質,因此就須要讓\(y\)的右兒子不變且全部知足性質的比\(x\)小的他的左兒子仍是\(x\)的左兒子,而\(y\)的左兒子變成\(x\)的右兒子,也就是下邊這個圖:(剛剛沒有\(y\)的右節點,如今加上,編號與以前的不一樣,本身理解理解\(qwq\)):
code

相似的一個圖,這樣就完成了一次旋轉。最後不樣忘記也是須要\(pushup\)的,來維護點的兒子數量和本身的數量。
下邊放一下\(Splay\)和旋轉的代碼blog

void rotate(int x){//旋轉
	int y=t[x].fa;
	int z=t[y].fa;
	int k=t[y].ch[1]==x;//找到x是y的左仍是右節點,便於進行上文中所說操做
	t[z].ch[t[z].ch[1]==y]=x;//如下都是上邊說過的操做。
	t[x].fa=z;
	t[y].ch[k]=t[x].ch[k^1];
	t[t[x].ch[k^1]].fa=y;
	t[x].ch[k^1]=y;
	t[y].fa=x;
	pushup(y);
	pushup(x);
}
void splay(int x,int goal){//旋轉
	while(t[x].fa!=goal){//父親節點不是目標
		int y=t[x].fa;
		int z=t[y].fa;
		if(z!=goal){//爺爺節點也不是目標
			(t[y].ch[0]==x)^(t[z].ch[0]==x)?rotate(x):rotate(y);//其中一個點的左兒子是x就翻轉x,否則翻轉y(由於樹有序,不能破壞性質)
		}//上邊這句話還須要細細鑽研,本題解之後還會完善
		rotate(x);
	}
	if(goal == 0){//到了根節點的話讓根節點更新
		root = x;
	}
}

一些操做

上邊把\(Splay\)的操做說了一遍,也就是關鍵的旋轉應該比較清晰了,下邊咱們就須要進行一些操做了。
平衡樹有許多能夠進行的操做:刪除,插入,查詢一個值\(x\)的排名,查詢\(x\)排名的值,還有值\(x\)的前驅和後繼。(前驅定義爲小於 \(x\),且最大的數,後繼定義爲大於 \(x\)且最小的數)。這些都須要上邊的旋轉,因此旋轉明白了,這些也就比較容易了。首先是結構體的定義:get

struct Node
{
       int ch[2];//子節點
       int fa;//父節點 
       int cnt;//數量
       int val;//值 
       int son;//兒子數量  
}t[maxn];

\(1\)、插入

咱們確定要從根節點開始向下找到符合這個值的點,或者新建一個點,那麼咱們首先另\(u\)爲根節點,其父親爲\(0\),而後若是有根且插入的值不是當前節點的值,那麼咱們就須要向子節點擴展,這裏個人擴展比較巧妙,由於兒子只有左右分別用\(0\)\(1\)表示,因此直接用判斷來找究竟是左仍是右,也就是這樣的式子:\(x>t[u].val\)。若是大於的話,就是右兒子。不然就是左兒子。因此直接更新到當前節點的兒子節點。
當跳出向下擴展的循環,就說明當前點值就是要插入的值,咱們只須要將大小加一就行。若是沒有這樣的節點,那麼就新建一個節點,而後將父親的兒子節點置爲當前節點,而左右兒子的判斷如上文所說。其他的東西都是初始化,具體看代碼,最後不要忘了再將當前節點\(Splay\)到根(幾乎每種操做都須要\(Splay\),查詢前驅後繼和排名的值不用):博客

void Add(int x){
	int u=root,fa=0;
	while(u&&t[u].val!=x){
		fa=u;
		u=t[u].ch[x>t[u].val];
	}
	if(u){//有的話直接大小加一
		t[u].cnt++;
	}
	else{//沒有該值的節點
		u=++tot;
		if(fa)t[fa].ch[x>t[fa].val]=u;
		t[tot].ch[1]=0;
		t[tot].ch[0]=0;
		t[tot].val=x;
		t[tot].fa=fa;
		t[tot].cnt=1;
		t[tot].son=1;
	}
	splay(u,0);
}

\(2\)、查找值爲\(x\)的排名:

根據這個判斷\(x>t[u].val\)依次找\(x\)的位置,最後\(Splay\)一下就行了:it

void Find(int x){
	int u=root;//從根開始
	if(!u)return;//沒有樹就直接跳出
	while(t[u].ch[x>t[u].val] && x!=t[u].val){//依次向下找到當前值的點
		u=t[u].ch[x>t[u].val];//更新
	}
	splay(u,0);//旋轉
}

這個到最後把這個位置\(Splay\)到了根,因此答案就是當前\(Find\)以後根的左兒子的兒子數。

\(3\)、求前驅後繼(這個操做求出來的是節點編號)

咱們首先須要找到排名,也就是操做\(2\),而後\(Splay\)到根節點,若是值大於當前值且查找的是後繼或者小於當前且找前驅就直接返回,不然就向子節點轉移。找到轉移後最接近當前值的點,也就是說,若是第一次不知足,假如找前驅就向左走一個,而後找到左兒子的右節點的最下邊的點,也就是最接近這個查找的值的點。

int Fr_last(int x,int flag){//前驅flag爲0,後繼爲1
	Find(x);//找到位置
	int u=root;//根開始
	if((t[u].val>x&&flag) || (t[u].val<x && !flag)){//當前點知足就直接返回
		return u;
	}
	u=t[u].ch[flag];//向目標點(左或右)轉移
	while(t[u].ch[flag^1])u=t[u].ch[flag^1];//找到轉移後最接近當前值的點
	return u;//返回
}

\(4\)、查找排名爲\(x\)的值

首先從根節點開始,若是一共都沒有\(x\)個數,那麼就直接返回\(0\),否則的話就分別記錄一下當前點的左右節點,而後判斷,若是當前點的子節點樹加上當前點的值的數量小於查找的排名,直接減去而後走到右兒子,否則就走到左兒子就好了。

int Find_thval(int x){
	int u=root;//根開始
	if(t[u].son<x){//若是沒有這麼多,直接返回0
		return 0;
	}
	while(666666){//一直循環
		int y=t[u].ch[0];//記錄左兒子
		if(x>t[y].son+t[u].cnt){//排名大就減去,而後走到右節點
			x-=t[y].son+t[u].cnt;
			u=t[u].ch[1];
		}
		else{
			if(x<=t[y].son){//不然走到左節點
				u=y;
			}
			else return t[u].val;//若是排名比上邊的小,且比左節點的值大,這就是知足的價值,直接返回
		}
	}
}

個人這個代碼有一些等號的取捨不一樣,因此在查找的時候傳遞參數須要加上一。

\(5\)、刪除

咱們須要首先找出這個點的前驅和後繼,而後旋轉下去,要刪除的就是後繼的左兒子,假如這個點的數量大於\(1\),就直接數量減一就行了,而後翻轉到根節點,若是小於等於\(1\),那麼就把這個點變成\(0\),結束!

void Delete(int x){
	int Front=Fr_last(x,0);//前驅
	int Last=Fr_last(x,1);//後繼
	splay(Front,0);//旋轉
	splay(Last,Front);
	int del=t[Last].ch[0];//找到須要刪除的點
	if(t[del].cnt>1){//大於1直接減
		t[del].cnt--;
		splay(del,0);
	}
	else{//不然直接刪除
		t[Last].ch[0]=0;
	}
}

總結

以上就是\(Splay\)的一些實現和操做,之後博客還會進行修改和完善,這些只是暫時自學時的理解,若是有神犇能給蒟蒻一些指導那就更好了。
完結撒花\(qwqq\)
下邊推薦一個板子題普通平衡樹板子

板子題代碼:

細節的註釋上邊都寫過了,祝願你們學習愉快\(qwq\)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5+10;
const int Inf = 2147483647;
struct Node{
	int son,ch[2],fa,cnt,val;
}t[maxn];
int n,tot,root;
void pushup(int x){
	t[x].son = t[t[x].ch[0]].son+t[t[x].ch[1]].son+t[x].cnt;
}
void rotate(int x){
	int y=t[x].fa;
	int z=t[y].fa;
	int k=t[y].ch[1]==x;
	t[z].ch[t[z].ch[1]==y]=x;
	t[x].fa=z;
	t[y].ch[k]=t[x].ch[k^1];
	t[t[x].ch[k^1]].fa=y;
	t[x].ch[k^1]=y;
	t[y].fa=x;
	pushup(y);
	pushup(x);
}
void splay(int x,int goal){
	while(t[x].fa!=goal){
		int y=t[x].fa;
		int z=t[y].fa;
		if(z!=goal){
			(t[y].ch[0]==x)^(t[z].ch[0]==x)?rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(goal == 0){
		root = x;
	}
}
void Find(int x){
	int u=root;
	if(!u)return;
	while(t[u].ch[x>t[u].val] && x!=t[u].val){
		u=t[u].ch[x>t[u].val];
	}
	splay(u,0);
}

void Add(int x){
	int u=root,fa=0;
	while(u&&t[u].val!=x){
		fa=u;
		u=t[u].ch[x>t[u].val];
	}
	if(u){
		t[u].cnt++;
	}
	else{
		u=++tot;
		if(fa)t[fa].ch[x>t[fa].val]=u;
		t[tot].ch[1]=0;
		t[tot].ch[0]=0;
		t[tot].val=x;
		t[tot].fa=fa;
		t[tot].cnt=1;
		t[tot].son=1;
	}
	splay(u,0);
}
int Fr_last(int x,int flag){
	Find(x);
	int u=root;
	if((t[u].val>x&&flag) || (t[u].val<x && !flag)){
		return u;
	}
	u=t[u].ch[flag];
	while(t[u].ch[flag^1])u=t[u].ch[flag^1];
	return u;
}
void Delete(int x){
	int Front=Fr_last(x,0);
	int Last=Fr_last(x,1);
	splay(Front,0);
	splay(Last,Front);
	int del=t[Last].ch[0];
	if(t[del].cnt>1){
		t[del].cnt--;
		splay(del,0);
	}
	else{
		t[Last].ch[0]=0;
	}
}
int Find_thval(int x){
	int u=root;
	if(t[u].son<x){
		return 0;
	}
	while(666666){
		int y=t[u].ch[0];
		if(x>t[y].son+t[u].cnt){
			x-=t[y].son+t[u].cnt;
			u=t[u].ch[1];
		}
		else{
			if(x<=t[y].son){
				u=y;
			}
			else return t[u].val;
		}
	}
}
int main(){
	int n;
	Add(Inf);
	Add(-Inf);
	scanf("%d",&n);
	while(n--){
		int opt;
		scanf("%d",&opt);
		int x;
		if(opt == 1){
			scanf("%d",&x);
			Add(x);
		}
		if(opt == 2){
			scanf("%d",&x);
			Delete(x);
		}
		if(opt == 3){
			scanf("%d",&x);
			Find(x);
			int ans = t[t[root].ch[0]].son;
			printf("%d\n",ans);
		}
		if(opt == 4){
			int ans;
			scanf("%d",&x);
			ans = Find_thval(x+1);
			printf("%d\n",ans);
		}
		if(opt == 5){
			scanf("%d",&x);
			int ans = Fr_last(x,0);
			printf("%d\n",t[ans].val);
		}
		if(opt == 6){
			scanf("%d",&x);
			int ans = Fr_last(x,1);
			printf("%d\n",t[ans].val);
		}
	}
	return 0;
}
相關文章
相關標籤/搜索