平衡樹學習筆記

来源:https://www.cnblogs.com/OIerBoy/archive/2023/08/28/17367976.html
-Advertisement-
Play Games

# 平衡樹 平衡樹就是為了實現一類元素線上性結構中動態變化的功能所需要的數據結構。 平衡樹是一種基於二叉搜索樹的數據結構。 滿足:左兒子 $<$ 根 $<$ 右兒子。 也就是一切小於根節點的在左邊,一切大於根節點的在右邊。 這樣想要查找一個節點的位置時間複雜度就是 $O(\log n)$。 平衡樹主 ...


平衡樹

平衡樹就是為了實現一類元素線上性結構中動態變化的功能所需要的數據結構。

平衡樹是一種基於二叉搜索樹的數據結構。
滿足:左兒子 \(<\)\(<\) 右兒子。
也就是一切小於根節點的在左邊,一切大於根節點的在右邊。
這樣想要查找一個節點的位置時間複雜度就是 \(O(\log n)\)

平衡樹主要有三種:Splay,fhq_Treap,Treap。
我這次主要講前兩種。
當然還有其他的像替罪羊樹,紅黑色。

Splay

Splay 是 LCT 的基礎操作。
本人以為還是 Splay 比較好理解的。

核心操作

Splay 的基本操作就是將BST旋轉,分左旋和右旋(其實都差不多)
旋轉的要求:在不改變原有的中序遍歷的前提下改變樹的結構。
簡化一下:把兒子節點與父親節點的身份互換,且有BST性質。

旋轉 \(zig(x)\),\(zag(x)\) 如下圖:
image

具體描述

設要旋轉的節點為 \(x\),它的父親為 \(y\)\(y\) 的父親為 \(z\)

  • \(y\) 的左兒子設為 \(x\) 的右兒子
  • \(x\) 的右兒子存在,將 \(x\) 的右兒子的父親設為 \(y\)
  • \(x\) 的右兒子設為 \(y\)
  • \(y\) 的父親設為 \(x\)
  • \(x\) 的父親設為 \(z\)
  • \(z\) 存在,將 \(z\) 的某個子節點(原來 \(y\) 所在的子節點)設為 \(x\)

雙旋操作

在 Splay 中,每加入一個新的節點就需要把它旋轉到根。
設當前需旋轉的節點為 \(x\),節點的旋轉可分為以下三種:
\(1.\)\(x\) 的父親是根,這時直接旋轉即可。
\(2.\)父親和 \(x\) 的兒子同側(即同為左兒子或同為右兒子),這時先旋轉父親,再旋轉 \(x\)
\(3.\)父親和 \(x\) 的兒子不同側,這時將 \(x\) 旋轉兩次。
如下圖:
image
image

時間複雜度

對於這個時間複雜度的分析,我們需要用一下勢能分析
\(siz(x)\) 表示以 \(x\) 為根節點的子樹大小。
\(\phi(x)\) 表示在進行 \(x\) 次操作後的勢能函數。
\(c(x)\) 表示時間的時間複雜度。
\(a(x)\) 表示均攤的時間複雜度。
所以根據定義,我們可以得出:
\(\phi(x)=\log(siz(x))\)
\(a(x)=c(x)+\phi(x)-\phi(x-1)\)
\(\sum a(x)=\phi(n)-\phi(0)+\sum c(x)\)
因為根據 \(\phi(x)=\log(siz(x))\),所以現在肯定有:\(|\phi(n)-\phi(0)|\le n\log n\)
考慮每一次的 \(\Delta\phi\)

對於zig:

\[\Delta\phi=\phi'(p)-\phi'(p)+\phi'(x)-\phi(x)=\phi'(p)-\phi(x)\le\phi'(x)-\phi(x) \]

image

對於zig-zig

\[\Delta\phi=\phi'(z)+\phi'(y)+\phi'(x)-\phi(z)-\phi(y)-\phi(x) \]

\[=\phi'(z)-\phi'(y)-\phi(y)-\phi(x) \]

\[\le\phi'(x)+\phi'(z)-2\phi(x) \]

\[\le 3\phi'(x)-3\phi(x)+(\phi(x)+\phi'(z)-2\phi'(x)) \]

\[\le 3(\phi'(x)-\phi(x))-2k \]

image

那麼Splay到根的均攤代價為 \(O(logn)\)\(zig\) 只有一次操作,所以只會使均攤代價增加\(k\)
再算上 \(\phi(n)-\phi(0)=n\log n\)
所以總時間複雜度為 \(O(n\log n)\)

操作實現

講了核心操作和時間複雜度後,我們來看看它可以支持的操作。
註:由於我們只能保證 Splay 本身的時間複雜度,所以我們就必須只能通過旋轉來實現一些操作。

查詢數值

給定一個數 \(k\),查詢排名為 \(k\) 的數。

  • \(k\) 小於等於左子樹大小,就向左子樹走
  • 否則,將 \(k\) 先減去左子樹大小和當前節點的 \(cnt\),使得 \(k\), 等於在右子樹中的排名。然而若 \(k\) 小於等於 \(0\),說明已經找到,進行旋轉,並返回當前節點權值。

查詢 \(x\) 的前驅和後繼

我們先將 \(x\) 節點旋轉到根節點。

  • 前驅:就是 \(x\) 左子樹中最右邊的節點。
  • 後繼:就是 \(x\) 右子樹中最左邊的節點。

刪除節點 \(x\)

我們需要先將 \(x\) 旋轉到根節點,從而去得到它的前驅和後繼。
然後將前驅旋轉到根,再將後繼旋轉到根,就會得到下圖:
image
直接把 \(x\) 刪去即可。

查詢區間 \(\left[l,r\right]\)

我們需要將 \(l\) 的前驅旋轉到根,再將 \(r\) 的後繼旋轉到根節點,點像刪除操作。
\(l\) 前驅的右子樹就是整個 \([l,r]\) 區間了。

fhq_Treap(無旋Treap)

前言

首先,我們要 \(sto\) 範浩強 \(orz\)
dalao 發明瞭 fhq_Treap,因為 fhq_Treap 是三種平衡樹中最強悍的一種了,它可以維護值域,可以維護下標,可以維護區間修改,它還可以完成可持久化操作。其唯一弱於 splay 的就是在維護 LCT 上。

什麼是 fhq_Treap

fhp_Treap 首先是一個 Treap。
而 Treap 是什麼呢
Treap=BST+Heap。
所以 Treap 就是一個擁有二叉搜索樹的性質,但是每一個節點都通過一個附加權值來滿足符合堆的性質。
而這個附加權值就是 Treap 的一個關鍵,它是通過隨機生成一個 \(key\) 來維護一個近似的平衡。
而我們隨機生成的 \(key\) 要想讓它退化成鏈的概率是微乎其微的。(教練:你讓隨機 \(key\) 退化成鏈的概率就像你出門被核彈直接創死的概率一樣小。)
但是一個旋轉的 Treap 是有點噁心的。
所以,我們的無旋 Treap 就登場了。

核心操作

我們要想 Treap 不旋轉,我們就需要一個可以頂替旋轉的一種操作來實現,那就是拆分與合併。

split

split 就是把 Treap 以 \(k\) 值分為兩棵 Treap。
對於我們遍歷到每一個點,假如它的權值小於 \(k\),那麼它的左子樹,就要分到左子樹里,然後再遍歷它的右兒子,反之亦然。
因為它的最多操作次數就是一直分到底,時間複雜度就是 \(O(\log n)\)
對於前 \(k\) 個版的,就像找第 \(k\) 大的感覺。每次減掉 \(siz\)
這個用遞歸就可以實現了。

merge

merge 就是把被分開的兩顆 Treap 合併起來。
因為第一個 Treap 的權值都比較小,我們比較一下它的 \(kay\),假如第一個的 \(key\) 小,我們就可以直接保留它的所有左子樹,再把第二個 Treap 變成它的右兒子,反之亦然。
也就是說,我們其實是遍歷了第一個 Treap 的根定為最大節點,第二個 Treap 的根就是最小節點,時間複雜度就是 \(O(\log n)\) 了。

操作實現

插入數值

插入一個權值為 \(v\) 的點。

  • 把 Treap 以權值 \(v\) 分成兩顆。
  • 將權值 \(v\) 插入。
  • 再把兩顆 Treap 合併以來。
    image

刪除數值

刪除一個權值為 \(v\) 的點,很類似於插入操作。

  • 把 Treap 以權值 \(v\) 分成兩顆。
  • 將權值 \(v\) 刪去。
  • 再把兩顆 Treap 合併以來。

查詢指定值的排名

如果是在一個有序的序列中查詢排名,我們可以二分查找這個序列,然後根據找到的元素的下標來確定排名,假設下標從 \(1\) 開始,那麼排名就為該元素的下標 \(i\)。那麼,在它之前,也就有 \(i−1\) 個元素。由此,我們可以得到排名的一種定義:在有序序列中,一個元素的排名就是它前面的元素的個數 \(+1\)
在 fhq_Treap 上,我們就直接按 \(key−1\) 分裂樹,查一下值小於等於 \(key−1\) 的樹的大小,再 \(+1\) 即可。

總結

平衡樹大體就利用 BST 性質,通過一些如旋轉,分裂合併的操作來實現將我們需要進行修改的節點或子樹獨立出來,在進行修改。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • `pandas`小技巧系列是介紹的是使用`pandas`分析數據時,最常用的一些操作技巧。 具體包括: 1. [創建測試數據](https://www.cnblogs.com/wang_yb/p/17552748.html) 學習pandas的過程中,為了嘗試pandas提供的各類功能強大的函數,常 ...
  • ## 1. 什麼是WebSocket? WebSocket 是一種在單個 TCP 連接上進行全雙工通信的協議,它允許在瀏覽器和伺服器之間進行實時的、雙向的通信。相對於傳統的基於請求和響應的 HTTP 協議,WebSocket 提供了一種更有效、更實時的通信方式,適用於需要實時更新、實時通知和實時交互 ...
  • 來源:toutiao.com/article/7234104886726705716 ## 1.前言 我們的生產環境基本上都部署在雲伺服器上,例如應用伺服器、MySQL伺服器等。如果MySQL伺服器直接暴露在公網,就會存在很大的風險,為了保證數據安全,MySQL伺服器的埠是不對外開放的。 好巧不巧 ...
  • 你的Java服務是如何監控的呢? 1.Null:監控?什麼監控?我一個寫代碼的服務掛了跟我有什麼關係? 2.命令行:服務掛了?記憶體泄漏?jstat jmap jcmd,還好不是我寫的 3.擼代碼:Java採集JVM/伺服器資源信息 -> Prometheus -> Grafana,請允許我對業務代碼 ...
  • 正式使用官方的Java API Client操作ES之前,將與之有關的重要知識點先做一輪串講,後面開始編碼時,疑點已掃清,可以愉快而順暢的實現業務功能 ...
  • 來源:https://www.toutiao.com/article/6697540366528152077/ ## 前言 有時候我們需要知道線上的**redis的使用情況**,尤其需要知道一些**首碼的key值**,讓我們怎麼去查看呢?今天給大家分享一個小知識點! ## 事故產生 因為我們的用戶* ...
  • # The difference beteen two way 總所周知,Java實現多線程有兩種方式,分別是繼承Thread類和實現Runable介面,那麼它們的區別是什麼? **繼承 Thread 類:** 通過繼承 Thread 類,你可以創建一個直接表示線程的類。你可以覆蓋 Thread 類 ...
  • ## 問題描述 現有$n$個正整數組成的序列$a$,從中刪除一個數,得分是其本身同左、右相鄰的數的乘積, 然後再在剩餘的整數中繼續刪除,註意**序列兩端的數字a1和an是不能刪除的**,求這樣刪除$n-2$個整數後的最大得分。 例如有四個數$3 、4、5、6$,按照先$4$後$5$的刪除順序,其得分 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...