1. 程式人生 > >二叉搜尋樹、AVL以及紅黑自平衡二叉搜尋樹

二叉搜尋樹、AVL以及紅黑自平衡二叉搜尋樹

本文符號意義

q: 插入節點
M: 實際刪除節點(後繼節點)
P: 當前節點的父節點
G: 當前節點的爺爺節點
S: 當前節點的叔叔節點(即父節點的兄弟節點)
L: 左孩子
R: 右孩子
非空:節點的key值不為空

二叉搜尋樹

二叉搜尋樹的基本操作有search(搜尋)、insert(插入)、delete(刪除)

搜尋

key值小於當前節點,則搜尋當前節點的左子樹,反之右子樹,直到葉節點(左右孩子不存在)。若遇到相同key則返回True。
二叉搜尋樹的搜尋的時間複雜度最好是O(logn),但在以下兩種情況下,將和線性搜尋O(n)無異。

插入

搜尋到葉節點,若比葉節點key小,則新增為當前葉節點的左孩子,反之右孩子。

刪除

若預刪除節點M不是葉節點會破壞樹的性質。一種簡單的方法是尋找後繼節點,將後繼節點儲存的資料複製給預刪除節點,然後刪除後繼節點即可。後繼節點最多一個孩子節點
分成以下幾種情形討論:

  1. M是葉節點。無後繼節點,直接刪除(如圖中M,N,T)
  2. M只有左孩子。左孩子即為後繼節點(如圖中M就是P的後繼節點)
  3. M有右孩子。
    • M的右孩子沒有左孩子。右孩子就是後繼節點(如圖中T就是S的後繼節點)
    • M的右孩子有左孩子。左子樹中key最小的節點就是後繼節點,即最靠左的節點(只有右孩子或為葉節點)(如圖中N就是G的後繼節點)
# 尋找後繼節點以及刪除操作參考程式碼
def findSuccessor
(self, node): # 找到後繼節點, 即右子樹中最左的節點 currentNode = node.right.left while True: if not currentNode.hasLeft() or not currentNode.left.key: return currentNode currentNode = currentNode.left def delete(self, key): p = self._search(key) if p.key != key:
print('the {} is not on the tree'.format(key)) else: if p.hasRight(): if p.right.hasLeft(): succ = self.findSuccessor(p) p.key = succ.key succ.parent.left = succ.right if succ.hasRight(): succ.right.parent = succ.parent else: p.key = p.right.key if p.right.hasRight(): p.right.right.parent = p p.right = p.right.right else: if p.hasLeft(): p.parent.left = p.left p.left.parent = p.parent else: if p.isLeft(): p.parent.left = None else: p.parent.right = None

自平衡二叉搜尋樹

AVL樹以及紅黑樹是自平衡二叉搜尋樹。較二叉搜尋樹而言主要的差別在於為了編碼方便,在每個原本意義上的葉節點下加兩個key為空的節點作為新的葉節點(讓空的葉節點顯式存在能使對樹的操作更為簡便)。
自平衡二叉搜尋樹的基本操作與二叉搜尋樹相同,僅在插入和刪除樹中的節點的同時,加一個調節樹結構的過程使之儘量左右平衡。無論是AVL樹還是紅黑樹都需要旋轉操作來調節樹的結構。旋轉可分為左旋轉右旋轉,如圖所示:

可見,旋轉操作是以兩個節點(node1, node2)為基準。node1是老的子樹根節點,node2是node1的孩子,是新的子樹根節點。當node1, node2排列成"/“則進行右旋,當排列成”\"則進行左旋。

def _leftRotate(self, oldRoot, newRoot):
    # newRoot是oldRoot的右孩子
    oldRoot.right = newRoot.left
    if newRoot.left is not None:
        newRoot.left.parent = oldRoot
    newRoot.parent = oldRoot.parent
    if oldRoot.parent is not None:
        if oldRoot.parent.left == oldRoot:  # 舊的根節點是左孩子
            oldRoot.parent.left = newRoot
        else:
            oldRoot.parent.right = newRoot
    else:
        self.root = newRoot
    oldRoot.parent = newRoot
    newRoot.left = oldRoot

def _rightRotate(self, oldRoot, newRoot):
    # newRoot是oldRoot的左孩子
    oldRoot.left = newRoot.right
    if newRoot.right is not None:
        newRoot.right.parent = oldRoot
    newRoot.parent = oldRoot.parent
    if oldRoot.parent is not None:
        if oldRoot.parent.left == oldRoot:
            oldRoot.parent.left = newRoot
        else:
            oldRoot.parent.right = newRoot
    else:
        self.root = newRoot
    oldRoot.parent = newRoot
    newRoot.right = oldRoot

對於插入與刪除操作需要注意兩點:
無論插入節點q還是刪除節點M,都無需調節q或M的孩子的結構,因此只需以其為起點自底向上調整即可。
實際刪除的節點最多隻有一個非空孩子節點。

AVL樹

AVL樹的每個節點都有一個**平衡因子(balance factor, bf)**屬性,即該節點左子樹的高度減去右子樹的高度(也有書籍定義右減左) 。

平衡

更新平衡因子(updateBalance)

  • 新插入節點為右孩子,其父節點bf減1,反之加1
  • 若父節點更新之前bf等於0, break,反之以父節點為新的當前節點繼續向上更新

旋轉平衡樹結構(rebalance)

當節點的bf的值大於1,表明左子樹過深,需要右旋以減小深度,反之若bf小於-1,表明右子樹過深,需要左旋以較小深度。旋轉後,以當前節點為根的子樹高度不變或減小1,較小1則需要繼續向上更新。
旋轉後bf更新公式如下:
左旋:node1.bf=node1.bf+1min(node2.bf,0)node1.bf = node1.bf + 1-min(node2.bf, 0) node2.bf=node2.bf+1+max(node1.bf,0)node2.bf = node2.bf + 1 + max(node1.bf, 0)
右旋:node1.bf=node1.bf1max(node1.bf,0)node1.bf = node1.bf -1 -max(node1.bf, 0) node2.bf=node2.bf1+min(node1.bf,0)node2.bf = node2.bf - 1 + min(node1.bf, 0)
注:node2是node1的左孩子,進行右旋為例:
示例
圖中,T1,T2,T3表示子樹。h1,h2,h3為樹的高度。
旋轉前:node2.bf=h2h3(1)node2.bf = h2-h3\qquad(1) node1.bf=1+max(h2,h3)h1(2)node1.bf = 1+max(h2,h3)-h1\qquad(2)
旋轉後:new_node1.bf=h3h1(3)new\_node1.bf = h3-h1\qquad(3) new_node2.bf=h2[1+max(h3,h1)](4)new\_node2.bf = h2 - [1+max(h3, h1)]\qquad(4)
(3)-(2)得:new_node1.bf=node1.bf1max(h2h3,0)=node1.bf1max(node2.bf,0)new\_node1.bf = node1.bf - 1 - max(h2-h3, 0)=node1.bf-1-max(node2.bf, 0)
(4)-(1)得:new_node2.bf=node2.bf1max(h3h1,0)=node2.bf1+min(new_node1.bf,0)new\_node2.bf = node2.bf-1-max(h3-h1, 0)=node2.bf-1+min(new\_node1.bf,0)

插入

  1. 搜尋待插入葉節點q
  2. 賦予q以key值,bf設定為0,並新增空L、R孩子葉節點
  3. 以q為起點自底向上更新bf:
    • 若當前節點bf小於-1或大於1,則rebalance, break(旋轉後,當前節點的子樹必然恢復原來的高度,故無需繼續向上更新)
    • 當前節點父節點不存在,break
    • 父節點存在,當前節點為左孩子,父節點bf加1,反之減1
    • 若父節點bf等於0,break

刪除

  1. 搜尋預刪除節點
  2. 找到後繼節點M
  3. 以M為起點自底向上更新bf:
    • 若當前節點bf小於-1或大於1,則需要rebalance調整樹結構,以當前節點有右孩子為例(這裡與插入後調整有點區別)
      • 若右孩子平衡因子為0,則調整後樹的高度是不變的,break
      • 若右孩子平衡因子為-1或1,則調整後,需要以當前節點父節點為新的當前節點
    • 當前節點父節點不存在,break
    • 父節點存在:
      • 父節點bf等於0,若當前節點為左孩子,bf加1,反之減1,break
      • 父節點bf不等於0, 若當前節點為左孩子,bf加1,反之減1, 繼續向上更新
  4. 刪除後繼節點
# 插入和刪除節點後調整bf的參考程式碼
def rebalanceInsert(self, currentNode):
    # 插入節點,重新調整樹至平衡
    if currentNode.bf < 0:  # 左旋
        if currentNode.right.bf > 0:
            self.rightRotate(currentNode.right)
            self.leftRotate(currentNode)
        else:
            self.leftRotate(currentNode)
    elif currentNode.bf > 0:
        if currentNode.left.bf < 0:
            self.leftRotate(currentNode.left)
            self.rightRotate(currentNode)
        else:
            self.rightRotate(currentNode)

def rebalanceDelete(self, currentNode):
    # 刪除節點,重新調整
    nextNode = None
    if currentNode.bf < 0:  # 右樹過深,需要左旋調整
        if currentNode.right.bf < 0:
            self.leftRotate(currentNode)
            nextNode = currentNode.parent
        elif currentNode.right.bf == 0:  # 若當前節點的右孩子平衡因子為0,則旋轉後以當前節點為根的子樹高度不變,故結束向上更新
            self.leftRotate(currentNode)
        else:
            self.rightRotate(currentNode.right)
            self.leftRotate(currentNode)
            nextNode = currentNode.parent
    else:
        if currentNode.left.bf > 0:
            self.rightRotate(currentNode)
            nextNode = currentNode.parent
        elif currentNode.left.bf == 0:
            self.rightRotate(currentNode)
        else:
            self.leftRotate(currentNode.left)
            self.rightRotate(currentNode)
            nextNode = currentNode.parent
    return nextNode

def updateInsertBF(self, currentNode):
    # 插入節點後,更新平衡因子
    if abs(currentNode.bf) > 1:  # 樹失衡則進行旋轉調節
        self.rebalanceInsert(currentNode)
        return
    if currentNode.parent is not None:
        if currentNode.isLeft():
            currentNode.parent.bf += 1
        else:
            currentNode.parent.bf -= 1
        if currentNode.parent.bf != 0:
            self.updateInsertBF(currentNode.parent)

def updateDeleteBF(self, currentNode):
    # 刪除節點後更新平衡因子
    if abs(currentNode.bf) > 1:  # 樹失衡則進行旋轉調節
        currentNode = self.rebalanceDelete(currentNode)
        if currentNode is None:
            return
    if currentNode.parent is not None:
        oldBF = currentNode.parent.bf
        if currentNode.isLeft():
            currentNode.parent.bf -= 1
        else:
            currentNode.parent.bf += 1
        if oldBF != 0:  # 父節點為根的子樹,原本不平衡,那麼刪除節點後其子樹高度必改變,故需要繼續向上更新
            self.updateDeleteBF(currentNode.parent)

紅黑樹

紅黑樹的每個節點都有一個顏色(color)屬性,根節點以及葉子節點(key為空)均為黑色,而其他節點滿足如下兩條規則

  • rule1: 父子節點不能同為紅色,但可以同為黑色。
  • rule2: 某個節點到其子樹任意葉節點的路徑上包含的黑色節點個數(稱為black height)相同。
    節點顏色的更新要以這兩條準則為基礎。

平衡

插入或刪除後,如果樹中的顏色違反了上面兩條規則,則需要變更節點顏色,必要時需要旋轉。過程比較複雜下面針對插入與刪除的不同情形細講。

插入

  1. 搜尋到待插入葉節點q
  2. 賦予q待插入key值,標記為紅色,並新增空L、R孩子葉節點
  3. 以q為起點自底向上更新color:
    • q是根節點,將其標記為黑色,break
    • q的父節點P是黑色,break
    • q的父節點P是紅色(違反rule1,需調整):
      • q的叔叔節點S是紅色,將P與S變更為黑色, break
      • S是黑色:
        • q、P以及q的爺爺節點G排列滿足’/‘或’’,則以(G, P)為基準右旋或左旋,G變紅色,P變黑色, break
        • q、P以及q的爺爺節點G排列滿足’>‘或’<’,則以(P, q)為基準右旋或左旋, 轉上1

刪除

  1. 搜尋到待刪除節點的位置
  2. 找到後繼節點M,將其key複製給待刪除節點
  3. 以M為起點自底向上更新color:
    • M是紅色,其左右孩子L和R必為空,直接刪除M,break
    • M是黑色:
      • L和R若存在一個非空則必為紅色(參考rule2),刪除M,非空孩子接替其位置,並繼承M的顏色, break
      • L和R都是空:
        • M的兄弟節點S是紅色,S變黑, M的父節點P變紅,再以(P,S)為基準進行旋轉,轉下2
        • S是黑色:
          • S的孩子全部為空:若P為黑色,則將S變紅; 反之將P、S的顏色交換, break
          • S的右孩子R是紅色,左孩子任意,S變紅,P及R變黑,再以(P,S)為基準進行旋轉,break
          • S的左孩子L是紅色,L變黑,S變紅,再以(S,L)為基準進行旋轉,轉上2
# 插入和刪除節點後調整bf的參考程式碼
def updateInsertColor(self, currentNode):
    # 插入節點後更新顏色
    if currentNode.isRoot():
        currentNode.color = 0
        return
    elif not currentNode.parent.color:  # 父節點為黑色,不用更新
        return
    else:
        uncle = currentNode.parent.getSibling()
        if uncle.color:  # case1: 存在叔叔節點且顏色是紅色
            uncle.color = 0
            currentNode.parent.color = 0
            uncle.parent.color = 1  # 將爺爺節點顏色變更為紅色
            self.updateInsertColor(uncle.parent)
        else:
            if currentNode.parent.isLeft():
                if currentNode.isLeft():  # case2:當前節點、其父節點及爺爺節點位於一條直線上
                    currentNode.parent.parent.color = 1
                    currentNode.parent.color = 0
                    self.rightRotate(currentNode.parent.parent)
                else:  # case3: 當前節點、其父節點及爺爺節點不在一條直線上,先轉換到一條直線上,
                    self.leftRotate(currentNode.parent)
                    self.updateInsertColor(currentNode.left)
            else:
                if currentNode.isRight():
                    currentNode.parent.parent.color = 1
                    currentNode.parent.color = 0
                    self.leftRotate(currentNode.parent.parent)
                else:
                    self.rightRotate(currentNode.parent)
                    self.updateInsertColor(currentNode.right)

def updateDeleteColor(self, currrentNode):
    # 刪除節點後更新顏色
    if currrentNode.color:  # case1:M是紅色,直接刪除
        return
    else:
        if currrentNode.left.key:  # case2: M是黑色,子節點有個非空節點就,變為紅色
            currrentNode.left.color = 0
            return
        elif currrentNode.right.key:
            currrentNode.right.color = 0
            return
        else:  # case3: M是黑色,子節點都是空節點(最複雜的情形)
            S = currrentNode.getSibling()
            if S.color:  # 1: S是紅色,將其變更為黑色
                currrentNode.parent.color = 1
                S.color = 0
                if S.isLeft():
                    self.rightRotate(currrentNode.parent)
                else:
                    self.leftRotate(currrentNode.parent)
            if not (S.left.color or S.right.color):  # S沒有紅色孩子
                if currrentNode.parent.color:
                    currrentNode.parent.color, S.color = S.color, currrentNode.parent.color
                else:
                    S.color = 1
            elif S.right.color:  # 右孩子為紅色,左孩子顏色任意
                if S.isRight():  # S的紅色節點滿足'\'
                    S.right.color = 0
                    S.color = currrentNode.parent.color
                    currrentNode.parent.color = 0
                    self.leftRotate(currrentNode.parent)
                else:  # S的紅色節點滿足'>'
                    S.left.color = 0
                    S.color = 1
                    self.rightRotate
            
           

相關推薦

搜尋AVL以及平衡搜尋

本文符號意義 q: 插入節點 M: 實際刪除節點(後繼節點) P: 當前節點的父節點 G: 當前節點的爺爺節點 S: 當前節點的叔叔節點(即父節點的兄弟節點) L: 左孩子 R: 右孩子 非空:節點的key值不為空 二叉搜尋樹 二叉搜尋樹的基本操作有search

查詢(BST) | 平衡查詢AVL) | (RBT)

二叉查詢樹(BST) 特點:對任意節點而言,左子樹(若存在)的值總是小於本身,而右子(若存在)的值總是大於本身。 查詢:從根開始,小的往左找,大的往右找,不大不小的就是這個節點了; 插入:從根開始,小的往左,大的往右,直到葉子,就插入, 時間複雜度期望為

資料結構中常見的(BST搜尋AVL平衡RBTB-B+B*

BST樹 即二叉搜尋樹:        1.所有非葉子結點至多擁有兩個兒子(Left和Right);        2.所有結點儲存一個關鍵字;        3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹; 如:      

平衡AVLB的比較

1. 紅黑樹和自平衡二叉(查詢)樹區別 紅黑樹放棄了追求完全平衡,追求大致平衡,在與平衡二叉樹的時間複雜度相差不大的情況下,保證每次插入最多隻需要三次旋轉就能達到平衡,實現起來也更為簡單。 平衡二叉樹追求絕對平衡,條件比較苛刻,實現起來比較麻煩,每次插入新節點之後需要旋轉的

淺談查詢AVLBB+的原理及應用

一、二叉查詢樹 1、簡介 二叉查詢樹也稱為有序二叉查詢樹,滿足二叉查詢樹的一般性質,是指一棵空樹具有如下性質: 任意節點左子樹不為空,則左子樹的值均小於根節點的值. 任意節點右子樹不為空,則右子樹的值均大於於根節點的值. 任意節點的左右子樹也分別是二叉查

排序AVL最簡單的理解

前言 [為什麼寫這篇] 之前在知乎上看過一個提問:為什麼紅黑樹比AVL樹用的場景更為廣泛?其實,這兩者應用場景都挺廣泛的。紅黑樹在 STL 和 Linux 都有一定的運用。而AVL樹也在 Windows程序地址空間管理 中得到了使用。既然紅黑樹和AVL樹這麼

資料結構中的(搜尋AVL)

> [資料結構動圖展示網站](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html) ### 樹的概念 樹(英語:tree)是一種抽象資料型別(ADT)或是實作這種抽象資料型別的資料結構,用來模擬具有樹狀結構性質的資料集合。它是由n(

AVL——平衡搜尋

概念 AVL(Adelson-Velskii and Landis)樹得名於它的發明者 G.M. Adelson-Velsky 和 E.M. Landis,他們在 1962 年的論文《An algorithm for the organization of information》中

查詢(BST),平衡查詢(AVL),(RBT),B~/B+(B-tree)的比較

http://www.iteye.com/topic/614070 此少俠總結的特棒,直接收藏了。 我們這個專題介紹的動態查詢樹主要有: 二叉查詢樹(BST),平衡二叉查詢樹(AVL),紅黑樹(RBT),B~/B+樹(B-tree)。這四種樹都具備下面幾個優勢: (1) 都

Javascript之資料結構與演算法的平衡搜尋AVL)實現

Javascript之資料結構與演算法的自平衡二叉搜尋樹(AVL)實現 簡介 程式碼實現 簡介 AVL樹是一種自平衡樹。新增或移除節點時, AVL樹會嘗試自平衡。任意一個節點(不論深 度)的左子樹和右子樹高度最多相差1

數據結構(5) 第五天 快速排序歸並排序堆排序高級數據結構介紹:平衡B/B+

平衡二叉樹 let b+樹 堆排 mark 9.png 思想 incr 相等 01 上次課程回顧 希爾排序 又叫減少增量排序 increasement = increasement / 3 + 1 02 快速排序思想 思想: 分治法 + 挖坑

BB+AVL

付出 而不是 通過 找到 磁盤讀寫 三次 復雜度 節點 span 定義及概念 B樹 二叉樹的深度較大,在查找時會造成I/O讀寫頻繁,查詢效率低下,所以引入了多叉樹的結構,也就是B樹。階為M的B樹具有以下性質: 1、根節點在不為葉子節點的情況下兒子數為 2 ~ M2、除根結

AVL-平衡查詢(Java實現)

      在電腦科學中,AVL樹是最先發明的自平衡二叉查詢樹。AVL樹得名於它的發明者 G.M. Adelson-Velsky 和 E.M. Landis,他們在 1962 年的論文 "An algorithm for the organization of inform

AVLB的比較

   AVL, 紅黑樹,B樹 前段日子在研究著這幾中樹的優劣     首先我們來談談AVL樹,AVL樹是一棵平衡的二叉查詢樹。它的平衡因子為-1,1,0不平衡達到2就會將樹進行平衡化。 AVL對數字的分佈要求比較高,比如說是隨機數,有人曾經做過計算,在利用AVL樹與紅黑樹進

8.的定義,的效能分析和與平衡的比較

平衡二叉樹 平衡二叉樹或者是一顆空的二叉排序樹,或是具有下列性質的二叉排序樹: 根節點的左子樹和右子樹的深度最多相差1根節點的左子樹和右子樹都是平衡二叉樹 平衡因子 平衡因子是該節點的左子樹的深度與右子樹的深度之差。 最小不平衡子樹 在平衡二叉樹的構造過程中,以距離插入節

Jmeter之處理sessioncookie以及如何做關聯()Jmeter如何提取響應頭部的JSESSIONID

就是利用Jmeter做介面測試的時候,如何提取頭部的JSESSIONID然後傳遞到下一個請求,繼續完成當前使用者的請求。其實,關於這個問題有三種種解決方法:3)如果響應頭裡面有這個JSESSIONID,我們可以通過新增cookie來解決這個問題,今天這篇部落格,我們重點講解第

平衡AVL

平衡樹 平衡樹有AVL樹、紅黑樹、2-3樹、2-3-4樹 AVL樹 AVL樹是最早的一種平衡樹,它以發明者的名字命名。 特徵 在AVL樹中節點的左子樹和右子樹的高度差不會大於1 實現 在AVL樹中每個節點都儲存著一個額外的資料,它的左子樹和右子樹的高度差,這個差值不能大於1。插入一個元素後,

資料結構之平衡查詢(1)

今天開始,我們再來認識一個新的二叉樹,稱為自平衡二叉查詢樹。AVL樹是最先發明的自平衡二叉查詢樹。 AVL樹的特點是:對於樹中的任何節點,節點的左右子樹的高度差距最大為1,所以AVL樹也稱為高度平衡樹。AVL樹得名於它的發明者G.M. Adelson-Velsky和E.M.

B,B-,B*,B+和的區別

B樹        即二叉搜尋樹:        1.所有非葉子結點至多擁有兩個兒子(Left和Right);        2.所有結點儲存一個關鍵字;        3.非葉子結點的左指標指向小於其關鍵字的子樹,右指標指向大於其關鍵字的子樹;        如:

為什麼要有?什麼是?畫了20張圖,看完這篇你就明白了

為什麼要有紅黑樹 想必大家對二叉樹搜尋樹都不陌生,首先看一下二叉搜尋樹的定義: 二叉搜尋樹(Binary Search Tree),或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的