日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

Treap 学习笔记

發(fā)布時間:2023/12/24 windows 30 coder
生活随笔 收集整理的這篇文章主要介紹了 Treap 学习笔记 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

二叉查找樹

二叉查找樹是一棵有點權(quán)的二叉樹,具有以下幾個特征:

  • 左孩子的權(quán)值小于父親的權(quán)值
  • 右孩子的權(quán)值大于父親的權(quán)值
  • 中序遍歷及從小到大排序

二叉查找樹支持以下幾個操作:

  • 插入一個數(shù)
  • 刪除一個數(shù)
  • 找一個數(shù)的前驅(qū)
  • 找一個數(shù)的后繼
  • 詢問一個數(shù)的排名
  • 詢問排第幾名的數(shù)

二叉查找樹一棵二叉查找樹,所以在最優(yōu)的情況下單一操作的時間復(fù)雜度應(yīng)該是 \(\text{O}(\log n)\) 級別的。但是在進(jìn)行操作時,如果輸入的點權(quán)單調(diào)遞增或遞減,那么整個數(shù)據(jù)結(jié)構(gòu)就將由樹退化成為鏈。所以單次操作的時間復(fù)雜度最壞為 \(\text{O}(n)\) 級別。

普通平衡樹

為了使這個數(shù)據(jù)結(jié)構(gòu)平衡,平衡樹就應(yīng)運而生了。Treap 就是平衡樹的一種,這個算法就是將樹 (Tree) 與堆 (Heap) 相結(jié)合了起來。Treap 給每一個節(jié)點在維護(hù)原來的數(shù)值的同時,還添加了一個隨機值。但看權(quán)值,這是一顆二叉搜索樹,但是但看隨機值這又是一個堆。

儲存

首先我們應(yīng)該了解一下如何儲存一顆平衡樹。

因為平衡樹的結(jié)構(gòu)是會改變的,所以我們需要儲存每一個節(jié)點的左孩子與右孩子。因為一個節(jié)點可能會多次添加,所以應(yīng)該使用 cnt 記錄以下這個節(jié)點出現(xiàn)的個數(shù)。為了后面的操作,我們應(yīng)該還需要定義一個 size 變量記錄這個節(jié)點及子樹的大小。

所以在我們定義的結(jié)構(gòu)體應(yīng)該是下面這樣的:


struct node{
	int l,r,k,val,cnt,size;
}a[N];

updata

在進(jìn)行修改操作之后,節(jié)點的子樹大小會發(fā)行變化。updata 函數(shù)的功能是更新節(jié)點的 size 值。


void updata(int u){
	a[u].size=a[a[u].l].size+a[a[u].r].size+a[u].cnt;
}

make

在進(jìn)行操作時,為了節(jié)省空間復(fù)雜度,平衡樹使用了動態(tài)開點。動態(tài)開點就是你需要使用一個新節(jié)點時就現(xiàn)馬上申請一個空間,而不是全部預(yù)留好。


int make(int k){
	a[++tot].k=k,a[tot].val=rand(); //tot 記錄節(jié)點個數(shù)
	a[tot].cnt=a[tot].size=1;
	return tot;
}

zig && zag

既然需要再維護(hù)二叉查找樹的同時維護(hù)平衡樹,就需要在不改變平衡樹的性質(zhì)的情況下完成堆所需要的 swap 的操作。所以我們就迎來了平衡樹最重要的操作 zig 與 zag。

這是一棵平衡樹,其中 1 2 3 為節(jié)點 A B C 為子樹。
它們滿足以下性質(zhì):\(1>A>2>C>3>D\)

那么如果需要交換 2 3 的位置,那么在不違背其性質(zhì)的情況下將其改為:

這個過程就是 zig 操作,反之即是 zag 操作。代碼實現(xiàn)就是將將操作進(jìn)行模擬,方法如下:


void zig(int &p){
	int q=a[p].l;
	a[p].l=a[q].r,a[q].r=p,p=q;
	updata(a[p].r),updata(p);
}
void zag(int &p){
	int q=a[p].r;
	a[p].r=a[q].l,a[q].l=p,p=q;
	updata(a[p].l),updata(p);
}

build

因為在平衡樹中有旋轉(zhuǎn)操作,所以根節(jié)點有可能會在旋轉(zhuǎn)操作中改變位置。為了讓根節(jié)點的位置保持不變,可以建立兩個虛點,并令其優(yōu)先級遠(yuǎn)遠(yuǎn)高于其他的點,永遠(yuǎn)停留在根節(jié)點的位置。


void build(){
	make(-INF),make(INF);
	root=1,a[1].r=2,updata(root);
	if(a[1].val<a[2].val) zag(root);
}

insert

在插入操作中,一共有三種操作。反復(fù)執(zhí)行操作三,直至滿足操作一或操作二。

  • 操作一:需要處理的節(jié)點為 \(0\),意味著這個節(jié)點不存在,所以直接新建。

  • 操作二:已經(jīng)找到車要添加的節(jié)點,cnt 加一。

  • 操作三:需要添加的節(jié)點小于或大于這個節(jié)點,那么分別訪問左節(jié)點或右節(jié)點。


void insert(int &p,int k){
	if(p==0) p=make(k);
	else{
		if(a[p].k==k) a[p].cnt++;
		if(a[p].k>k){
			insert(a[p].l,k);
			if(a[a[p].l].val>a[p].val) zig(p);
		}if(a[p].k<k){
			insert(a[p].r,k);
			if(a[a[p].r].val>a[p].val) zag(p);
		}
	}updata(p);
}

del

在刪除操作中,同樣分為三種操作:

  • 操作一:沒有找到這個點就直接返回,不進(jìn)行修改操作。

  • 操作二:如果這個節(jié)點的值大于或者小于要刪除的值,那么就繼續(xù)訪問左孩子或者右孩子。

  • 操作三:找到了這個值,如果 cnt 大于 \(1\),那么直接 cnt-- 否則尋找比這個節(jié)點大的集合中的最小值。


void del(int &p,int k){
	if(p==0) return ;
	if(a[p].k==k){
		if(a[p].cnt>1){
			a[p].cnt--;
			updata(p);
			return;
		}if(a[p].l||a[p].r){
			if(!a[p].r||a[a[p].l].val) zig(p),del(a[p].r,k);
			else zag(p),del(a[p].l,k);
		}else p=0;
		updata(p);
		return;
	}if(a[p].k>k) del(a[p].l,k);
	else del(a[p].r,k);
	updata(p);
}

get_rank

get_rank 函數(shù)可以獲得某個點的排名。在尋找時如果節(jié)點在左子樹,則這個節(jié)點在左子樹的排名就是這個節(jié)點在這棵子樹上的排名。反之,如果這個節(jié)點在右子樹,那么他的排名就是左子樹的大小+根節(jié)點的大小+自己在右子樹的排名。


int get_rank(int p,int k){
	if(p==0) return 0;
	if(a[p].k==k) return a[a[p].l].size+1;
	if(a[p].k>k) return get_rank(a[p].l,k);
	return a[a[p].l].size+a[p].cnt+get_rank(a[p].r,k);
}

因為查詢的數(shù)可能不在樹中存在,所以但是 get_rank 的返回值又是默認(rèn)其存在的,所以將答案設(shè)為了函數(shù)值\(-1\)。為了避免發(fā)生這樣的錯誤,需要在定義一個 find 函數(shù)檢查是否存在這個節(jié)點。


bool find(int p,int x){
	if(a[p].k==x) return 0;
	if(a[p].val==0) return 1;
	if(a[p].k>x) return find(a[p].l,x);
	return find(a[p].r,x);
}

get_key

get_key 函數(shù)可以獲取某個排名的數(shù)。當(dāng)訪問到一個節(jié)點時,如果這個節(jié)點的左子樹的大小大于它的排名,那么這個節(jié)點就應(yīng)該在左子樹。如果這個排名大于這個節(jié)點的大小 + 左子樹的大小,那么這個節(jié)點就應(yīng)該在右子樹。其他的情況就應(yīng)該就在這個節(jié)點。


int get_key(int p,int rank){
	if(p==0) return INF;
	if(a[a[p].l].size>=rank) return get_key(a[p].l,rank);
	if(a[a[p].l].size+a[p].cnt>=rank) return a[p].k;
	return get_key(a[p].r,rank-a[a[p].l].size-a[p].cnt);
}

get_pr

get_pr 函數(shù)可以找到一個數(shù)的前驅(qū),及比他大的數(shù)中最小的一個。因為平衡樹滿足左孩子 \(<\) 根節(jié)點 \(<\) 右孩子,所以只需要先走到左孩子,再一直向右走就可以了。


int get_pr(int p,int k){
	if(p==0) return-INF;
	if(a[p].k>=k) return get_pr(a[p].l,k);
	return max(get_pr(a[p].r,k),a[p].k);
}

get_ne

get_ne 函數(shù)可以找到一個數(shù)的后驅(qū),及比他小的數(shù)中最大的一個。因為平衡樹滿足左孩子 \(<\) 根節(jié)點 \(<\) 右孩子,所以只需要先走到右孩子,再一直向左走就可以了。


int get_ne(int p,int k){
	if(p==0) return INF;
	if(a[p].k<=k) return get_ne(a[p].r,k);
	return min(get_ne(a[p].l,k),a[p].k);
}

P3369 普通平衡樹

這一題就是一道模板題,只需要將前面的操作整合在一起就可以了。


#include<bits/stdc++.h>
using namespace std;
const int N=100010,INF=1e8;
int n;
struct Node{int l,r,k,val,cnt,size;}a[N];
int root,tot;
void updata(int u){a[u].size=a[a[u].l].size+a[a[u].r].size+a[u].cnt;}
int make(int k){
	a[++tot].k=k,a[tot].val=rand();
	a[tot].cnt=a[tot].size=1;
	return tot;
}
void zig(int &p){
	int q=a[p].l;
	a[p].l=a[q].r,a[q].r=p,p=q;
	updata(a[p].r),updata(p);
}
void zag(int &p){
	int q=a[p].r;
	a[p].r=a[q].l,a[q].l=p,p=q;
	updata(a[p].l),updata(p);
}
void build(){
	make(-INF),make(INF);
	root=1,a[1].r=2,updata(root);
	if(a[1].val<a[2].val) zag(root);
}
void insert(int &p,int k){
	if(p==0) p=make(k);
	else{
		if(a[p].k==k) a[p].cnt++;
		if(a[p].k>k){
			insert(a[p].l,k);
			if(a[a[p].l].val>a[p].val) zig(p);
		}if(a[p].k<k){
			insert(a[p].r,k);
			if(a[a[p].r].val>a[p].val) zag(p);
		}
	}updata(p);
}
void del(int &p,int k){
	if(p==0) return ;
	if(a[p].k==k){
		if(a[p].cnt>1){
			a[p].cnt--;
			updata(p);
			return;
		}if(a[p].l||a[p].r){
			if(!a[p].r||a[a[p].l].val) zig(p),del(a[p].r,k);
			else zag(p),del(a[p].l,k);
		}else p=0;
		updata(p);
		return;
	}if(a[p].k>k) del(a[p].l,k);
	else del(a[p].r,k);
	updata(p);
}
int get_rank(int p,int k){
	if(p==0) return 0;
	if(a[p].k==k) return a[a[p].l].size+1;
	if(a[p].k>k) return get_rank(a[p].l,k);
	return a[a[p].l].size+a[p].cnt+get_rank(a[p].r,k);
}
int get_key(int p,int rank){
	if(p==0) return INF;
	if(a[a[p].l].size>=rank) return get_key(a[p].l,rank);
	if(a[a[p].l].size+a[p].cnt>=rank) return a[p].k;
	return get_key(a[p].r,rank-a[a[p].l].size-a[p].cnt);
}
int get_pr(int p,int k){
	if(p==0) return-INF;
	if(a[p].k>=k) return get_pr(a[p].l,k);
	return max(get_pr(a[p].r,k),a[p].k);
}
int get_ne(int p,int k){
	if(p==0) return INF;
	if(a[p].k<=k) return get_ne(a[p].r,k);
	return min(get_ne(a[p].l,k),a[p].k);
}
bool find(int p,int x){
	if(a[p].k==x) return 0;
	if(a[p].val==0) return 1;
	if(a[p].k>x) return find(a[p].l,x);
	return find(a[p].r,x);
}
int main(){
	build();
	cin>>n;
	for(int i=1,op,x;i<=n;i++){
		cin>>op>>x;
		if(op==1) insert(root,x);
		if(op==2) del(root,x);
		if(op==3) cout<<get_rank(root,x)+find(root,x)-1;
		if(op==4) cout<<get_key(root,x+1);
		if(op==5) cout<<get_pr(root,x);
		if(op==6) cout<<get_ne(root,x);
		if(op!=1&&op!=2)cout<<endl;
	}return 0;
}

總結(jié)

以上是生活随笔為你收集整理的Treap 学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。