算法学习之二叉排序树的平衡化

本文探讨了为何需要平衡二叉排序树,如何通过旋转操作(右旋和左旋)保持树的平衡,以及关键的AVL树平衡化算法。通过实例和代码演示了如何确保查询效率,避免树结构退化为数组。

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

首先提出问题:问什么要让二叉树排序树平衡化

比如给出{1,2,3,4,5,6,7}让我们将其构造成排序树,如下图

可以看出,此时二叉树变成了数组,而我们为了提高搜索效率的初衷也被打破了

为了解决这种极端情况,我们需要对二叉树进行平衡化

概念

  • 每个节点的左子树和右子树的高度差至多为一

  • 必须是二叉查找树

  • 高度是从根节点到目标节点的层数

思路

我们需要查询出从根节点开始其左右子节点的高度

  • 若左子树的高度-右子树高度>1,该节点右旋

  • 若右子树的高度-左子树高度>1,该节点左旋

  • 右旋的步骤

    • 让该节点的左节点更为新的根结点,即该节点的左子节点指向左子树的右子树,该节点的左子树的右子节点指向该节点,在用父节点指向新的节点

  • 左旋的步骤

    • 让该节点的右节点更为新的根结点,即该节点的右子节点指向右子树的左子树,该节点的右子树的左子节点,指向该节点,在用父节点指向新的节点

 进行右旋时,节点左子树的右子树高度大于左子树的左子树高度时,其左子树要先左旋,既保证左子树高度大于右子树高度

进行左旋时,节点右子树的左子树高度大于右子树的右子树高度时,其右子树要先右旋,既保证右子树高度大于左子树高度

代码


class BinaryNode {

    /**
     * 查找父节点
     * @param node
     * @return
     */
    public BinaryNode getFatherNode(BinaryNode node) {
        BinaryNode node1 = null;
        if (this.left != null) {
            if (this.left == node) {
                node1 = this;
                return node1;
            }else {
                node1 = this.left.getFatherNode(node);
            }
        }
        if (node1 != null) {
            return node1;
        }
        if (this.right != null) {
            if (this.right == node) {
                node1 = this;
                return node1;
            }else {
                node1 = this.right.getFatherNode(node);
            }
        }
        return node1;
    }

    /**
     * 查询节点的高度
     * @return 该节点的高度
     */
    public int highOfNode() {
       return Math.max(left == null ? 0 : left.highOfNode(),right == null ? 0 : right.highOfNode()) + 1 ;
    }

    /**
     * 左子树的高度
     * @return 如果左节点为空,返回0,反之返回高度
     */
    public int leftHigh() {
        if (this.left == null) {
            return 0;
        }else {
            return left.highOfNode();
        }
    }

    /**
     * 右子树的高度
     * @return 如果右节点为空,返回0,反之返回高度
     */
    public int rightHigh() {
        if (this.right == null) {
            return 0;
        }else {
            return right.highOfNode();
        }
    }
}
class BinarySortTree {

    /**
     * 左旋转,使根结点的右节点成为新的根结点,原来根结点的右节点指向新根节点的左节点
     * 这样可以使当前树的左子树高度加一,右子树高度减一
     */
    public void leftRotation(BinaryNode node){
        // 方案一
        // 1.创建一个新的节点,newNode值等于根节点的值
//        BinaryNode node = new BinaryNode(root.getValue());
//        // 2.把新节点的左子树设置为根节点的左子树
//        node.left = root.left;
//        // 3.把新节点的右子树设置为根结点的右子树的左子树
//        node.right = root.right.left;
//        // 4.把根结点的值换为右子节点的值
//        root.setValue(root.right.getValue());
//        // 5.把根节点的右子树设置为右子树的右子树
//        root.right = root.right.right;
//        // 6.把当前节点的左子树设置为新节点
//        root.left = node;
        // 方案2
        // 找到其父节点
        boolean flag = true;
        BinaryNode fatherNode = getFatherNode(node);
        if (fatherNode != null && fatherNode.left != null && fatherNode.left == node) {
            flag = false;
        }
        // 左旋,该节点的右子树指向右子树的左子树,该节点的右子树的左子树,指向该节点,在用父节点指向新的节点
        BinaryNode index = node;
        BinaryNode newNode = node.right;
        node.right = newNode.left;
        newNode.left = index;
        if (node == root) {
            root = newNode;
        }else if (flag) {
            fatherNode.right = newNode;
        }else {
            fatherNode.left = newNode;
        }
    }


    /**
     * 右旋转,使根结点的左节点成为新的根结点,原来根结点的左节点指向新根节点的右节点
     * 这样可以使当前树的左子树高度减一,右子树高度加一
     */
    public void rightRotation(BinaryNode node) {
        boolean flag = true;
        BinaryNode fatherNode = getFatherNode(node);
        if (fatherNode != null && fatherNode.left != null && fatherNode.left == node) {
            flag = false;
        }
        BinaryNode index = node;
        BinaryNode newNode = node.left;
        node.left = newNode.right;
        newNode.right = index;
        if (node == root) {
            root = newNode;
        }else if (flag) {
            fatherNode.right = newNode;
        }else {
            fatherNode.left = newNode;
        }
    }

    /**
     * 顺序二叉树平衡化,平衡化是指每个节点的左子树和右子树的高度差至多为一
     * @param node
     */
    public void avl(BinaryNode node) {
        // 当左子树高度 - 右子树高度 > 1时,右旋,使左子树高度减一,右子树高度加一
        if ((node.leftHigh() - node.rightHigh()) > 1) {
            // 当节点左子树的右子树高度大于左子树的左子树高度时,其左子树要先左旋,既保证左子树高度大于右子树高度
            if (node.left != null && node.left.rightHigh() > node.left.leftHigh()) {
                leftRotation(node.left);
                rightRotation(node);
            }else {
                rightRotation(node);
            }
        }
        // 当右子树高度 - 左子树高度 > 1时,左旋,使右子树高度减一,左子树高度加一
        if ((node.rightHigh() - node.leftHigh()) > 1) {
            // 当节点右子树的左子树高度大于右子树的右子树高度时,其右子树要先右旋,既保证右子树高度大于左子树高度
            if (node.right != null && node.right.leftHigh() > node.right.rightHigh()) {
                rightRotation(node.right);
                leftRotation(node);
            }else {
                leftRotation(node);
            }
        }
        if (node.left != null) {
            avl(node.left);
        }
        if (node.right != null) {
            avl(node.right);
        }
    }

}

1本程序在vc++6.0编译通过并能正常运行。 2主界面 程序已经尽量做到操作简便了,用户只需要根据提示一步步进行操作就行了。 六思考和总结: 这个课程设计的各个基本操作大部分都在我的综合性实验中实现了,所以做这个主要攻克插入和删除这两个算法!其中插入在书本上已经有了,其中的右平衡算法虽然没有给出,但通过给出的左平衡算法很容易就可以写出右平衡算法。所以最终的点就在于删除算法的实现!做的过程中对插入算法进行了非常非常多次的尝试!花了非常多的时间,这其中很多时候是在对程序进行单步调试,运用了VC6。0的众多良好工具,也学到了很多它的许多好的调试手段。 其中删除算法中最难想到的一点是:在用叶子结点代替要删除的非叶子结点后,应该递归的运用删除算法去删除叶子结点!这就是整个算法的核心,其中很强烈得体会到的递归的强大,递归的最高境界(我暂时能看到的境界)! 其它的都没什么了。选做的那两个算法很容易实现的: 1合并两棵平衡二叉排序树:只需遍历其中的一棵,将它的每一个元素插入到另一棵即可。 2拆分两棵平衡二叉排序树:只需以根结点为中心,左子树独立为一棵,右子树独立为一棵,最后将根插入到左子树或右子树即可。 BSTreeEmpty(BSTree T) 初始条件:平衡二叉排序树存在。 操作结果:若T为空平衡二叉排序树,则返回TRUE,否则FALSE. BSTreeDepth(BSTree T) 初始条件:平衡二叉排序树存在。 操作结果:返回T的深度。 LeafNum(BSTree T) 求叶子结点数,非递归中序遍历 NodeNum(BSTree T) 求结点数,非递归中序遍历 DestoryBSTree(BSTree *T) 后序遍历销毁平衡二叉排序树T R_Rotate(BSTree *p) 对以*p为根的平衡二叉排序树作右旋处理,处理之后p指向新的树根结点 即旋转处理之前的左子树的根结点 L_Rotate(BSTree *p) 对以*p为根的平衡二叉排序树作左旋处理,处理之后p指向新的树根结点, 即旋转处理之前的右子树的根结点 LeftBalance(BSTree *T) 对以指针T所指结点为根的平衡二叉排序树作左平衡旋转处理, 本算法结束时,指针T指向新的根结点 RightBalance(BSTree *T) 对以指针T所指结点为根的平衡二叉排序树作右平衡旋转处理, 本算法结束时,指针T指向新的根结点 Insert_AVL(BSTree *T, TElemType e, int *taller) 若在平衡二叉排序树T中不存在和e有相同的关键字的结点, 则插入一个数据元素为e的新结点,并返回OK,否则返回ERROR. 若因插入而使二叉排序树失去平衡,则作平衡旋转处理 布尔变量taller反映T长高与否 InOrderTraverse(BSTree T) 递归中序遍历输出平衡二叉排序树 SearchBST(BSTree T, TElemType e, BSTree *f, BSTree *p) 在根指针T所指的平衡二叉排序树中递归的查找其元素值等于e的数据元素, 若查找成功,则指针p指向该数据元素结点,并返回TRUE,否则指针p 指向查找路径上访问的最后一个结点并返回FALSE,指针f指向T的双亲, 其初始调用值为NULL Delete_AVL(BSTree *T, TElemType e, int *shorter) 在平衡二叉排序树中删除元素值为e的结点,成功返回OK,失败返回ERROR PrintBSTree_GList(BSTree T) 以广义表形式打印出来 PrintBSTree_AoList(BSTree T, int length) 以凹入表形式打印,length初始值为0 Combine_Two_AVL(BSTree *T1, BSTree T2) 合并两棵平衡二叉排序树 Split_AVL(BSTree T, BSTree *T1, BSTree *T2) 拆分两棵平衡树 } (2)存储结构的定义: typedef struct BSTNode { TElemType data; int bf; //结点的平衡因子 struct BSTNode *lchild, *rchild;//左.右孩子指针 }BSTNode, *BSTree;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值