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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[学习笔记] 伸展树splay详解+全套模板+例题[Luogu P3369 【模板】普通平衡树]

發(fā)布時間:2023/12/3 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [学习笔记] 伸展树splay详解+全套模板+例题[Luogu P3369 【模板】普通平衡树] 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 引入概念
  • 全套模板
    • 變量聲明
    • update
    • ==rotate旋轉(zhuǎn)==
    • splay操作
    • insert插入
    • delete刪除
    • 查找x的位置
    • 查找第k大
    • 前驅(qū)/后繼
  • 極小值-inf和極大值inf的作用
  • 例題:P3369 【模板】普通平衡樹
    • 題目
    • code

聲明一下,許多代碼的注解都在模板代碼里面寫了的,所以正文可能不會很多
其次是splaysplaysplay很多操作treaptreaptreap我都已經(jīng)詳解過了,只需要掌握不一樣的旋轉(zhuǎn)板塊即可

引入概念

在這之前大家要了解二叉搜索樹或者treap再或者非旋treap,也可以不了解,我會再次盡全力詳細的給大家講懵splay

二叉搜索樹:是一種數(shù)據(jù)結(jié)構(gòu),每個點都存有各自的鍵值,按中序遍歷這棵樹,按鍵值生成的序列是有序的

顯而易見對于給定的序列nnn,它的二叉搜索樹不是唯一的
煮個栗子:123451\ 2\ 3\ 4\ 51?2?3?4?5,就能畫出很多不一樣的二叉搜索樹


伸展樹(Splay Tree),也叫分裂樹,是一種二叉排序樹,它能在O(logn)O(logn)O(logn)內(nèi)完成插入、查找和刪除操作
它由丹尼爾·斯立特Daniel Sleator 和 羅伯特·恩卓·塔揚Robert Endre Tarjan在1985年發(fā)明的

在伸展樹上的一般操作都基于伸展操作:
假設(shè)想要對一個二叉查找樹執(zhí)行一系列的查找操作,為了使整個查找時間更小,被查頻率高的那些條目就應(yīng)當(dāng)經(jīng)常處于靠近樹根的位置
于是想到設(shè)計一個簡單方法:
在每次查找之后對樹進行重構(gòu),把被查找的條目搬移到離樹根近一些的地方
伸展樹應(yīng)運而生:
伸展樹是一種自調(diào)整形式的二叉查找樹,它會沿著從某個節(jié)點到樹根之間的路徑,通過一系列的旋轉(zhuǎn)把這個節(jié)點搬移到樹根去
它的優(yōu)勢在于不需要記錄用于平衡樹的冗余信息

假設(shè)想要對一個二叉查找樹執(zhí)行一系列的查找操作
為了使整個查找時間更小,被查頻率高的那些條目就應(yīng)當(dāng)經(jīng)常處于靠近樹根的位置
于是想到設(shè)計一個簡單方法:
在每次查找之后對樹進行重構(gòu),把被查找的條目搬移到離樹根近一些的地方
splay tree應(yīng)運而生。splay tree是一種自調(diào)整形式的二叉查找樹,它會沿著從某個節(jié)點到樹根之間的路徑,通過一系列的旋轉(zhuǎn)把這個節(jié)點搬移到樹根去

———————百度百科老師親身授課,講懵一群中華少年

這張圖太好看了,忍不住盜過來


重點的就是模板,模板的原理會在該模板板塊介紹,不要慌~~

全套模板


變量聲明

我用的是結(jié)構(gòu)體treetreetree,方便學(xué)習(xí)LCTLCTLCT(暴露了) 后面的封裝
tree[i].valtree[i].valtree[i].val:表示該點的值
tree[i].cnttree[i].cnttree[i].cnt:表示該點在樹上的出現(xiàn)次數(shù)
tree[i].siztree[i].siztree[i].siz:表示該點的子樹大小,包括自己在內(nèi)
tree[i].ftree[i].ftree[i].f:表示該點的爸爸(誒真乖)
tree[i].son[2]tree[i].son[2]tree[i].son[2]:表示該點的兩個兒子:son[0]son[0]son[0]左兒子,son[1]son[1]son[1]右兒子


這個沒有什么值得講的,不同的題肯定會有添加或更改,比如最大值就應(yīng)該寫成
tree[x].maxx=max(tree[tree[x].son[0]].maxx,tree[tree[x].son[1]].maxx,tree[x].val)tree[x].maxx = max(tree[tree[x].son[0]].maxx,tree[tree[x].son[1]].maxx,tree[x].val)tree[x].maxx=max(tree[tree[x].son[0]].maxx,tree[tree[x].son[1]].maxx,tree[x].val)
這里以求和為例

update

void update ( int x ) {tree[x].siz = tree[tree[x].son[0]].siz + tree[tree[x].son[1]].siz + tree[x].cnt; }

rotate旋轉(zhuǎn)

treaptreaptreap期間我們了解了單旋轉(zhuǎn)(只旋一次),但是splaysplaysplay則是用雙旋
接著因為是二叉樹,雙旋就分為了兩種情況,直線型旋轉(zhuǎn)和折線型旋轉(zhuǎn)


直線型旋轉(zhuǎn),即三點成一條直線

這種情況的旋轉(zhuǎn)規(guī)則:先旋轉(zhuǎn)父親,再旋轉(zhuǎn)自己


折線型旋轉(zhuǎn)

這種情況的旋轉(zhuǎn)規(guī)則:旋轉(zhuǎn)完自己,再旋轉(zhuǎn)自己(自轉(zhuǎn)兩次)


總結(jié)一張圖:

void rotate ( int x ) {//x是要旋轉(zhuǎn)的點 int fa = tree[x].f;//x的父親(father縮寫) int Gfa = tree[fa].f;//x的祖父/fa的父親(grandfather縮寫(*^__^*))int k = ( tree[fa].son[1] == x );//x是fa的哪一個兒子 0左兒子 1右兒子if( Gfa) tree[Gfa].son[tree[Gfa].son[1] == fa] = x;//兒子非要當(dāng)?shù)?取代了爹原來在祖父下的位置tree[x].f = Gfa; tree[fa].son[k] = tree[x].son[k ^ 1];if( tree[x].son[k ^ 1] ) tree[tree[x].son[k ^ 1]].f = fa;tree[x].son[k ^ 1] = fa;tree[fa].f = x;update ( fa );//別忘了更新信息update ( x ); }//0^1=1 1^1=0 其實也可以用取反(!)代替

splay操作

我們使用雙旋的做法,因為如果單旋將xxx旋到想要的位置,毒瘤會卡到我們n2n^2n2
那么如果想旋轉(zhuǎn)到根的話,可以給第二個參數(shù)傳0

void splay ( int x, int goal ) {//將x旋轉(zhuǎn)到goal的兒子 如果goal是0意味著將x轉(zhuǎn)到根while ( tree[x].f != goal ) {int fa = tree[x].f, Gfa = tree[fa].f;if ( Gfa != goal )//如果fa不是根節(jié)點就是兩類(直線 折線)旋轉(zhuǎn)( ( tree[Gfa].son[0] == fa ) ^ ( tree[fa].son[0] == x ) ) ? rotate ( x ) : rotate ( fa );//有點技巧但也很好理解 前兩坨^=0就是直線的意思rotate ( x );}if ( ! goal )root = x;//如果goal是0 將根節(jié)點更新為x }

insert插入

先用個動圖直觀感受一下

treaptreaptreap是孿生兄弟,從根開始,根據(jù)值的大小比較判斷是往左走(x<tree[root].valx<tree[root].valx<tree[root].val)還是往右走(x>tree[root].valx>tree[root].valx>tree[root].val)

void insert ( int x ) {int u = root, fa = 0;//當(dāng)前位置u及u的父節(jié)點fwhile ( u && tree[u].val != x ) {//仍有點且并未移動到想要的值 fa = u;u = tree[u].son[x > tree[u].val];//x大于當(dāng)前點的值就在右兒子里面找 否則向左找 }if ( u ) //已經(jīng)建過x這個值的位置了 tree[u].cnt ++;else {u = ++ Size;//新節(jié)點的位置 if ( fa ) tree[fa].son[x > tree[fa].val] = u;tree[u].son[0] = tree[u].son[1] = 0;//新點目前肯定沒有兒子tree[u].val = x;tree[u].f = fa;tree[u].cnt = tree[u].siz = 1;}splay ( u, 0 );//把當(dāng)前位置移到根保證結(jié)構(gòu)平衡 因為前面更改了子樹大小必須splay去update保證siz的正確 }

delete刪除

思路是首先分別找到xxx的前驅(qū)p1p1p1和后繼p2p2p2,那么在當(dāng)前樹上就滿足p1<x<p2p1<x<p2p1<x<p2并且中間沒有其它數(shù)
很妙的就是我們把p1p1p1旋轉(zhuǎn)到根,此時所有值比p1p1p1的都在右子樹,然后把p2p2p2旋轉(zhuǎn)到p1p1p1的兒子處,此時p2p2p2的左兒子就是xxx且只有一個,因為p2p2p2的左子樹要滿足>p1>p1>p1<p2<p2<p2,顯而易見因為定義這里面只能插xxx,那么直接對p2p2p2的左子樹進行操作即可

void Delete ( int x ) {int pre = PreSuf ( x, 0 ), suf = PreSuf ( x, 1 );splay ( pre, 0 );splay ( suf, pre );//pre有可能為0 但這個時候suf就應(yīng)該旋轉(zhuǎn)到根int u = tree[suf].son[0];if ( tree[u].cnt > 1 ) {tree[u].cnt --;splay ( u, 0 );}elsetree[suf].son[0] = 0, splay( suf, 0 ); }

查找x的位置

我相信給個圖,大家就懂了


與插入是一個意思,此處就不過多解釋

void find ( int x ) {//查找x的位置并旋轉(zhuǎn)到根節(jié)點 if ( ! root ) //樹是空的return;int u = root;while ( tree[u].son[x > tree[u].val] && x != tree[u].val )//存在兒子并且當(dāng)前點不是我們要找的 u = tree[u].son[x > tree[u].val];splay ( u, 0 ); }

查找第k大

不要多說廢話了,不理解可以移步上面的treaptreaptreap講解

int findkth ( int x ) {if ( tree[root].siz < x )//整棵樹的大小都沒有k即不存在 return -1;int u = root;while ( 1 ) {if ( x <= tree[tree[u].son[0]].siz )u = tree[u].son[0];else if ( x <= tree[u].cnt + tree[tree[u].son[0]].siz )return u;else {x -= ( tree[tree[u].son[0]].siz + tree[u].cnt );u = tree[u].son[1];}} }

前驅(qū)/后繼

前驅(qū)后繼的思路很妙,我們以前驅(qū)為例,把xxx旋到根,那么左子樹就是比xxx小的,然后就在左兒子里面一直往右兒子走,likethis↓like\ this↓like?this

但是如果樹上沒有我們要找的xxx,怎么辦呢,這個時候的樹根究竟是什么,根據(jù)我們findfindfind的原理寫法,可以知道我們一定是找的最接近于xxx的值,不是它的前驅(qū)就是它的后繼,那么這個時候根就有可能是答案
我們就在findfindfind后加入兩個特判

int PreSuf ( int x, int f ) {//f=0找前驅(qū) f=1找后繼 find ( x );//查找后因為splay此時樹根就是要查詢節(jié)點if ( tree[root].val > x && f )//如果當(dāng)前節(jié)點的值大于x并且要查找的是后繼因為find原因可以直接返回了 return root;if ( tree[root].val < x && ! f )//與找后繼同理 return root;int u = tree[root].son[f];if ( ! u )return 0;while ( tree[u].son[f ^ 1] )u = tree[u].son[f ^ 1];return u; }

極小值-inf和極大值inf的作用

在沒看到這個之前,如果你就拿著模板跑了,恭喜你流失了一天甚至更多的青春
因為上述模板都是在插入了哨兵的前提下才能運行的接下來讓本蒟蒻來給你錯誤的講講哨兵的優(yōu)秀

如果有哨兵存在,那么這些點永遠都不會是死在最前面或者死的時候墊在最下面,就幫助我們少考慮很多邊界,我昨天沒有加哨兵,不停地補刀做手術(shù),還是千瘡百孔,病很多都是并發(fā)癥,醫(yī)不過來,加了個哨兵,自己就好了

最后簡單提一下封裝的好處,顯然就是整個在一坨,方便整體移動和調(diào)試。代碼分層也很清晰。可以用結(jié)構(gòu)體

struct node {里面放所有splay的操作 }T; 調(diào)用函數(shù)需要寫成 T.insert() 之類的

還可以

namespace splay {里面放所有splay操作 } 調(diào)用函數(shù)需要寫成 splay :: insert() 之類的

通常是題目解法涉及到多種算法時,常按算法將各自模板進行封裝。這樣你可以很清楚地知道某個函數(shù)是屬于哪一層的算法。

例題:P3369 【模板】普通平衡樹

題目

送你離開千里之外

code

#include <cstdio> #define maxn 100005 #define INF 0x7f7f7f7f struct node {int f, cnt, val, siz, son[2]; }tree[maxn]; int n, Size, root;void update ( int x ) {tree[x].siz = tree[tree[x].son[0]].siz + tree[tree[x].son[1]].siz + tree[x].cnt; }void rotate ( int x ) { int fa = tree[x].f; int Gfa = tree[fa].f;int k = ( tree[fa].son[1] == x );tree[Gfa].son[tree[Gfa].son[1] == fa] = x;tree[x].f = Gfa; tree[fa].son[k] = tree[x].son[k ^ 1];tree[tree[x].son[k ^ 1]].f = fa;tree[x].son[k ^ 1] = fa;tree[fa].f = x;update ( fa );update ( x ); }void splay ( int x, int goal ) {while ( tree[x].f != goal ) {int fa = tree[x].f, Gfa = tree[fa].f;if ( Gfa != goal )( ( tree[Gfa].son[0] == fa ) ^ ( tree[fa].son[0] == x ) ) ? rotate ( x ) : rotate ( fa );rotate ( x );}if ( ! goal )root = x; }void insert ( int x ) {int u = root, fa = 0;while ( u && tree[u].val != x ) {fa = u;u = tree[u].son[x > tree[u].val]; }if ( u ) tree[u].cnt ++;else {u = ++ Size; if ( fa ) tree[fa].son[x > tree[fa].val] = u;tree[u].son[0] = tree[u].son[1] = 0;tree[u].val = x;tree[u].f = fa;tree[u].cnt = tree[u].siz = 1;}splay ( u, 0 ); }void find ( int x ) {if ( ! root )return;int u = root;while ( tree[u].son[x > tree[u].val] && x != tree[u].val )u = tree[u].son[x > tree[u].val];splay ( u, 0 ); }int PreSuf ( int x, int f ) { find ( x );if ( tree[root].val > x && f )return root;if ( tree[root].val < x && ! f )return root;int u = tree[root].son[f];if ( ! u )return 0;while ( tree[u].son[f ^ 1] )u = tree[u].son[f ^ 1];return u; }void Delete ( int x ) {int pre = PreSuf ( x, 0 ), suf = PreSuf ( x, 1 );splay ( pre, 0 );splay ( suf, pre );int u = tree[suf].son[0];if ( tree[u].cnt > 1 ) {tree[u].cnt --;splay ( u, 0 );}elsetree[suf].son[0] = 0; }int findkth ( int x ) {if ( tree[root].siz < x )return -1;int u = root;while ( 1 ) {if ( x <= tree[tree[u].son[0]].siz )u = tree[u].son[0];else if ( x <= tree[u].cnt + tree[tree[u].son[0]].siz )return u;else {x -= ( tree[tree[u].son[0]].siz + tree[u].cnt );u = tree[u].son[1];}} }int main() {insert ( INF );insert ( -INF );scanf ( "%d", &n );int opt, x;for ( int i = 1;i <= n;i ++ ) {scanf ( "%d %d", &opt, &x );switch ( opt ) {case 1 : insert ( x ); break;case 2 : Delete ( x ); break;case 3 : {find ( x );printf ( "%d\n", tree[tree[root].son[0]].siz );break;}case 4 : {int u = findkth ( x + 1 );printf ( "%d\n", tree[u].val );break;}case 5 : {int u = PreSuf ( x, 0 );printf ( "%d\n", tree[u].val );break;}case 6 : {int u = PreSuf ( x, 1 );printf ( "%d\n", tree[u].val );break;}}}return 0; } #include <cstdio> #define maxn 100005 #define INF 0x7f7f7f7f struct SplayTree {struct node {int f, cnt, val, siz, son[2];void init ( int Val, int fa ) {val = Val;cnt = siz = 1;f = fa;son[0] = son[1] = 0;}}tree[maxn];int root, Size;void update ( int x ) {tree[x].siz = tree[tree[x].son[0]].siz + tree[tree[x].son[1]].siz + tree[x].cnt;}void rotate ( int x ) { int fa = tree[x].f; int Gfa = tree[fa].f;int k = ( tree[fa].son[1] == x );tree[Gfa].son[tree[Gfa].son[1] == fa] = x;tree[x].f = Gfa; tree[fa].son[k] = tree[x].son[k ^ 1];tree[tree[x].son[k ^ 1]].f = fa;tree[x].son[k ^ 1] = fa;tree[fa].f = x;update ( fa );update ( x );}void splay ( int x, int goal ) {while ( tree[x].f != goal ) {int fa = tree[x].f, Gfa = tree[fa].f;if ( Gfa != goal )( ( tree[Gfa].son[0] == fa ) ^ ( tree[fa].son[0] == x ) ) ? rotate ( x ) : rotate ( fa );rotate ( x );}if ( ! goal )root = x;}void insert ( int x ) {int u = root, fa = 0;while ( u && tree[u].val != x ) {fa = u;u = tree[u].son[x > tree[u].val]; }if ( u ) tree[u].cnt ++;else {u = ++ Size; if ( fa ) tree[fa].son[x > tree[fa].val] = u;tree[u].son[0] = tree[u].son[1] = 0;tree[u].val = x;tree[u].f = fa;tree[u].cnt = tree[u].siz = 1;}splay ( u, 0 );}void find ( int x ) {if ( ! root )return;int u = root;while ( tree[u].son[x > tree[u].val] && x != tree[u].val )u = tree[u].son[x > tree[u].val];splay ( u, 0 ); }int PreSuf ( int x, int f ) { find ( x );if ( tree[root].val > x && f )return root;if ( tree[root].val < x && ! f )return root;int u = tree[root].son[f];if ( ! u )return 0;while ( tree[u].son[f ^ 1] )u = tree[u].son[f ^ 1];return u;}void Delete ( int x ) {int pre = PreSuf ( x, 0 ), suf = PreSuf ( x, 1 );splay ( pre, 0 );splay ( suf, pre );int u = tree[suf].son[0];if ( tree[u].cnt > 1 ) {tree[u].cnt --;splay ( u, 0 );}elsetree[suf].son[0] = 0;}int findkth ( int x ) {if ( tree[root].siz < x )return -1;int u = root;while ( 1 ) {if ( x <= tree[tree[u].son[0]].siz )u = tree[u].son[0];else if ( x <= tree[u].cnt + tree[tree[u].son[0]].siz )return u;else {x -= ( tree[tree[u].son[0]].siz + tree[u].cnt );u = tree[u].son[1];}}}}T; int n;int main() {T.insert ( -INF );T.insert ( INF );scanf ( "%d", &n );int opt, x;for ( int i = 1;i <= n;i ++ ) {scanf ( "%d %d", &opt, &x );switch ( opt ) {case 1 : T.insert ( x ); break;case 2 : T.Delete ( x ); break;case 3 : {T.find ( x );printf ( "%d\n", T.tree[T.tree[T.root].son[0]].siz );break;}case 4 : {int u = T.findkth ( x + 1 );printf ( "%d\n", T.tree[u].val );break;}case 5 : {int u = T.PreSuf ( x, 0 );printf ( "%d\n", T.tree[u].val );break;}case 6 : {int u = T.PreSuf ( x, 1 );printf ( "%d\n", T.tree[u].val );break;}}}return 0; }

總結(jié)

以上是生活随笔為你收集整理的[学习笔记] 伸展树splay详解+全套模板+例题[Luogu P3369 【模板】普通平衡树]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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