平衡二叉树(AVL)python代码实现,注释超级详细

本文介绍了二叉排序树(BST)的概念及其非递归插入、查询和删除操作的Python代码实现。接着,详细阐述了AVL树的平衡因子和四种旋转操作,以及在AVL树中插入节点的处理过程。最后,提供了AVL树的Python实现,并通过示例展示了插入和遍历操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要了解AVL,就要先了解二叉排序树,二叉排序树(BST)的定义就是:
右子树的所有关键字均不小于根关键字的值。

  • 若它的左子树不空,则左子树上的所有关键字的值均不大于根关键字的值。
  • 若它的右子树不空,则右子树上的所有关键字的值均不小于根关键字的值。
  • 左右子树又各是一颗二叉排序树

二叉排序树和AVL树的中序遍历都是升序(或者降序)序列
写者的二叉排序树基于里面的元素不重复。下面是二叉排序树建立插入删除。话不多说直接上代码

# -*- coding : utf-8 -*-
# @Author : zhu
# @Time :  2022/4/26 18:04
import random


class BiTreeNode(object):
    # 先来定义一下树里面的节点
    def __init__(self, data):
        self.lchild = None  # 二叉树左子树
        self.rchild = None  # 二叉树右子树
        self.data = data  # 二叉树节点的值
        self.parent = None  # 让结点能找到父节点 二叉树查询和插入用不到,删除能用到


class BTS(object):
    # 二叉树排序树
    def __init__(self, li=None):  # 我们允许构造时候插入一个列表li
        self.root = None  # 根节点 类型是BiTreeNode类型
        if li:  # 有li我们就插入 插入我们用的是非递归插入
            for val in li:
                self.insert_no_rec(val)

    def insert_no_rec(self, val):
        # 非递归插入
        p = self.root  # p的类型是BiTreeNode类型的
        if not p:  # 它根都没有
            self.root = BiTreeNode(val)
            return
        while True:
            if p.data > val:  # 插入的值比较小,放到p节点的左边
                if p.lchild:  # 我们插入时候只能插入到叶子节点,一直往下找它
                    p = p.lchild  # 运行完我们就进行了下一次的while循环
                else:  # 能从左边插入 且插入的是叶子
                    p.lchild = BiTreeNode(val)  # 左边插上
                    p.lchild.parent = p  # 与他父亲建立关系
                    return
            elif p.data < val:  # 这个和上面同理。只不过是右边插入
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = BiTreeNode(val)
                    p.rchild.parent = p
                    return
            else:  # 若插入的值树里面有,我们不插入它
                return

    def query_no_rec(self, val):  # 非递归查询
        p = self.root
        while p:  # 有p才能找
            if p.data > val:  # 值小去左边查
                p = p.lchild
            elif p.data < val:  # 值大去右边
                p = p.rchild
            else:  # 找到!
                return p
        return None  # 退出循环了说明啥也没找到

    # 删除有三种情况 node_1.删除的是叶子  node_21.删除的只有一个左孩子 node_22.删除的只有一个右孩子
    # node_3.删除的有左孩子右孩子,这种情况下我们从删除的节点的右孩子一直往左找,这样我们就会得到和目标节点相差最小的点(或者从被删除的节点的左孩子一直向右找)
    def __remove_node_1(self, node):
        # 第一种情况 node是要被删除的,node是叶节点
        if not node.parent:  # 若删除是根节点,考虑一下
            self.root = None
        elif node.parent.lchild == node:  # 如果是父节点的左孩子
            node.parent.lchild = None  # 父节点左孩子删去
        else:
            node.parent.rchild = None

    def __remove_node_21(self, node):
        # 第二种情况的2.1   node仅有个左孩子 node是要被删除的
        if not node.parent:  # 同样是根节点
            self.root = node.lchild
            node.lchild.parent = None  # 让它父亲变为空
        elif node.parent.lchild == node:
            # 看到node在父亲的左子树上
            node.parent.lchild = node.lchild  # node父亲连node的孩子
            node.lchild.parent = node.parent  # node孩子连node父亲
        else:  # 看到node在父亲为右子树上 和上面的elif类似的思想
            node.parent.rchild = node.lchild
            node.lchild.parent = node.parent

    def __remove_node_22(self, node):
        # 第二种情况2.2  node仅有一个右孩子 那么思想和上面node_21一样 写者就省略了
        if not node.parent:
            self.root = node.rchild
            node.rchild.parent = None
        elif node.parent.lchild == node:
            node.parent.lchild = node.rchild
            node.rchild.parent = node.parent
        else:
            node.parent.rchild = node.rchild
            node.rchild.parent = node.parent

    def delect(self, val):
        # 真正外部用到的删除函数
        if not self.root:  # 树为空特殊处理一下
            raise ValueError("is_entry_tree")
        else:
            p = self.query_no_rec(val)  # 返回要删除的节点
            if not p:  # 没找到特殊处理
                raise ValueError("can_not_find_this_value")
            elif not p.lchild and not p.rchild:
                # 没有孩子的情况
                self.__remove_node_1(p)
            elif not p.rchild:
                # p仅有左子树情况下
                self.__remove_node_21(p)
            elif not p.lchild:
                # 仅有右子树
                self.__remove_node_22(p)
            else:
                # p左右子树都有,进行第三种情况
                min_node = p.rchild
                while min_node.lchild:
                    # 有左孩子 一直走
                    min_node = min_node.lchild
                p.data = min_node.data  # 把值给p填上
                # 删除min_node节点,只可能是情况1或者情况2.2
                if min_node.rchild:
                    self.__remove_node_22(min_node)
                else:
                    self.__remove_node_1(min_node)

    def pre_order(self, root):
        # 递归实现一下打印,先序遍历
        if root:
            print(root.data, end=",")
            self.pre_order(root.lchild)
            self.pre_order(root.rchild)

    def in_order(self, root):
        # 中序遍历
        if root:
            self.in_order(root.lchild)
            print(root.data, end=",")
            self.in_order(root.rchild)


bts = BTS([random.randint(0, 20) for i in range(10)])  # 随机10个数试一下
# bts.pre_order(bts.root)
bts.in_order(bts.root)  # 我们可以通过先序和中序画出树

接下来是AVL实现,它的实现比二叉搜索树多个平衡因子balance factor(下面简写bf)。只有每个节点的bf = -1,0,1 我们才称为二叉搜索树。在本文中我们的bf=右子树的深度-左子树的深度(即右边沉bf=1)。二叉搜索树的插入有四种情况。左旋、右旋、左旋右旋、右旋左旋。下图是三种情况

在这里插入图片描述

对于左旋右旋我们可以用巧法记,即插入A使得不平衡,那么让A当父亲,当了父亲两个孩子都不要了 ,分别分给旁边的两颗子树,同理右旋左旋一样。对于插入还有不懂得可以询问我或者看看资料,我们注重代码的实现。

# -*- coding : utf-8 -*-
# @Author : zhu
# @Time :  2022/4/26 23:23

from test08 import BTS, BiTreeNode  # test08是上一个代码.py建立的名字


# 我们二叉平衡树能用到BTS和BiTreeNode的数据结构,我们可以继承一下
class AVLNode(BiTreeNode):
    def __init__(self, data):
        BiTreeNode.__init__(self, data)
        self.bf = 0  # 平衡因子


class AVLTree(BTS):
    def __init__(self, li=None):
        BTS.__init__(self, li)  # 后面我们会重写插入操作的

    def __rotate_left(self, p, c):
        """
        左旋--->由右子树的右孩子多了造成的
        :param p: 图中的p节点
        :param c: 图中的c节点
        s1 s2 s3 依次是节点带的小二叉树,看着图写代码最合适
        :return: 返回旋转过后 当前子树 的根节点
        我们会发现转移过后p,c平衡因子都变为0了。
        (注意:若是删除左旋 那么和插入的左旋的bf值有可能不同,笔者删除还没写)
        """
        # s2 与 p相连
        s2 = c.lchild
        p.rchild = s2
        if s2:  # 有s2我们才连父亲
            s2.parent = p
        # c 与 p相连
        c.lchild = p
        p.parent = c
        # 更新bf
        p.bf, c.bf = 0, 0
        return c

    def __rotate_right(self, p, c):
        # 右旋--->由左子树的左孩子多了造成的,代码和上面类似
        s2 = c.rchild
        p.lchild = s2
        if s2:
            s2.parent = p
        c.rchild = p
        p.parent = c
        c.bf, p.bf = 0, 0
        return c

    def __rotate_left_right(self, p, c):
        # 左旋右旋--->由左子树的右孩子多了造成的
        # 1.a 是 c右孩子 要变成该子树的根
        a = c.rchild
        s2 = a.lchild
        s3 = a.rchild
        # 2.我们要让s2,s3与c,p建立关系
        c.rchild = s2
        p.lchild = s3
        if s2:
            s2.parent = c
        if s3:
            s3.parent = p
        # 3.我们让a与cp建立关系
        a.lchild = c
        c.parent = a
        a.rchild = p
        p.parent = a
        # 4.更新bf 这时候我们得分三种情况
        # 这三种情况分别是 1.s2上加了key,也就是说高度为h+2
        # 2.s3上加了key,高度为h+2   3. s2.s3高度都为h+1,也就是说插入的是a
        # 这三种情况不论怎么样,旋转完之后a.bf都为0
        if a.bf < 0:
            c.bf = 0
            p.bf = 1
        elif a.bf > 0:
            c.bf = -1
            p.bf = 0
        else:
            c.bf = 0
            p.bf = 0
        a.bf = 0
        return a

    def __rotate_right_left(self, p, c):
        # 右旋左旋--->由右子树的左孩子多了造成的
        # 和上面代码类似的思想
        a = c.lchild
        s2 = a.lchild
        s3 = a.rchild
        p.rchild = s2
        if s2:
            s2.parent = p
        c.lchild = s3
        if s3:
            s3.parent = c

        a.lchild = p
        p.parent = a
        a.rchild = c
        c.parent = a
        # 这里的bf建议画个图, 或者是看教材的书
        if a.bf > 0:  # key插入到s3上了
            a.bf = 0
            p.bf = -1
            c.bf = 0
        elif a.bf < 0:  # 在s2上插入
            a.bf = 0
            p.bf = 0
            c.bf = 1
        else:  # s1s2s3s4都是空  插入的是a
            p.bf = 0
            c.pf = 0
        return a

    def insert_no_rec(self, val):
        p = self.root
        if not self.root:  # 根节点为空,特殊处理一下
            self.root = AVLNode(val)
            return
        while True:
            if p.data > val:  # 要去左边插入
                if p.lchild:  # P有左孩子,往下面找
                    p = p.lchild
                else:  # p没有左孩子,插到它下面
                    p.lchild = AVLNode(val)
                    p.lchild.parent = p
                    node = p.lchild  # 记录插入的点
                    break  # 插入了我们就退出这个while循环 接着在对它进行旋转操作
            elif p.data < val:  # 要去右边插入
                if p.rchild:
                    p = p.rchild
                else:
                    p.rchild = AVLNode(val)
                    p.rchild.parent = p
                    node = p.rchild  # 记录要插入的点
                    break
            else:  # 重复节点不做操作
                return

        # 更新bf,插入之后他的父亲的bf可能会变化,所以我们一定会一直往上面找,只要找到bf=0就可视为平衡二叉树
        while node.parent:  # node一开始是我们插入的点
            if node.parent.lchild == node:  # 加入的是左边,左边沉了,我们更新node.parent-1。
                # 三种情况
                if node.parent.bf < 0:  # 本来是-1,我们一更新就减1变成-2了,我们就要旋转。这一句细细品味一下
                    x = node.parent  # 记录一下未旋转情况下的根节点
                    g = node.parent.parent  # 记录一下图p的父节点
                    if node.bf == -1:  # 右旋(现在node就相当于图中的c)
                        n = self.__rotate_right(node.parent, node)
                    else:  # node.bf只能为1
                        n = self.__rotate_left_right(node.parent, node)
                elif node.parent.bf == 0:  # 我们继续向上走
                    node.parent.bf = -1
                    node = node.parent
                    continue
                else:  # 发现加上之后bf=0了 我们就退出
                    node.parent.bf = 0
                    break
            else:  # 加的是右边 node.parent.bf += 1,思想和上面一样
                if node.parent.bf == -1:
                    node.parent.bf = 0
                    break
                elif node.parent.bf == 0:
                    node.parent.bf = 1
                    node = node.parent
                    continue
                else:
                    x = node.parent
                    g = node.parent.parent
                    if node.bf == -1:
                        n = self.__rotate_right_left(node.parent, node)
                    else:
                        n = self.__rotate_left(node.parent, node)
            # 链接更新完的树和原来的树
            n.parent = g
            if g:  # g有的话下一步
                if g.rchild == x:  # 看未旋转之前是左孩子还是右孩子
                    g.rchild = n
                else:
                    g.lchild = n
                break
            else:
                self.root = n
                break


avl = AVLTree([i for i in range(10, -1, -1)])
avl.in_order(avl.root)  # 0,1,2,3,4,5,6,7,8,9,10,
print()
avl.pre_order(avl.root)  # 7,3,1,0,2,5,4,6,9,8,10,

写者只是简单的实现了一下,例如一些内存删除可能不是特别完善,也可能有一些小的问题没有考虑清楚,若有问题可以随时和我沟通。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值