特殊二叉树和平衡二叉树和树的遍历

参考文章:https://www.cnblogs.com/lfalex0831/p/9698249.html

参考文章:https://www.cnblogs.com/utank/p/4256133.html

参考文章:https://blog.csdn.net/qq_42730750/article/details/108285846

 

一、介绍

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个结点最多只能有两棵子树,且有左右之分。

 

二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。也就是说二叉树是每个结点最多有两个子树的数据结构。

 

二叉树的基本形态

  1. 空二叉树
  2. 只有一个根结点的二叉树
  3. 只有左子树;
  4. 只有右子树
  5. 完全二叉树

如下图所示

 

二叉树相关术语

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 
  • 叶节点:也叫终端节点,即度为0的节点; 
  • 分支节点:度不为0的节点; 
  • 父节点:也叫双亲节点,若一个节点含有子节点,则这个节点称为其子节点的父节点,例如:B 结点是 A 结点的孩子,则A结点是B结点的双亲; 
  • 子节点:也叫孩子节点,一个节点含有的子树的根节点称为该节点的子节点; 
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 
  • 树的度:一棵树中,最大的节点的度称为树的度; 
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推; 
  • 树的高度或深度:树中节点的最大层次; 
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟; 
  • 节点的祖先:从根到该节点所经分支上的所有节点; 
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  • 森林:由m(m>=0)棵互不相交的树的集合称为森林;
  • 路径和路径长度:从结点n1到nk的路径为一个结点序列n1,n2,...,nk。ni是ni+1的父结点。路径所包含边的个数为路径的长度

 

【二叉树的几个性质】

  1. 一个二叉树第i层的最大结点数为:2i-1,i≥1;
  2. 深度为k的二叉树有最大结点总数为:2k-1,k≥1;
  3. 对任何非空二叉树T,若n0表示叶结点的个数,n2是度为2的非叶结点个数,那么两者满足关系:n0=n2+1;

    假设:叶结点个数为n0;度为1的结点个数为n1;度为2的结点个数为n2

    则二叉树的总边数N=2*n2+n1;总结点数N′=n0+n1+n2

    因N+1=N′,所以2*n2+n1+1=n0+n1+n2;得n0=n2+1。

 

二、二叉树特殊类型

1、斜二叉树

斜二叉树:只有左子节点或只有右子节点的二叉树称为斜二叉树;

 

斜二叉树特点:
  • 度为1;
  • 只有左子节点或右子节点;

 

2、满二叉树

满二叉树(完美二叉树):除最后一层无任何子节点外,每一层上的所有结点都有两个子结点;

 

 

满二叉树特点:
  • 叶子结点只能在最后一层;
  • 非叶子节点的结点的度为2;

 

3、完全二叉树

完全二叉树:有n个结点的二叉树,对树中的结点从上至下、从左到右顺序进行编号,编号为i(1≤i≤n)结点与满二叉树中编号为i结点在二叉树中的位置相同

 

完全二叉树的顺序存储结构特点:

  1. 根结点的序号为1;
  2. 结点(序号为i)的左孩子结点的序号是:2 * i,若2*i > n,则没有左孩子;
  3. 结点(序号为i)的右孩子结点的序号是:2 * i + 1,若2*i+1 > n,则没有右孩子;

 

注意:满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树;

 

4、线索二叉树

对于二叉树,无论是何种,叶子节点左右两边都是空链域,切空链域的个数还很多。准确的说,n各结点的二叉链表共有2n个链域,非空链域为n-1个,但其中的空链域却有n+1个。

那么怎么避免空叶子节点的空间浪费呢?

 

而线索二叉树是在上面null的位置放入遍历时的前驱结点和后继结点,如下图:

 

上图只是其中一种线索,黑线代表前驱,红线代表后继。

 

线索:利用原来的空链域存放指针,指向树中其他结点。这种指针称为线索。
实际就是为了解决无法直接找到该结点在某种遍历序列中的前驱和后继结点的问题。

 

为了避免混淆,尚需改变结点结构,增加两个标志域。

lchild LTag data(数据) RTag rchild
其中:
    LTag=0  lchild域指示结点的左孩子
    LTag=1  lchild域指示结点的前驱
    RTag=0  rchild域指示结点的右孩子
    RTag=1  rchild域指示结点的后继

例如:

 

代码实现:

二叉线索链表节点的定义如下:

class ThreadNode(object):
    def __init__(self, data='#'):
        self.data = data
        self.lchild = None
        self.rchild = None
        self.ltag = 0
        self.rtag = 0

以某种次序遍历将二叉树变为线索二叉树的过程称为线索化
线索化的实质就是当二叉树中某节点不存在左孩子或右孩子时,将其lchild域或rchild域指向该节点的前驱或后继。

普通二叉树

 

改成线索二叉树

 

 

以这棵二叉树为例,来看一下线索二叉树是如何构造的

1.先序遍历线索二叉树

上述二叉树的先序遍历为:A B D E C F ABDECFABDECF

 

上面这棵就是先序线索二叉树,是通过先序遍历构造的,根据原二叉树的二叉链表可知,空指针(即度为1的节点或叶子节点的孩子指针域)得到了有效利用。
  先序线索二叉树寻找节点的后继的过程如下:
  (1) 如果该节点有左孩子,则左孩子就是该节点的后继;
  (2) 如果该节点无左孩子但有右孩子,则右孩子就是该节点的后继;
  (3) 如果该节点即无左孩子又无右孩子(即叶子节点),则右链域指向的节点就是该节点的后继。

 

2.后序线索二叉树

上述二叉树的后序遍历为:D E B F C A

 

 后序线索二叉树寻找节点的后继的过程如下:
  (1) 如果该节点是二叉树的根,则该节点的后继为空;
  (2) 如果该节点是其双亲的右孩子,或者是其双亲的左孩子且双亲没有子树,则该节点的后继即为双亲;
  (3) 如果该节点是其双亲的左孩子,且其双亲有右子树,则该节点的后继为双亲的右子树上按后序遍历得到的第一个节点。

先序线索二叉树、后序线索二叉树寻找节点的前驱也都复杂,这里也就不介绍了,不常用,最常用的是下面要介绍的中序线索二叉树。

 

3.中序线索二叉树

上述二叉树的后序遍历为:D B E A F C

中序线索二叉树的建立如下:
  令指针PreNode指向刚刚访问过的节点,指针RootNode指向正在访问的节点,即PreNode指向RootNode的前驱。在中序遍历过程中,先判断RootNode是否有左孩子,若没有左孩子就将它的lchild指向PreNode;然后再判断PreNode是否有右孩子,若没有右孩子就将它的rchild指向RootNode
  为了方便,可以在二叉树的线索链表上添加一个头节点,令其lchild域的指针指向二叉树的根节点,其rchild域的指针指向中序遍历时访问的最后一个节点(即最右边的那个节点);然后再令二叉树中序序列中的第一个节点(即二叉树最左边的那个节点)的lchild域指针和最后一个节点的rchild域指针均指向头节点。没错,这是二叉树的双向线索链表。

 

三、二叉树的遍历

二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中所有节点,使得每个节点被访问依次且仅被访问一次。


二叉树的遍历次序不同于线性结构,线性结构最多也就是分为顺序、循环、双向等简单的遍历方式。
而树不存在唯一的后继节点,在访问一个节点后,下一个被访问的节点面临着不同的选择,所以我们需要规范遍历方式

 

三种遍历方法:

  1. 前序遍历:根-左-右 
  2. 中序遍历:左-根-右
  3. 后序遍历:左-右-根

记忆点:前中后指的是根节点

 

1、前序遍历

定义:先访问根节点,然后访问左子树,再访问右子树

按照定义遍历的顺序遍历结果为:A B D H I E J C F K G

代码实现:

# 前序遍历
# 递归
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def tree(node):
            if not node:
                return
            res.append(node.val)
            tree(node.left)
            tree(node.right)
        tree(root)
        return res


# 迭代
class Solution:
    """
    它先将根节点 cur 和所有的左孩子入栈并加入结果中,直至 cur 为空,用一个 while 循环实现:
    然后,每弹出一个栈顶元素 tmp,就到达它的右孩子,再将这个节点当作 cur 重新按上面的步骤来一遍,直至栈为空。这里又需要一个 while 循环。
    """
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        
        cur, stack, res = root, [], []
        while cur or stack:
            while cur:  # 根节点和左孩子入栈
                res.append(cur.val)
                stack.append(cur)
                cur = cur.left
            tmp = stack.pop()  # 每弹出一个元素,就到达右孩子
            cur = tmp.right
        return res

 

2、中序遍历

定义:先访问左子树,再访问根节点,最后访问右子树

按照定义遍历的顺序遍历结果为:H D I B E J A F K C G

代码实现:

# 中序遍历
# 递归
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def tree(node):
            if not node:
                return
            tree(node.left)
            res.append(node.val)
            tree(node.right)
        tree(root)
        return res



# 迭代
class Solution:
    """
    和前序遍历的代码完全相同,只是在出栈的时候才将节点 tmp 的值加入到结果中。
    """
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        
        cur, stack, res = root, [], []
        while cur or stack:
            while cur:  # 根节点和左孩子入栈
                stack.append(cur)
                cur = cur.left
            tmp = stack.pop()
            res.append(cur.val)  # 出栈再加入结果
            cur = tmp.right
        return res

 

3、后序遍历

定义:先访问左子树,再访问右子树,最后访问根节点

按照定义遍历的顺序遍历结果为:H I D J E B K F G C A

代码实现:

 

# 后续遍历
# 递归
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def tree(node):
            if not node:
                return
            tree(node.left)
            tree(node.right)
            res.append(node.val)
        tree(root)
        return res



# 迭代
class Solution:
    """
    继续按照上面的思想,这次我们反着思考,节点 cur 先到达最右端的叶子节点并将路径上的节点入栈;
    然后每次从栈中弹出一个元素后,cur 到达它的左孩子,并将左孩子看作 cur 继续执行上面的步骤。
    最后将结果反向输出即可。参考代码如下:
    """
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        if not root:
            return []
        
        cur, stack, res = root, [], []
        while cur or stack:
            while cur:  # 先达最右端
                res.append(cur.val)
                stack.append(cur)
                cur = cur.right
            tmp = stack.pop()
            cur = tmp.left
        return res[::-1]

 

4、层次遍历

定义:逐层的从根节点开始,每层从左至右遍历;
按照定义遍历的顺序遍历结果为:A B C D E F G H I J K

代码实现:

 

四、平衡树

平衡树(Balance Tree,BT) 指的是,任意节点的子树的高度差都小于等于1。常见的符合平衡树的有,B树(多路平衡搜索树)、AVL树(二叉平衡搜索树)等。

平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。

参考文章:https://www.cnblogs.com/suimeng/p/4560056.html

 

1、平衡二叉树-AVL Tree

平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。

AVL树首先是一种二叉查找树(也可以叫二叉排序树)二叉查找树是这么定义的,为空或具有以下性质:

  • 若它的左子树不空,则左子树上所有的点的值均小于其根节点
  • 若它的右子树不空,则右子树上所有的点的值均大于其根节点
  • 它的左右子树分别为二叉查找树

AVL 树是具有以下性质的二叉查找树:

  • 左子树和右子树的高度差(平衡因子)不能超过 1
  • 左子树和右子树都是 AVL树

例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况。有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本。而只有建立的树如图2,才能够最大地体现二叉树的优点。

 

在上述的例子中,图2就是一棵平衡二叉树。科学家们提出平衡二叉树,就是为了让树的查找性能得到最大的体现(至少我是这样理解的,欢迎批评改正)。下面进入今天的正题,平衡二叉树。

 

平衡二叉树的定义(特性):

  1. 可以是空树。
  2. 假如不是空树,任何一个节点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过1

平衡之意,如天平,即两边的分量大约相同。如定义,假如一棵树的左右子树的高度之差超过1,如左子树的树高为2,右子树的树高为0,子树树高差的绝对值为2就打破了这个平衡。如依次插入1,2,3三个节点(如下图)后,根节点的右子树树高减去左子树树高为2,树就失去了平衡。

 

 

那么在建立树的过程中,我们如何知道左右子树的高度差呢?在这里我们采用了平衡因子进行记录。

平衡因子以某个节点为根,这个节点的左子树的高度减去右子树的高度。由平衡二叉树的定义可知,平衡因子的取值为0,1,-1的都是平衡二叉树,分别对应着左右子树等高,左子树比较高,右子树比较高。如下图

 

说到这里,我们已经可以大概知道平衡二叉树的结构定义需要什么内容了,数据成员,平衡因子,以及左右分支。

 

如何判断二叉树是否为平衡树

前置知识:二叉树的深度

# 节点定义如下
# Definition for a binary tree node.
class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
n3 = TreeNode(3)
n9 = TreeNode(9)
n20 = TreeNode(20)
n15 = TreeNode(15)
n7 = TreeNode(7)

n3.left = n9
n3.right = n20
n20.left = n15
n20.right = n7
n3 = TreeNode(3)
n9 = TreeNode(9)
n20 = TreeNode(20)
n15 = TreeNode(15)
n7 = TreeNode(7)

n3.left = n9
n3.right = n20
n20.left = n15
n20.right = n7
Solution().maxDepth(n3)

 

# 方法一:每到一个节点,深度就+1
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        return self.get_dep(root, 1)

    def get_dep(self, node, depth):
        if not node:
            return depth - 1
        return max(self.get_dep(node.left,depth+1),self.get_dep(node.right,depth+1))


# 方法二:
class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if root is None:
            return 0
        else:
            left_depth = self.maxDepth(root.left)
            right_depth = self.maxDepth(root.right)
        return max(left_depth,right_depth)+1
算法解析:
终止条件: 当 root​ 为空,说明已越过叶节点,因此返回 深度 0 。
递推工作: 本质上是对树做后序遍历,从叶子节点一层层向上加。
计算节点 root​ 的 左子树的深度 ,即调用 maxDepth(root.left);
计算节点 root​ 的 右子树的深度 ,即调用 maxDepth(root.right);
返回值: 返回 此树的深度 ,即 max(maxDepth(root.left), maxDepth(root.right)) + 1。
方法二解析

 

判断这个二叉树是否为平衡二叉树

# 方法一
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root:
            # 空树也是平衡树
            return True
        left_height = self.get_dep(root.left, 1)
        right_height = self.get_dep(root.right, 1)
        if abs(left_height-right_height) > 1:
            return False
        else:
            return self.isBalanced(root.left) and self.isBalanced(root.right)

    def get_dep(self, node, depth):
        if not node:
            return depth - 1
        return max(self.get_dep(node.left,depth+1),self.get_dep(node.right,depth+1))


# 方法二
class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root:
            return True

        left_height = self.depth(root.left)
        right_height = self.depth(root.right)
        if abs(left_height - right_height) > 1:
            return False
        else:
            return self.isBalanced(root.left) and self.isBalanced(root.right)

    def depth(self, root):
        if not root: 
            return 0

        return max(self.depth(root.left), self.depth(root.right)) + 1

 

2、AVL树的插入时的失衡与调整

失衡与调整的引导

说了这么久,我们开始进入今天的重点,如何将一棵不平衡的二叉树变成平衡二叉树(只讨论不平衡的是因为假如树是平衡的就不必我们进行处理)。平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的

 

最小失衡子树在新插入的节点向上查找以第一个平衡因子的绝对值超过1的节点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的,如下。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。

在图7中。2节点(左子树树高-右子树树高)的绝对值=2。同理,3节点的平衡因子也为2.此时同时存在了两棵不平衡子树,而以3为根的树是最小的不平衡子树。我们只要将其以3为中心,将最小不平衡树向左旋转,即可得到平衡二叉树,如图8。具体方法后续讲解。

 

失衡与处理详解

版权声明:本文为ssf_cxdm原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/ssf_cxdm/article/details/81607235

 

4种常见的调整方式
  1. 左单旋,也叫LL型,以中为支,高右转
  2. 右单旋,也叫RR型,以中为支,高左转
  3. 左右双旋,也叫LR型,下二整体先左转,后与LL相同
  4. 右左双旋,也叫RL型,下二整体先右转,后与RR相同

LL的旋转。LL失去平衡的情况下,可以通过一次旋转让AVL树恢复平衡。步骤如下:

1、将根节点的左孩子作为新根节点。
2、将新根节点的右孩子作为原根节点的左孩子。
3、将原根节点作为新根节点的右孩子。

LL旋转示意图如下:
这里写图片描述

 

RR的旋转:RR失去平衡的情况下,旋转方法与LL旋转对称,步骤如下:

1、将根节点的右孩子作为新根节点。
2、将新根节点的左孩子作为原根节点的右孩子。
3、将原根节点作为新根节点的左孩子。
RR旋转示意图如下:
这里写图片描述

 

LR的旋转:LR失去平衡的情况下,需要进行两次旋转,步骤如下:

1、围绕根节点的左孩子进行RR旋转。
2、围绕根节点进行LL旋转。
LR的旋转示意图如下:
这里写图片描述

 

RL的旋转:RL失去平衡的情况下也需要进行两次旋转,旋转方法与LR旋转对称,步骤如下:

1、围绕根节点的右孩子进行LL旋转。
2、围绕根节点进行RR旋转。
RL的旋转示意图如下:
这里写图片描述

 

3.插入时失衡与调整的总结
  1. 在所有的不平衡情况中,都是按照“寻找最小不平衡树”->“寻找所属的不平衡类别”->“根据4种类别进行固定化程序的操作”。
  2. LL,LR,RR,RL其实已经为我们提供了最后哪个结点作为新的根指明了方向。如LR型最后的根结点为原来的根的左孩子的右孩子,RL型最后的根结点为原来的根的右孩子的左孩子。我们只要记住这四种情况,可以很快地推导出所有的情况。
  3. 维护平衡二叉树,最麻烦的地方在于平衡因子的维护。想要熟悉这个过程,建议读者多多画图,在感官上首先体验这个过程。

说到这里,我们已经了解了了解了什么是平衡二叉树,插入结点后如何调整平衡二叉树。我们数据结构中经常讲到的有增删查改,那么下面我们来讲解一下如何删除。

 

3、AVL树的删除时的失衡与调整

1.预备知识

1.树的删除

假如有一棵二叉查找树如下,我们对它进行中序遍历,可以得到1, 2, 2.5, 3。我们发现,这是一个递增的序列。假如我们现在要删除的结点为3,在不考虑树的平衡问题时,应该哪个结点来作为顶替3的位置呢呢?答案是:对排序二叉树进行中序遍历时,3的直接前驱或者直接后驱。在这里,就是2.5,所以删除后,不进行调整的结果如中间图。假如我们现在要删除的结点为2,在不考虑树的平衡问题时,1顶替2的位置(假设左孩子优先于右孩子)。最后如下右图。

具体的步骤如下:

      i.  寻找到要删除的结点(3)

     ii.  将待删除结点的直接前驱或者直接后驱赋值给待删除结点(2.5赋值给3结点)

    iii.  将直接前驱或者直接后驱删除(将叶子结点的2.5删除)

         

     由于我们今天主要讲的是平衡二叉树的平衡调整,所以这部分就权当给读者恶补一下。假如读者还是不能理解,请先查看一下二叉查找树的删除,再继续往下看。

2.平衡因子的预告

  我们已经知道,平衡因子有且仅有三种取值,LH,RH,EH。对于如下的一棵树,删除一个结点后

  a)   原本树左右子树等高。根平衡因子的取值变化为EH->LH,EH->RH。

  b)   原本树左右子树不等高,在较高的子树上进行删除,根平衡因子的取值变化为LH->EH,RH->EH。需要注意的是,当根的平衡因子变化为LH->EH,RH->EH时整棵树的高度是下降的。最简单的例子如下。以下两棵树,分别删除1,3后,平衡因子LH->EH,RH->EH。最后树的高度都下降了。

      

  c)  原本树左右子树不等高,在较低的子树上进行删除,此时需要对树进行平衡处理。如下删除了结点1,得到右边的不平衡树。

      

3. 什么会导致树高降低

  a)   如第2点的的第b项,根的平衡因子由LH->EH,RH->EH时整棵树的高度是下降的。

  b)   建立在a点以及平衡处理正确的基础上,对树进行正确的平衡处理后,树高会降低。为什么呢?因为其实最小不平衡子树进行旋转后,最小不平衡子树根的平衡因子总是变

  为EH,或者说,平衡调整总是降低了最小不平衡子树的高度。举例如下。树的高度由原来的3变为了2.

    

2.正式进入AVL树的删除与调整

1. 删除结点导致平衡二叉树失衡

  AVL树也是一棵二叉查找树,所以它的删除也是建立在二叉查找树的删除之上的,只是,我们需要在不平衡的时候进行调整。而我们在预备知识的第2点中的C项中已经提及到,假如我们在较低的子树上进行删除,将会直接导致不平衡树的出现。那么,我们需要进行平衡处理的,就在于此种情况。举个栗子。

    

2. 调整不平衡子树后,导致了更大的不平衡子树

  假设最小不平衡子树为A,它为双亲结点b的左子树,而b的平衡因子为RH。假设我们现在对A进行了平衡处理,如上所讲,进行平衡处理将导致树高降低。即我们让b较矮的子树变得更矮了。此时对于b而言,同样也是不平衡的。此时,我们需要再一次进行一次平衡处理。举个栗子如下。

  假设我们删除了结点6.那么最小不平衡子树就是1,3,5对应的二叉树。它的双亲10的平衡因子为RH。我们首先对最小不平衡子树进行调整,结果如右图。我们发现,最小不平衡子树从根结点的左子树变成了整棵树,所以这个时候我们又要进行一次平衡调整。具体的平衡调整步骤与插入时是一致的,在这里就赘述。

       

  在讲解插入新的结点进行平衡时,说到删除时与插入时不有着很大的不同就在于此。插入时,进行一次平衡处理,整棵树都会处于平衡状态,而在删除时,需要进行多次平衡处理,才能保证树处于平衡状态。

  细心的朋友可能发现,上面右图中,最小不平衡子树的较高子树的平衡因子为EH。这个时候,就出现了前面插入时提及的不可能出现的失衡情况。

3.失衡与调整的最后一种情况LE与RE

  LE与RE型的失衡树,在进行调整的时候,和LL与RR型的旋转方式是一致的。只是最后初始根结点的平衡因子不为EH而已。就拿上面的例子而言,调整后的结果如下。初始根结点的平衡因子为RH。相对应的,假如是LE的情况,调整后初始根结点的平衡因子为LH。

          

 

posted @ 2020-09-12 11:15  我用python写Bug  阅读(508)  评论(1编辑  收藏  举报