主頁 > 軟體設計 > 競賽最好用的平衡樹-Size Balanced Tree(SBT)【建議收藏】

競賽最好用的平衡樹-Size Balanced Tree(SBT)【建議收藏】

2021-10-25 07:20:42 軟體設計

大家好,

前段時間我更新過AVL、紅黑樹以及搜索二叉樹的簡單講解,今天我們還是圍繞著平衡樹的話題,來講解一個很牛逼的平衡樹結構,這種結構是我國的一位大牛:陳啟峰,在2006年,他還在讀高中階段,所發明的一種平衡樹結構,叫Size Balanced Tree(SBT),根據節點的個數,來調整平衡性,一直到今天,這種平衡樹結構,在演算法競賽領域是非常常用的,雖然SBT的時間復雜度跟AVL、紅黑樹這些平衡樹一樣,但是SBT是比較好寫,比較好改的,所以在演算法競賽時,是最常用的一種演算法,陳啟峰SBT論文(譯)

本期文章原始碼:GitHub

img

前期文章

二叉樹的概念以及搜索二叉樹

AVL平衡樹

淺析紅黑樹

文章目錄

  • 一、左右旋轉
  • 二、Maintain方法
  • 三、add方法和delete方法

一、左右旋轉

還是老樣子,為了維持平衡性,SBT也是需要進行旋轉操作的,只是說,呼叫旋轉操作的時機,跟其他的平衡樹有點區別而已,講解旋轉之前,我們先來認識SBT的節點:

//可以直接改為泛型,這里為了理解這種結構,就忽略了
public class SBTNode {
    public int val; //值域
    public SBTNode left, right; //左右孩子
    public int size; //節點數
    
    public SBTNode(int val) {
        this.val = val;
        size = 1; //默認節點大小是1
    }
}

SBT的節點,只是多了一個size域,用于表示以當前節點為根節點時,這顆子樹的節點數,整棵樹的平衡性,就是根據這個size來調整的,

右旋轉:(LL型)

image-20211023155252766

旋轉操作的指標,相互之間的轉換,我相信以前了解過平衡樹的同學,應該是知道的(如果不知道,請翻閱前期文章),問題就在于如何,如何修改這些節點的size?

看上圖,T1-T3,表示子樹,另外兩個表示節點,旋轉之后,我們會發現,T1和T2和T3這三顆子樹,他們下面的節點是沒有發生改變的,也就是說T1-T3,這三個是不需要再次計算size的,我們只需要計算上圖那兩個白色的節點的size即可!

又因為,旋轉之后,新的根節點的節點數,還應該是原根節點的節點數,所以最后我們只需要計算旋轉之后,原根節點的節點數即可,也就等于該節點的左子樹節點數加上右子樹的節點數,再加自己本身這個節點,

//SBT右旋轉
private SBTNode R_Rotate(SBTNode node) {
    SBTNode newRoot = node.left;
    node.left = newRoot.right;
    newRoot.right = node; 
    
    //計算size
    newRoot.size = node.size; //新根節點的節點數,等于原根節點的節點數
    node.size = (node.left != null? node.left.size : 0) + 
        (node.right != null? node.right.size : 0) + 1;
    return newRoot; //回傳新的根節點
}

左旋轉:(RR型)

跟上面的旋轉一樣,每個節點之間的指標指向,這里就不深究了,同學可以看看我前期的文章,有講解,主要還是size的計算,同樣的,T1~T3的節點數,都是沒有變,所以不用管,只需計算原根節點和新根節點的節點數,切新根節點的節點數,就是原根節點的節點數,

//SBT左旋轉操作
private SBTNode L_Rotate(SBTNode node) {
    SBTNode newRoot = node.right;
    node.right = newRoot.left;
    newRoot.left = node;
    
    //計算size
    newRoot.size = node.size; //繼承原根節點的節點數
    node.size = (node.left != null? node.left.size : 0) + 
        (node.right != null? node.right.size : 0) + 1;
    return newRoot; //回傳新的根節點
}

以上兩種就是最基本的LL型和RR型,在SBT中,也是有LR型和RL型的,跟AVL中一樣,不需要再寫額外的代碼,只需要呼叫兩次左旋轉或者右旋轉,如下:

  • 假設A節點需要進行LR型的旋轉,則只需A.left = 左旋轉一次,再A= 右旋轉一次,
  • 同理,假設B節點需要進行RL型的旋轉,則只需B.right = 右旋轉一次,再B = 左旋轉一次,

具體在什么時候需要呼叫旋轉函式,我們在接下來的Maintain方法里講解,

img

二、Maintain方法

Maintain方法,就是SBT樹,最核心的地方,也就是這個方法,能夠保證一棵樹具有平衡性的,

首先,我們需要知道,在什么時候,才需要進行旋轉操作,在AVL中,是通過判斷平衡因子來處理平衡性;在左傾紅黑樹中,則是根據每個節點顏色來處理平衡性,而SBT是:

首先看這張圖:

image-20211023163650786

上面的四種旋轉(LL、LR、RR、RL),分別對于一下四點:

  • LL型:T1的size > 二叔的size
  • LR型:T2的size > 二叔的size
  • RL型:T3的size > 大叔的size
  • RR型:T4的size > 大叔的size

以上四點,就是觸發機關,只要滿足這四點的某一個條件,則需要進行旋轉操作,總結起來就一句話:叔叔的節點數,必須大于等于侄子的節點數,不然的話,就需要進行平衡調整,具體是為什么這種機制,就能夠保證平衡性,請翻閱文章開頭,陳啟峰的那篇論文,

到目前為止,我們就知道了SBT的核心,代碼寫起來就簡單多了,分別計算叔叔節點和侄子節點的節點數,if判斷即可,值得注意的是,旋轉操作之后,還需要遞回呼叫Maintain方法,遞回呼叫的物件,就是:哪個節點的子樹被換了,則需要呼叫這個Maintain(新子樹);舉個例子:原先A節點的右子樹是T2,旋轉操作之后,A節點的右子樹變為了T3,那么就需要遞回呼叫Maintain(T3)

//Maintain方法
private SBTNode maintain(SBTNode cur) {
    if (cur == null) {
        return null;
    }

    //計算對應節點的節點數,null的話,就是0
    int leftSize = cur.left != null ? cur.left.size : 0;
    int rightSize = cur.right != null ? cur.right.size : 0;
    int leftLeftSize = cur.left != null ? (cur.left.left != null ? cur.left.left.size : 0) : 0;
    int leftRightSize = cur.left != null ? (cur.left.right != null ? cur.left.right.size : 0) : 0;
    int rightLeftSize = cur.right != null ? (cur.right.left != null ? cur.right.left.size : 0) : 0;
    int rightRightSize = cur.right != null ? (cur.right.right != null ? cur.right.right.size : 0) : 0;

    if (leftLeftSize > rightSize) { //LL型
        cur = R_Rotate(cur);//右旋
        
        cur.right = maintain(cur.right);
        cur = maintain(cur);
    } else if (leftRightSize > rightSize) { //LR型
        cur.left = L_Rotate(cur.left);
        cur = R_Rotate(cur);
        
        cur.left = maintain(cur.left);
        cur.right = maintain(cur.right);
        cur = maintain(cur);
    } else if (rightLeftSize > leftSize) { //RL型
        cur.right = R_Rotate(cur.right);
        cur = L_Rotate(cur);
        
        cur.left = maintain(cur.left);
        cur.right = maintain(cur.right);
        cur = maintain(cur);
    } else if (rightRightSize > leftSize) { //RR型
        cur = L_Rotate(cur);
        
        cur.left = maintain(cur.left);
        cur = maintain(cur);
    }
    return cur;
}

切記:遞回呼叫Maintain時,一定是先呼叫cur的左右子樹,然后才是呼叫cur,因為cur的處理,是依賴于他的左右孩子的,

可能有同學就會疑惑了,這么多遞回函式,這個代碼能跑完嗎?

當然能夠跑完,因為旋轉操作之后,遞回呼叫Maintain,能夠在O(1)的時間內完成,

三、add方法和delete方法

Maintain方法之后,SBT的就算掌握大部分的代碼了,其余的添加和洗掉代碼,完全跟搜索二叉樹的增加洗掉,一模一樣,只是需要在添加洗掉之后,呼叫Maintain方法,用于調整平衡性即可,

//add方法
public void add(int val) {
    this.root = add(this.root, val);
}
//方法多載
private SBTNode add(SBTNode cur, int val) {
    if (cur == null) {
        return new SBTNode(val);
    } else {
        cur.size++; //沿途節點的節點數加1
        if (val < cur.val) {
            cur.left = add(cur.left, val);
        } else {
            cur.right = add(cur.right, val);
        }
    }
    //添加之后,需要呼叫maintain,進行平衡操作
    return maintain(cur); 
}

在SBT中,delete的時候,在大多時候,是不需要進行平衡調整的,why?

是因為沒必要,假設當前SBT樹的高度是H,現在洗掉一個節點后,高度可能還是H,又或者是H- 1,此時調整平衡與不調整平衡,都不影響后續的操作,比如洗掉之后,高度還是H,下次查找或者添加時,時間復雜度還是O(H),影響因素還是高度值,所以在一些比賽中,為了達到優化,在delete中,不進行平衡性的調整,而是把平衡性的調整,放在了add方法里,

//delete方法
public void delete(int val) {
    if (contains(val)) { //包含當前值得話,就遞回洗掉
        root = delete(root, val);
    }
}

private SBTNode delete(SBTNode cur, int val) {
    cur.size--; //沿途節點的節點數-1
    if (cur.val > val) {
        cur.left = delete(cur.left, val);
    } else if (cur.val < val) {
        cur.right = delete(cur.right, val);
    } else {
        if (cur.left == null && cur.right == null) { //沒有左右子樹
            cur = null;
        } else if (cur.left == null && cur.right != null) { //只有右子樹
            cur = cur.right;
        } else if (cur.left != null && cur.right == null) { //只有左子樹
            cur = cur.left;
        } else {
            //左右兩個子樹都有的情況
            SBTNode pre = null;
            SBTNode des = cur.right; //向右子樹查找最左節點
            des.size--;
            while (des.left != null) {
                pre = des;
                des = des.left;
                des.size--; //最終的des節點,會重新計算節點數
            }
            if (pre != null) {
                pre.left = des.right;
                des.right = cur.right; //將des節點,替換cur節點
            }
            des.left = cur.left;//連接原先的左子樹
            des.size = des.left.size + (des.right != null ? des.right.size : 0) + 1;
            cur = des;
        }
    }
    
    //cur = maintain(cur); //平衡調整
    return cur;
}

特別需要注意的是,就是被洗掉節點的左右子樹都不為null時,需要找一個節點來替換當前被洗掉的節點,一般都是在被洗掉節點的右子樹上,查找最小(最左)的節點進行替換,這一點,也是搜索二叉樹的洗掉操作,最容易出錯的一個點,值得重點關注,

還有一些簡單的方法沒寫,大家自主實作即可,比如contains、isEmpty等等,

好啦,本期更新就到此結束啦!SBT樹學好之后,可以幫助你在一些演算法題上得到更好的幫助,這種結構也算比較好改,可能根據SBT改其他的結構,總之,這種結構,值得好好學習,

我們下期見吧!!!
在這里插入圖片描述

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/335207.html

標籤:其他

上一篇:工廠模式--擺脫你日復一日new物件卻依舊單身的苦惱!

下一篇:《詩經》那么美,讀不懂多浪費|爬取一本好好學習,準備做一個“有文化”的程式人!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more