平衡樹專題Splay

寫在前面: 部分來自孫寶(@Steven24)的博客,表示感謝。

認識

什么是Splay

就是BST的一種,整體效率是很高的,均攤的次數是O(logn)級別的。

基本操作就是把節點旋轉到BST的root,從而改善BST的平衡性,但是很多人會在旋轉中轉暈

建議找個動圖看看,或是上B站找個幾分鐘的視頻看看就理解了。

燒烤

如何可以把一個節點旋轉到BST的root的地方,這是Splay的核心,為了完成這個操作,我們主要是需要分成兩步:

1.每旋轉一次,就使得我們的目標節點x的層次上升一層,最后到達root層

2.在旋轉完后,BST的平衡性被破壞了,這是毋庸置疑的,所以我們需要進行一系列的調整,使這棵BST重新回到平衡狀態

顯然,如果只考慮1,那么使用Treap樹的旋轉法即可,每次x與x的父親交換位置(x上升一層)。可Treap樹的這種“單旋”并不能減少BST的層數。

所以我們就升級一下,再拉來一個更能打的,祖父節點,讓這三代人一起轉。

Splay旋轉

#define left 0,right 1

這個就是LL,RR,LR,RL四個Splay的基本模型

網上有很多

自己看吧

我推薦這個,因為我就是看著他弄懂的基礎知識

Splay四種模型

Splay操作

由于支持單點旋轉改變平衡因子的特性Splay常用于處理區間分裂和合并問題。

例如:一個常見的區間操作,修改或查詢區間[L,R],用Splay樹就很容易實現:先把L-1旋轉到根,然后把節點R+1旋轉到L-1的右子樹上,此時,L+1的左子樹就是區間[L,R]。

1.旋轉:

rotate(x)對x點進行一次旋轉,若x是一個右兒子,左旋,反之亦反之。

Splay Rotate


void rotate(int x)//單旋一次
{int f=t[x].fa;//f:父親int g=t[f].fa;//g:祖父int son=get(x);if(son==1)//x是左兒子,右旋{t[f].rs=t[x].ls;if(t[f].rs){t[t[f].rs].fa=f;}}else//x是右兒子,左旋{t[f].ls=t[x].rs;if(t[f].ls){t[t[f].ls].fa=f;}}t[f].fa=x;//x旋為f的父節點if(son==1)//左旋,f變為x的左兒子{t[x].ls=f;}else//右旋,f變為x的右兒子{t[x].rs=f;}t[x].fa=g;//x現在是祖父的兒子if(g)//更新祖父的兒子{if(t[g].rs==f){t[g].rs=x;}else{t[g].ls=x;}}Update(f);Update(x);
}

Splay(int x,int goal),把節點x旋轉到goal位置。goal=0表示把x旋轉到根,x是新的根。goal≠0表示把x旋轉為goal的兒子。

Splay Rotate Destiny

?void Splay(int x,int goal)
{if(goal==0){root=x;}while(1){int f=t[x].fa;//一次處理x,f,g三個節點int g=t[f].fa;if(f==goal){break;}if(g!=goal)//有祖父,分為一字旋和之字旋兩種情況{if(get(x)==get(f))//一字旋,先旋轉f,g{rotate(f);}else//之字旋,直接旋轉x{rotate(x);}}rotate(x);}Update(x);
} 

2.分裂和合并:

Insert()、Del()函數中包含了分裂與合并,詳情見代碼注釋。利用Splay函數實現分裂與合并,編碼很簡單。

Splay Insert and Delete

?void Insert(int L,int len)//插入一段區間
{int x=kth(root,L);//x為第L個數的位置,y為第L+1個數的位置int y=kth(root,L+1);Splay(x,0);//分裂Splay(y,x);//先把x旋轉到根,然后把y旋轉到x的兒子,且y的兒子為空t[y].ls=build(1,len,y);//合并:建一棵樹,掛到y的左兒子上Update(y);Update(x);
}
void Del(int L,int R)//刪除區間[L+1,R]
{int x=kth(root,L);int y=kth(root,R+1);Splay(x,0);//y是x的右兒子,y的左兒子是待刪除的區間Splay(y,x);t[y].ls=0;//剪短左子樹,等于直接刪除,這里為了簡單,沒有釋放空間Update(y);Update(x);
}

3.查詢:

Splay Find

?int kth(int x) { //查詢排名為x的數 int now = root;while (1) {if (siz[son[now][0]] >= x) now = son[now][0]; //查過頭了else if (siz[son[now][0]] + cnt[now] >= x) return val[now]; //正好查到else {x -= (siz[son[now][0]] + cnt[now]);now = son[now][1]; //沒查夠 } }
}

Splay Rank

?int rank(int x) { //查詢x的排名 int now = root, ans = 0;while (1) {if (!now) return ans + 1; //樹中不存在這個數 那就是目前查到比它小的數的數目+1if (val[now] > x) now = son[now][0]; //要查的數比now節點的數小 說明在左子樹else if (val[now] == x) {ans += siz[son[now][0]]; //比它小的數的數目splay(now);return ans + 1; } else { //要查的數在左子樹 ans += siz[son[now][0]] + cnt[now]; //此時當前節點和左子樹所有數都比它小 now = son[now][1];}} 
}

Splay Find pre/nxt

?int findpre() { //查詢根節點的前驅對應的結點編號 int now = son[root][0]; //首先進入左子樹 因為左子樹的所有數都比它小 while (son[now][1]) now = son[now][1]; //然后一直往大的走 找最大的return now; 
} int findnxt() { //跟上面同理 int now = son[root][1];while (son[now][0]) now = son[now][0];return now;
}···else if (opt == 5) {splay.insert(x); //把x先旋到根節點 而且一定要插入而不是直接旋 防止樹上本來就沒有xprintf("%d\n", splay.val[splay.findpre()]);splay.del(x); //用完刪掉 
} else {splay.insert(x); //同上 printf("%d\n", splay.val[splay.findnxt()]);splay.del(x);
}

注意

(感謝孫寶)

所以 splay 容易寫掛的原因找到了 要是哪個 splay 和 maintain 忘寫了就寄了
那么怎么記住到底哪要寫哪不用寫呢

對于 maintain 很簡單 改了啥就把它自己 maintain 一下 再把父親(如果有)也 maintain 一下
對于 splay 實際上我們使用這個函數的同時如果要實現提根的功能 那就寫
比如 insert 我們查詢前驅后繼需要使用它并且把插入的數旋到根 所以要寫
比如 rank 我們就是要用它把權值為 x 的結點旋到根節點 所以要寫
再比如 del 呃這個你肯定不能忘

如果還記不住怎么辦呢

首先要明確 splay 是復雜度的保證 寫少了復雜度就會假
但是寫幾個肯定是沒事的
maintain 也是 如果該更新的沒更新肯定就寄了
但是把沒必要更新的也更新了問題就不大
所以實在不行這倆就是能寫就寫

當然這么干常數就又會變大 所以最好還是記一下上面那個

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38279.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38279.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38279.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

為適配kubelet:v0.4 安裝指定版本的docker

系統版本信息 cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) 0.4 版本的kubelet 報錯信息記錄 E0603 19:00:38.273720 44142 kubelet.go:734] Error syncing pod: API error (400): {"message": "starting container with non-empty reque…

免交互簡單操作

免交互 交互:我們發出指令控制程序的運行,程序在接收到指令后按照指令的效果作出對應的反應 免交互:間接的,通過第三方的方式把指令傳給程序,不用直接下達指令 Here Document免交互 這是命令行格式,也可…

不用找了!這個軟件自帶各行業話術,客服效率飛躍

有一款客服工具軟件,不但能吸附聊天窗口,實現圖文視頻話術的一鍵發送,還內置了多行業的優質客服話術模板,允許用戶直接下載使用,快速構建起適合自身企業的專業客服知識庫。 前言 在今天的快節奏商業環境中&#xff0c…

Linux shell腳本編程

一、sehll簡介: 用戶通過shell向計算機發送指令的 計算機通過shell給用戶返回指令的執行結果 1.1、通過shell編程可以達到的效果 提高工作的效率 可以實現自動化 1.2、sehll腳本編寫的流程 1、用vi/vim創建一個.sh的文件 2、在文件中進行開發 3、個文件賦予可執行權…

CesiumJS【Basic】- #047 繪制閃爍線(Entity方式)- 需要自定義著色器

文章目錄 繪制閃爍線(Entity方式)- 需要自定義著色器1 目標2 代碼2.1 main.ts繪制閃爍線(Entity方式)- 需要自定義著色器 1 目標 使用Entity方式繪制閃爍線 2 代碼 2.1 main.ts import * as Cesium from cesium;const viewer = new Cesium<

【如何使用RSA簽名驗簽】python語言

文章目錄 簽名方法異步同步通知數據驗簽生活號響應數據驗簽同步響應數據驗簽 &#x1f308;你好呀&#xff01;我是 山頂風景獨好 &#x1f388;歡迎踏入我的博客世界&#xff0c;能與您在此邂逅&#xff0c;真是緣分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的…

作業7.2

用結構體數組以及函數完成: 錄入你要增加的幾個學生&#xff0c;之后輸出所有的學生信息 刪除你要刪除的第幾個學生&#xff0c;并打印所有的學生信息 修改你要修改的第幾個學生&#xff0c;并打印所有的學生信息 查找你要查找的第幾個學生&#xff0c;并打印該的學生信息 1 /*…

idea常用問題記錄

文章目錄 1.ant構建報錯編譯錯誤1.1 解決辦法 1.ant構建報錯編譯錯誤 Compile failed;xxx 1.1 解決辦法

Python系統教程02

鞏固 input()輸出函數 回顧 1 、 input()函數&#xff1a; 在 input()函數輸入時&#xff0c;輸入的內容一定為字符串類型。 2 、條件分支語句&#xff1a; 每一個 if 語句可以看成一個個體&#xff0c;elif 和 else 都是一個 if 個體的一部分&#xff0c;每一個 if 個體 運…

51單片機外部中斷(按鍵識別)

歡迎入群共同學習交流 時間記錄&#xff1a;2024/7/2 一、電路原理圖 51單片機包含INT0、INT1兩個外部中斷接口 二、知識點介紹 1.中斷寄存器位介紹 &#xff08;1&#xff09;TCON定時控制寄存器&#xff0c;位0&#xff08;IT0&#xff09;中斷INT0請求信號選擇位&#x…

WordPress主題開發進群付費主題v1.1.2 多種引流方式

全新前端UI界面&#xff0c;多種前端交互特效讓頁面不再單調&#xff0c;進群頁面群成員數&#xff0c;群成員頭像名稱&#xff0c;每次刷新頁面隨機更新不重復&#xff0c;最下面評論和點贊也是如此隨機刷新不重復 進群頁面簡介&#xff0c;群聊名稱&#xff0c;群內展示&…

注意!年齡越大,社交圈子越窄?其實這是老人的理性選擇!數學家告訴你:何時該跳槽,何時該堅守!你必須知道的三個智慧:讓你的人生更加精彩!

我們到底應該在什么情況下探索新事物&#xff0c;什么情況下專注于已有的東西呢&#xff1f;本質上來說&#xff0c;這個問題就是在詢問&#xff0c;你究竟應該耗費精力去探索新的信息&#xff0c;還是專注從既有的信息中獲取收獲&#xff1f; 有人采訪了臨終的老人&#xff0c…

中國三大平原矢量示意圖分享

我們在《中國地勢三級階梯示意圖分享》、《中國四大高原矢量示意圖分享》和《中國主要山脈矢量示意圖分享》等文中&#xff0c;為你分享過中國地勢相關的矢量示意圖。 現在再為你分享一下我國東北平原、華北平原和長江中下游平原的矢量示意圖&#xff0c;這三大平原均位于我國…

隨想錄總結 Day 77

隨想錄總結 Day 77 回憶75天的做題時間&#xff0c;差點沒堅持下來的有兩個時間點&#xff0c;一個是在前20天&#xff0c;很多時候二叉樹這種基礎題&#xff0c;前中后序列遍歷之類的。基礎&#xff0c;但真正寫一遍&#xff0c;每道題又有多種寫法。花了很長時間但是也就是一…

go sync包(七)Sync.Map

Sync.Map 原理 通過 read 和 dirty 兩個字段實現數據的讀寫分離&#xff0c;讀的數據存在只讀字段 read 上&#xff0c;將最新寫入的數據存在 dirty 字段上。讀取時會先查詢 read&#xff0c;不存在再查詢 dirty&#xff0c;寫入時則只寫入 dirty。讀取 read 并不需要加鎖&am…

每天一個數據分析題(三百九十九)- 邏輯回歸

邏輯回歸中&#xff0c;若選0.5作為閾值區分正負樣本&#xff0c;其決策平面是&#xff08; &#xff09; A. wxb&#xff1d; 0 B. wxb&#xff1d; 1 C. wxb&#xff1d; -1 D. wxb&#xff1d; 2 數據分析認證考試介紹&#xff1a;點擊進入 題目來源于CDA模擬題庫 點…

Python實現萬花筒效果:創造炫目的動態圖案

文章目錄 引言準備工作前置條件 代碼實現與解析導入必要的庫初始化Pygame定義繪制萬花筒圖案的函數主循環 完整代碼 引言 萬花筒效果通過反射和旋轉圖案創造出美麗的對稱圖案。在這篇博客中&#xff0c;我們將使用Python來實現一個動態的萬花筒效果。通過利用Pygame庫&#xf…

大數據可視化實驗(八):大數據可視化綜合實訓

目錄 一、實驗目的... 1 二、實驗環境... 1 三、實驗內容... 1 1&#xff09;Python縱向柱狀圖實訓... 1 2&#xff09;Python水平柱狀圖實訓... 3 3&#xff09;Python多數據并列柱狀圖實訓.. 3 4&#xff09;Python折線圖實訓... 4 5&#xff09;Python直方圖實訓...…

PAT 1108 Finding Average

原題鏈接&#xff1a;PAT 1108 Finding Average The basic task is simple: given N real numbers, you are supposed to calculate their average. But what makes it complicated is that some of the input numbers might not be legal. A legal input is a real number in…

Python只讀取Excel文件的一部分數據,比如特定范圍的行和列?

如何只讀取Excel文件的一部分數據&#xff0c;比如特定范圍的行和列&#xff1f; 在Python中&#xff0c;如果你只想讀取Excel文件的特定范圍&#xff0c;可以使用以下方法&#xff1a; pandas: Pandas是一個強大的數據處理庫&#xff0c;它有一個內置函數read_excel()用于讀…