【全网最易懂的红黑树讲解】一眼看懂二叉树、平衡树、红黑树,一文打尽

简介: 【全网最易懂的红黑树讲解】一眼看懂二叉树、平衡树、红黑树,一文打尽

第一部分:二叉树的奥秘


1.1 二叉树基础


什么是二叉树?

二叉树是一种每个节点最多有两个子节点的树结构,通常分为左子节点和右子节点。它在数据组织、资源管理等方面有着广泛应用。


二叉树的类型

  • 完全二叉树:除了最后一层外,每一层都被完全填满,并且所有节点都尽可能地集中在左侧。
  • 满二叉树:一个高度为h,且含有2^h - 1个节点的二叉树。


1.2 二叉树的遍历


想象我们有一个简单的二叉树,其结构如下:

    🌳
   /   \
 🌿     🌾
 /
🍃

这棵树的节点分布可以用emoji表示为:根节点是🌳,它的左子节点是🌿,右子节点是🌾;🌿的左子节点是🍃。


前序遍历(根左右)


  1. 访问根节点:🌳
  2. 访问左子树:
  • 访问🌿
  • 访问🌿的左子树:🍃
  1. 访问右子树:🌾

前序遍历结果:🌳 → 🌿 → 🍃 → 🌾


中序遍历(左根右)


  1. 访问左子树:
  • 访问🌿的左子树:🍃
  • 访问🌿
  1. 访问根节点:🌳
  2. 访问右子树:🌾

中序遍历结果:🍃 → 🌿 → 🌳 → 🌾


后序遍历(左右根)


  1. 访问左子树:
  • 访问🌿的左子树:🍃
  • 访问🌿
  1. 访问右子树:🌾
  2. 访问根节点:🌳

后序遍历结果:🍃 → 🌿 → 🌾 → 🌳


层序遍历(逐层从左到右)


  1. 第一层:🌳
  2. 第二层:🌿 → 🌾
  3. 第三层:🍃

层序遍历结果

  • 第一层:🌳
  • 第二层:🌿 → 🌾
  • 第三层:🍃


1.3 实战演练:构建你的第一个二叉树


让我们动手编写一个简单的二叉树实现,并进行前序遍历:

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
 
def preorder_traversal(node):
    if node:
        print(node.value, end=' ')
        preorder_traversal(node.left)
        preorder_traversal(node.right)
 
# 构建示例二叉树
root = TreeNode('🌳')
root.left = TreeNode('🌿')
root.right = TreeNode('🌾')
root.left.left = TreeNode('🍃')
 
# 前序遍历输出
preorder_traversal(root)
输出结果
    🌳
   /   \
 🌿     🌾
 /
🍃


第二部分:探索平衡之美——平衡树


2.1平衡树的定义与重要性


什么是平衡树?

平衡树,顾名思义,是一种内部结构平衡的树。它确保了树的高度保持在一个低水平,从而使得增加、删除、查找等操作都能在对数时间内完成。这种平衡通过确保树的每个节点的左右子树的高度差不超过特定的值来实现。


平衡树的应用场景

平衡树广泛应用于数据库索引、文件系统、网络路由等领域,几乎在任何需要快速数据检索的场合都能见到它的身影。


2.2 AVL树:平衡树的典范


AVL树的定义

AVL树是最早被发明的自平衡二叉搜索树。在AVL树中,任意节点的两个子树的高度最大差别为1,这使得AVL树几乎是完全平衡的。


AVL树的旋转操作详解

为了维持平衡,AVL树引入了旋转操作,主要有四种类型:左旋、右旋、左右旋和右左旋。

  • 左旋(Right Rotation) 🔄
    当一个节点的左子树比右子树矮时,我们进行左旋。
  • 右旋(Left Rotation) 🔄
    当一个节点的右子树比左子树矮时,我们进行右旋。
  • 左右旋和右左旋 🔄
    这是两步操作的组合,用于处理更复杂的不平衡情况。


2.3 实战演练:平衡树的构建与操作


现在,让我们来看看如何构建一个AVL树,并执行基本操作。


构建AVL树的步骤

  1. 插入节点:像普通的二叉搜索树一样插入节点。
  2. 检查平衡:插入后,沿着路径向上检查每个节点的平衡。
  3. 应用旋转:如果发现不平衡,根据不平衡的类型应用适当的旋转操作。


平衡树的插入示例代码

class TreeNode:
    def __init__(self, key, height=1):
        self.key = key
        self.left = None
        self.right = None
        self.height = height
 
class AVLTree:
    def insert(self, root, key):
        # 标准的二叉搜索树插入
        if not root:
            return TreeNode(key)
        elif key < root.key:
            root.left = self.insert(root.left, key)
        else:
            root.right = self.insert(root.right, key)
 
        # 更新高度
        root.height = 1 + max(self.getHeight(root.left),
                              self.getHeight(root.right))
 
        # 获取平衡因子
        balance = self.getBalance(root)
 
        # 不平衡调整
        # 左左
        if balance > 1 and key < root.left.key:
            return self.rightRotate(root)
        # 右右
        if balance < -1 and key > root.right.key:
            return self.leftRotate(root)
        # 左右
        if balance > 1 and key > root.left.key:
            root.left = self.leftRotate(root.left)
            return self.rightRotate(root)
        # 右左
        if balance < -1 and key < root.right.key:
            root.right = self.rightRotate(root.right)
            return self.leftRotate(root)
 
        return root
 
    def leftRotate(self, z):
        y = z.right
        T2 = y.left
        y.left = z
        z.right = T2
        z.height = 1 + max(self.getHeight(z.left),
                           self.getHeight(z.right))
        y.height = 1 + max(self.getHeight(y.left),
                           self.getHeight(y.right))
        return y
 
    def rightRotate(self, y):
        x = y.left
        T2 = x.right
        x.right = y
        y.left = T2
        y.height = 1 + max(self.getHeight(y.left),
                           self.getHeight(y.right))
        x.height = 1 + max(self.getHeight(x.left),
                           self.getHeight(x.right))
        return x
 
    def getHeight(self, root):
        if not root:
            return 0
        return root.height
 
    def getBalance(self, root):
        if not root:
            return 0
        return self.getHeight(root.left) - self.getHeight(root.right)

假设我们按顺序插入一系列的键值:9, 5, 10, 0, 6, 11, -1, 1, 2。我们将逐步展示每次插入后的树结构和必要的旋转操作。


首先,初始化AVL树并插入上述键值:

# 初始化AVL树
avl_tree = AVLTree()
root = None
 
# 插入键值
keys = [9, 5, 10, 0, 6, 11, -1, 1, 2]
for key in keys:
    root = avl_tree.insert(root, key)

初始状态(空树):

null

插入9:

插入5:

插入10:

插入0(无需旋转):

插入6(无需旋转):

插入11:

插入-1(无需旋转):

插入1,然后进行旋转调整:

插入2,导致需要进行左右旋转(左旋在5上,然后右旋在9上):


第三部分:红黑树——自平衡的艺术


红黑树是一种特殊的二叉搜索树,它通过一系列的规则和调整操作来保持树的平衡,确保在最坏的情况下仍能提供良好的时间复杂度。接下来,让我们一起深入探索红黑树的奥秘。


3.1 红黑树的基本原理


红黑树的定义


红黑树是每个节点都带有颜色属性的二叉搜索树,颜色或为红色或为黑色。但它不仅仅是一个普通的二叉搜索树,它通过一系列的性质来维护树的平衡。


红黑树的性质


红黑树维持平衡的关键在于五个基本性质:

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 所有叶子(NIL节点)都是黑色。
  4. 如果一个节点是红色的,则它的两个子节点都是黑色的。(即红色节点不能相邻)
  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

这些性质共同作用,确保了红黑树的高效性能。


3.2 红黑树的操作精讲


插入操作与调整机制


插入操作可能会破坏红黑树的性质,因此需要通过旋转和重新着色来恢复这些性质。插入操作大致可以分为以下几个步骤:

  1. 将新节点以红色插入,保证性质5不被破坏。
  2. 调整树,以恢复其他可能被破坏的性质。


现在展示一个比较难的插入示例:

初始树长这样

现在我们要在这棵树上面插入一个3的点

因为此时3和4都是红色 并且3是左节点 而3的父节点4是一个右子节点 所以要单向向右旋转(就是上图中的4和3节点顺时针旋转)变成下图这样

但是违背了红色节点不能相邻的原则  所以需要调整

就变成这样了  这样就不违反红黑树的性质了


上方的过程我们用代码实现如下

class Node:
    def __init__(self, value, color="RED"):
        self.value = value
        self.left = None
        self.right = None
        self.parent = None
        self.color = color
 
class RedBlackTree:
    def __init__(self):
        self.TNULL = Node(value=0, color="BLACK")  # TNULL代表NIL节点
        self.root = self.TNULL
 
    def insert(self, key):
        # 新节点总是以红色插入
        node = Node(key)
        node.left = self.TNULL
        node.right = self.TNULL
 
        parent = None
        current = self.root
 
        # 找到新节点的插入位置
        while current != self.TNULL:
            parent = current
            if node.value < current.value:
                current = current.left
            else:
                current = current.right
 
        # 设置新节点的父节点
        node.parent = parent
 
        if parent is None:  # 树是空的
            self.root = node
        elif node.value < parent.value:
            parent.left = node
        else:
            parent.right = node
 
        node.color = "RED"
        node.left = self.TNULL
        node.right = self.TNULL
 
        # 调整红黑树
        self.fix_insert(node)
 
    def rotate_left(self, x):
        y = x.right
        x.right = y.left
        if y.left != self.TNULL:
            y.left.parent = x
 
        y.parent = x.parent
        if x.parent is None:
            self.root = y
        elif x == x.parent.left:
            x.parent.left = y
        else:
            x.parent.right = y
        y.left = x
        x.parent = y
 
    def rotate_right(self, x):
        y = x.left
        x.left = y.right
        if y.right != self.TNULL:
            y.right.parent = x
 
        y.parent = x.parent
        if x.parent is None:
            self.root = y
        elif x == x.parent.right:
            x.parent.right = y
        else:
            x.parent.left = y
        y.right = x
        x.parent = y
 
    def fix_insert(self, k):
        while k != self.root and k.parent.color == "RED":
            if k.parent == k.parent.parent.right:
                u = k.parent.parent.left  # 叔叔节点
                if u.color == "RED":
                    # 情况1:叔叔节点是红色
                    u.color = "BLACK"
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    k = k.parent.parent
                else:
                    if k == k.parent.left:
                        # 情况2:当前节点是左孩子
                        k = k.parent
                        self.rotate_right(k)
                    # 情况3:当前节点是右孩子
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    self.rotate_left(k.parent.parent)
            else:
                # 同上,但方向相反
                u = k.parent.parent.right
 
                if u.color == "RED":
                    u.color = "BLACK"
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    k = k.parent.parent
                else:
                    if k == k.parent.right:
                        k = k.parent
                        self.rotate_left(k)
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    self.rotate_right(k.parent.parent)
        self.root.color = "BLACK"


删除操作


相信这个也是让很多小伙伴头疼的地方


删除操作是红黑树中较为复杂的一部分,因为它可能会破坏红黑树的性质。删除节点后,需要通过一系列的调整来恢复这些性质。删除操作主要分为以下几个步骤:


  • 查找并删除节点:首先,我们需要找到要删除的节点。如果该节点有两个子节点,通常的做法是用其后继节点(中序遍历下的下一个节点)来代替它,然后删除后继节点。这样,问题就转化为了删除最多只有一个子节点的节点。
  • 调整红黑树:如果被删除的节点或其替代节点是红色,通常不需要进一步的操作,因为红色节点的删除不会影响红黑树的黑高。但如果是黑色节点,那么我们需要进行调整来恢复红黑树的性质。


删除操作可能违反的红黑树性质包括:

  • 性质2:根节点是黑色。
  • 性质4:红色节点的子节点必须是黑色的。
  • 性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。


调整过程中,我们需要处理以下几种情况:


情况1:需要删除的节点是红色


1.1如下

删除22这种情况就不用说  直接删就好了 因为他是叶节点

但如果要删除30

首先 先二分查找找到30

找到以后  往左找 找比他小的

把这个找到的替代品复制一份 赋值到他原来的节点上,然后那个原先的25就去掉了

此时违背了红色节点不能相邻的原则 所以要调整

把22变成黑色  完成

情况2:兄弟节点是黑色,且兄弟的两个子节点都是黑色

 

将兄弟节点染成红色,并将父节点设置为新的当前节点,继续进行调整。


情况3:兄弟节点是黑色,且兄弟的左子节点是红色,右子节点是黑色


将兄弟节点染成红色,兄弟的左子节点染成黑色,并对兄弟节点进行右旋转,这样可以转换为情况4。


情况4:兄弟节点是黑色,且兄弟的右子节点是红色


将兄弟节点染成父节点的颜色,父节点染成黑色,兄弟的右子节点染成黑色,然后对父节点进行左旋转,最后将根节点染成黑色。


下面是一个简化的代码示例,展示了如何处理这些情况:

def fix_delete(self, x):
    while x != self.root and x.color == "BLACK":
        if x == x.parent.left:
            s = x.parent.right
            if s.color == "RED":
                s.color = "BLACK"
                x.parent.color = "RED"
                self.rotate_left(x.parent)
                s = x.parent.right
 
            if s.left.color == "BLACK" and s.right.color == "BLACK":
                s.color = "RED"
                x = x.parent
            else:
                if s.right.color == "BLACK":
                    s.left.color = "BLACK"
                    s.color = "RED"
                    self.rotate_right(s)
                    s = x.parent.right
 
                s.color = x.parent.color
                x.parent.color = "BLACK"
                s.right.color = "BLACK"
                self.rotate_left(x.parent)
                x = self.root
        else:
            # 同上,但是方向相反
            # 这里省略了与上面相似的代码
            pass
    x.color = "BLACK"


查找操作


在红黑树中,查找操作与二叉搜索树相同  其实就是二分查找

        🖤  (7)
       /   \
    🔴  (3)  🖤  (18)
           /   \
        🔴 (10)  🔴 (22)

在这个示例中,🖤 表示黑色节点,🔴 表示红色节点。这棵树满足红黑树的性质。


现在我们来搜索值为 10 的节点。

第一步:从根节点开始搜索,比较目标值 10 和当前节点值 7,由于 10 大于 7,因此我们向右子树搜索。

        🖤  (7)
       /   \
    🔴  (3)  🖤  (18)
           /   \
        🔴 (10)  🔴 (22)
                  ^

第二步:我们到达节点值为 18 的节点,继续比较目标值 10 和当前节点值 18,由于 10 小于 18,因此我们向左子树搜索。

        🖤  (7)
       /   \
    🔴  (3)  🖤  (18)
           /   \
       🔴 (10)  🔴 (22)
        ^

第三步:我们到达节点值为 10 的节点,发现目标值和当前节点值相等,搜索结束,找到了目标节点。

下面是实现的代码

class Node:
    def __init__(self, key, color='Red'):
        self.key = key
        self.color = color
        self.left = None
        self.right = None
 
class RedBlackTree:
    def __init__(self):
        self.root = None
 
    def search(self, key):
        return self._search(self.root, key)
 
    def _search(self, node, key):
        if not node or node.key == key:
            return node
        if key < node.key:
            return self._search(node.left, key)
        return self._search(node.right, key)


遍历操作


        🖤  (7)
       /   \
    🔴  (3)  🖤  (18)
           /   \
        🔴 (10)  🔴 (22)


中序遍历

的顺序是 3, 7, 10, 18, 22。

步骤:

  1. 访问节点 3
  2. 访问节点 7
  3. 访问节点 10
  4. 访问节点 18
  5. 访问节点 22


前序遍历

前序遍历按照根左右的顺序访问节点。

步骤:

  1. 访问节点 7
  2. 访问节点 3
  3. 访问节点 18
  4. 访问节点 10
  5. 访问节点 22


后序遍历

后序遍历按照左右根的顺序访问节点。

步骤:

  1. 访问节点 3
  2. 访问节点 10
  3. 访问节点 22
  4. 访问节点 18
  5. 访问节点 7
相关文章
|
20天前
|
算法 架构师 NoSQL
「AVL平衡树专项」带你领略常用的AVL树与红黑树的奥秘(规则篇)
原先属于X的空结点被A“继承”。如果被删除结点是黑色结点,则可能造成树的黑色高度变化。
395 0
|
1月前
|
存储 算法 编译器
【C++入门到精通】C++入门 —— 红黑树(自平衡二叉搜索树)
【C++入门到精通】C++入门 —— 红黑树(自平衡二叉搜索树)
19 1
|
1月前
|
关系型数据库 C++
【C++高阶(四)】红黑树深度剖析--手撕红黑树!
【C++高阶(四)】红黑树深度剖析--手撕红黑树!
|
1月前
|
C++
【数据结构&C++】超详细一文带小白轻松全面理解 [ 二叉平衡搜索树-AVL树 ]—— [从零实现&逐过程分析&代码演示&简练易懂]
【数据结构&C++】超详细一文带小白轻松全面理解 [ 二叉平衡搜索树-AVL树 ]—— [从零实现&逐过程分析&代码演示&简练易懂]
|
前端开发 算法 API
[LeetCode算法]有了二叉树层序遍历,妈妈再也不用担心我不会做二叉树层级题了
博主最近在刷`leetcode`,做到二叉树套题的时候发现很多题的解题思路都是基于二叉树的层序遍历来完成的,因此写下这篇文章,记录一下二叉树层序遍历这件"神器"在实战的运用。
122 1
|
存储 机器学习/深度学习 Java
《Java数据结构》这些树和二叉树的性质你还记得吗?
《Java数据结构》这些树和二叉树的性质你还记得吗?
整理得吐血了,二叉树、红黑树、B&B+树超齐全,快速搞定数据结构
没有必要过度关注本文中二叉树的增删改导致的结构改变,规则操作什么的了解一下就好,看不下去就跳过,本文过多的XX树操作图片纯粹是为了作为规则记录,该文章主要目的是增强下个人对各种常用XX树的设计及缘由的了解,也从中了解到常用的实现案例使用XX树实现的原因。
|
存储 算法 前端开发
日拱算法之不能不知道的“红黑树”
不知道前端小伙伴们都了解“红黑树”吗?本瓜,之前听是听过,但是它到底是干嘛的,并不十分清楚。在认识了平衡二叉树、AVL 树之后,现在已经来到了这个节点,必须来看下“红黑树”了!
|
算法 网络安全 C++
【数据结构之旅】「AVL平衡树专项」带你领略常用的AVL树与红黑树的奥秘(规则篇)
【数据结构之旅】「AVL平衡树专项」带你领略常用的AVL树与红黑树的奥秘(规则篇)
141 0
【数据结构之旅】「AVL平衡树专项」带你领略常用的AVL树与红黑树的奥秘(规则篇)
二叉树的最近公共祖先(中等难度)(面试常考)!!
二叉树的最近公共祖先(中等难度)(面试常考)!!
65 0
二叉树的最近公共祖先(中等难度)(面试常考)!!