平衡二叉搜索树删除的实现

news2024/12/22 9:48:30

前言

        上期讲了平衡二叉搜索树的插入,这一期我们来讲讲删除。同时,二叉搜索树的简介不会出现在本篇博客之中,如有需要可以查看上一篇博客《平衡二叉搜索树插入的实现》。

平衡二叉搜索树插入的实现-CSDN博客文章浏览阅读659次,点赞9次,收藏13次。因为二叉搜索树在插入的时候最坏的情况可能会变成一条单一链表,从而使查找或者插入的时候消耗大量的时间。所以为了解决这一情况诞生了平衡二叉搜索树,其作用是为了减少二叉搜索树的整体高度,从而使查找插入删除的效率提高。https://blog.csdn.net/2302_81342533/article/details/142610087

一、删除思路

        首先,删除需要先找到需要删除的节点。通过关键值找到对应节点,小了向左,大了向右。找到节点就能够开始删除,相反没找到就删除失败。

        在之前的二叉搜索树的里面有提到怎么删除一个节点,现在复习一下。

        假如找到就开始删除,这里一共有4种情况,但是分为3种讨论:

        (1)该节点没有子节点。

        (2)该节点只有右子树。

        (3)该节点只有左子树。

        (4)该节点左右子树都存在。

        其中第一种情况包括在第二种情况中实现代码。

        如果只有一边子树有节点,那么就让父节点链接另一个子树。

        如果左右节点都存在,就用右子树最左侧节点,或者左子树最右侧节点对节点进行替换值,然后该节点就变成了情况(2)(3)。

        删除节点完成后就需要修改节点的记录值bf。这个时候也分为3种情况,如果节点的值为1或者-1表示节点的高度没有改变所以就可以不用继续向上查找节点。

图1-1 喊出节点后树的高度不变

        如果删除完成后,节点的值变为0,子树就变矮了,那么就需要继续向上遍历节点。

图1-2 节点删除后树变矮

        如果节点的值在修改之后等于2或者-2,就需要对二叉树进行旋转从而让它保持平衡。这一类就分为3种情况,而且旋转比插入的额时候复杂一点,同时这一部分也是重点内容。

1、左单旋

        左单旋的发生在节点的值在修改之后等于2的时候,当有节点的右子树高于左子树或者等于左子树的时候都需要左单旋。

图1-3 需要左单旋的情况

        以上两种情况再旋转之后的bf值夜不相同,如果是第一种情况,那么旋转后节点A、B的值都为0,树的高度仍然降低了,也就是说还需要继续向上修改父节点的记录值bf。如果是第二种情况,高度就和旋转前相同,就不需要继续向上搜索了。B节点的记录值变为-1,A的记录值变为1。

图1-4 左旋后高度降低的情况

图1-5 左旋之后高度不变的情况

2、右左双旋

        右左双旋夜发生在节点的记录值为2的时候,右节点中记录值为-1的情况。这中情况和插入的时候一样。使树的高度降低,所有节点的记录值修改也和插入的时候修改值相同。

图1-6 需要右左双旋

        甚至旋转的代码都能直接使用。

图1-7 右左双旋

3、右单旋

        右单旋就是左单旋的翻版,规律和左单旋是一样的。需要都单旋的情况发生在父节点记录值变为-2,而左节点记录值为-1或者0的情况。

图2-1 需要右单旋的情况

        同理,B的记录值如果是0旋转之后就不要要继续向上修改记录值bf,相反,则需要遍历。

图2-2 右单旋后高度变矮的情况

图2-3 右单旋后高度不变的情况

4、左右双旋

        当父节点记录值为-2,左节点记录值为1的时候就需要进行左右双旋。左右双旋之中同样分为三种情况。

图2-4 需要左右双旋的情况

        旋转之后的结果和插入的左右双旋一致。

图2-5 左右双旋

二、代码实现

        代码实现的解释写法都在代码的注释当中,代码同时包含之前实现的插入函数:

#include <iostream>
#include <string>
#include <assert.h>
#include <utility>
#include <algorithm>
#include <cmath>

using namespace std;

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data = T())
	: _pLeft(nullptr)
	, _pRight(nullptr)
	, _pParent(nullptr)
	, _data(data)
	, _bf(0)
	{}

	AVLTreeNode<T>* _pLeft;
	AVLTreeNode<T>* _pRight;
	AVLTreeNode<T>* _pParent;
	T _data;
	int _bf;   // 节点的平衡因子
};

// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	AVLTree()
		: _pRoot(nullptr)
	{}

    // 在AVL树中插入值为data的节点
	bool Insert(const T& data)
    {
        // 根节点为空直接插入并修改
        if(nullptr == _pRoot)
        {
            _pRoot = new Node(data);
            return true;
        }

        Node* parent = nullptr;
        Node* cur = _pRoot;

        // 找到对应的插入节点
        while(cur)
        {
            parent = cur;
            if(data < cur->_data)
            {
                cur = cur->_pLeft;
            }
            else if(data > cur->_data)
            {
                cur = cur->_pRight;
            }
            else
            {
                return false;
            }
        }

        // 插入节点
        cur = new Node(data);
        if(data < parent->_data)
        {
            parent->_pLeft = cur;
            cur->_pParent = parent;
        }
        else if(data > parent->_data)
        {
            parent->_pRight = cur;
            cur->_pParent = parent;
        }
        else
        {
            assert(false);
            return false;
        }

        // AVL树向上检查,修改平衡因子
        while(parent)
        {
            // 修改平衡因子
            if(parent->_pLeft == cur)
            {
                parent->_bf--;
            }
            else if(parent->_pRight == cur)
            {
                parent->_bf++;
            }
            else
            {
                // 节点存储的有问题
                assert(false);
            }

            // 考虑是否旋转子树
            // 1. 为0不需要继续向上修改
            if(0 == parent->_bf)
            {
                break;
            }
            // 2. 等于1或者-1需要继续向上修改平衡因子
            else if(1 == parent->_bf || -1 == parent->_bf)
            {
                cur = parent;
                parent = parent->_pParent;
            }
            // 3. 因子等于2或者-2就说明需要旋转
            else if(-2 == parent->_bf || 2 == parent->_bf)
            {
                // 继续分为4种情况
                // 3.1. 左子树的左子树超高
                if(-2 == parent->_bf && -1 == cur->_bf)
                {
                    RotateR(parent);
                }
                // 3.2. 右子树的右子树超高
                else if(2 == parent->_bf && 1 == cur->_bf)
                {
                    RotateL(parent);
                }
                // 3.3. 左子树的右子树超高
                else if(-2 == parent->_bf && 1 == cur->_bf)
                {
                    RotateLR(parent);
                }
                // 3.4. 右子树的左子树超高
                else if(2 == parent->_bf && -1 == cur->_bf)
                {
                    RotateRL(parent);
                }
                break;
            }
        }
        return true;
    }

    bool Erase(const T& data)
    {
        if(_pRoot == nullptr)
        {
            return false;
        }

        // 双节点遍历链表
        Node* parent = nullptr;
        Node* cur = _pRoot;

        while(cur)
        {
            if(data > cur->_data)
            {
                cur = cur->_pRight;
            }
            else if(data < cur->_data)
            {
                cur = cur->_pLeft;
            }
            else
            {
                break;
            }
        }

        // 未查找到节点,删除失败
        if(cur == nullptr)
        {
            return false;
        }

        // 找到节点,能够删除
        pair<Node*, Node*> tmp = _Erasecur(cur);
        parent = tmp.first;
        cur = tmp.second;
        if(cur)
        {
            cur->_pParent = parent;
        }

        // 节点删除成功后,需要向上调整_bf值
        while(parent)
        {
            // 1. 节点删除后需要继续向上查改
            if(parent->_bf == 0)
            {
                cur = parent;
                parent = parent->_pParent;
                // 没有父节点退出
                if(nullptr == parent)
                {
                    break;
                }
                // 增减bf
                if(parent->_pLeft == cur)
                {
                    parent->_bf++;
                }
                else if(parent->_pRight == cur)
                {
                    parent->_bf--;
                }
                else
                {
                    assert(false);
                }
            }
            // 2. 节点的值为1或者-1的时候表示高度不变
            else if(1 == parent->_bf || -1 == parent->_bf)
            {
                break;
            }
            // 3. 节点的值为2或者-2就需要旋转,并且可能需要继续向上查找
            else if(2 == parent->_bf || -2 == parent->_bf)
            {
                Node* other = nullptr;
                if(parent->_pLeft == cur)
                {
                    other = parent->_pRight;
                }
                else if(parent->_pRight == cur)
                {
                    other = parent->_pLeft;
                }
                else
                {
                    assert(false);
                }

                if(2 == parent->_bf)
                {
                    if(0 == other->_bf)
                    {
                        RotateL(parent);
                        parent->_bf = 1;
                        other->_bf = -1;
                        parent = other;
                    }
                    else if(1 == other->_bf)
                    {
                        RotateL(parent);
                    }
                    else if(-1 == other->_bf)
                    {
                        RotateRL(parent);
                    }
                    else
                    {
                        assert(false);
                    }
                }
                else if(-2 == parent->_bf)
                {
                    if(0 == other->_bf)
                    {
                        RotateR(parent);
                        parent->_bf = -1;
                        other->_bf = 1;
                        parent = other;
                    }
                    else if(-1 == other->_bf)
                    {
                        RotateR(parent);
                    }
                    else if(1 == other->_bf)
                    {
                        RotateLR(parent);
                    }
                    else
                    {
                        assert(false);
                    }
                }
            }
        }
        return true;
    }
    
    // AVL树的验证
	bool IsAVLTree()
	{
		return _IsAVLTree(_pRoot).second;
	}

private:
    // 删除对应节点,返回父节点
    pair<Node*, Node*> _Erasecur(Node* cur)
    {
        Node* parent = cur->_pParent;
        // 1. 左子树为空
        if(nullptr == cur->_pLeft)
        {
            if(cur == _pRoot)
            {
                _pRoot = cur->_pRight;
                delete cur;
                return make_pair(parent, _pRoot);
            }
            else if(parent->_pLeft == cur)
            {
                parent->_pLeft = cur->_pRight;
                parent->_bf++;
                delete cur;
                return make_pair(parent, parent->_pLeft);
            }
            else if(parent->_pRight == cur)
            {
                parent->_pRight = cur->_pRight;
                parent->_bf--;
                delete cur;
                return make_pair(parent, parent->_pRight);
            }
            else
            {
                assert(false);
            }     
        }
        // 2. 右子树为空
        else if(nullptr == cur->_pRight)
        {
            if(cur == _pRoot)
            {
                _pRoot = cur->_pLeft;
                delete cur;
                return make_pair(parent, _pRoot);
            }
            else if(parent->_pLeft == cur)
            {
                parent->_pLeft = cur->_pLeft;
                parent->_bf++;
                delete cur;
                return make_pair(parent, parent->_pLeft);
            }
            else if(parent->_pRight == cur)
            {
                parent->_pRight = cur->_pLeft;
                parent->_bf--;
                delete cur;
                return make_pair(parent, parent->_pRight);
            }
            else
            {
                assert(false);
            }     
        }
        // 左右子树均不为空
        else
        {
            // 找到右子树最左节点
            Node* Rsonmin = cur->_pRight;
            while(Rsonmin->_pLeft)
            {
                Rsonmin = Rsonmin->_pLeft;
            }
            cur->_data = Rsonmin->_data;
            return _Erasecur(Rsonmin);
        }

        // 防出错
        return make_pair(nullptr, nullptr);
    }

    // 根据AVL树的概念验证pRoot是否为有效的AVL树
	pair<int, bool> _IsAVLTree(Node* pRoot)
    {
        if(pRoot == nullptr)
        {
            return make_pair(0, true);
        }

        pair<int, bool> lson = _IsAVLTree(pRoot->_pLeft);
        cout << pRoot->_data << " ";
        pair<int, bool> rson = _IsAVLTree(pRoot->_pRight);

        int hight = max(rson.first, lson.first) + 1;
        bool Is = rson.second && lson.second && abs(rson.first - lson.first) <= 1;

        return make_pair(hight, Is);
    }

	// size_t _Height(Node* pRoot)
    // {
    //     if(nullptr == pRoot)
    //     {
    //         return 0;
    //     }
    //     else{
    //         return max<size_t>(_Height(pRoot->_pLeft), _Height(pRoot->_pRight)) + 1;
    //     }
    // }
    // 右单旋
	void RotateR(Node* pParent)
    {
        Node* SubL = pParent->_pLeft;
        Node* SubLR = SubL->_pRight;
        Node* parent = pParent->_pParent;

        pParent->_pLeft = SubLR;
        pParent->_pParent = SubL;

        if(SubLR)
            SubLR->_pParent = pParent;

        SubL->_pRight = pParent;
        SubL->_pParent = parent;

        if(pParent == _pRoot)
        {
            _pRoot = SubL;
        }
        else
        {
            if(parent->_pLeft == pParent)
            {
                parent->_pLeft = SubL;
            }
            else if(parent->_pRight == pParent)
            {
                parent->_pRight = SubL;
            }
            else
            {
                assert(false);
            }
        }
        
        pParent->_bf = SubL->_bf = 0;
    }

    // 左单旋
	void RotateL(Node* pParent)
    {
        Node* SubR = pParent->_pRight;
        Node* SubRL = SubR->_pLeft;
        Node* parent = pParent->_pParent;

        pParent->_pRight= SubRL;
        pParent->_pParent = SubR;

        if(SubRL)
            SubRL->_pParent = pParent;

        SubR->_pLeft = pParent;
        SubR->_pParent = parent;

        if(pParent == _pRoot)
        {
            _pRoot = SubR;
        }
        else
        {
            if(parent->_pLeft == pParent)
            {
                parent->_pLeft = SubR;
            }
            else if(parent->_pRight == pParent)
            {
                parent->_pRight = SubR;
            }
            else
            {
                assert(false);
            }
        }
        
        pParent->_bf = SubR->_bf = 0;
    }

    // 右左双旋
	void RotateRL(Node* pParent)
    {
        Node* SubR = pParent->_pRight;
        Node* SubRL = SubR->_pLeft;
        int bf = SubRL->_bf;

        RotateR(SubR);
        RotateL(pParent);

        if(0 == bf)
        {
            SubR->_bf = SubRL->_bf = pParent->_bf = 0;
        }
        else if(-1 == bf)
        {
            SubRL->_bf = pParent->_bf = 0;
            SubR->_bf = 1;
        }
        else if(1 == bf)
        {
            SubRL->_bf = SubR->_bf = 0;
            pParent->_bf = -1;
        }
        else
        {
            assert(false);
        }
    }

    // 左右双旋
	void RotateLR(Node* pParent)
    {
        Node* SubL = pParent->_pLeft;
        Node* SubLR = SubL->_pRight;
        int bf = SubLR->_bf;

        RotateL(SubL);
        RotateR(pParent);

        if(0 == bf)
        {
            SubL->_bf = SubLR->_bf = pParent->_bf = 0;
        }
        else if(1 == bf)
        {
            SubLR->_bf = pParent->_bf = 0;
            SubL->_bf = -1;
        }
        else if(-1 == bf)
        {
            SubLR->_bf = SubL->_bf = 0;
            pParent->_bf = 11;
        }
        else
        {
            assert(false);
        }
    }

private:
	Node* _pRoot;
};

三、代码测试

        测试代码如下:

#include "AVLtree.hpp"

int main()
{
    AVLTree<int> avltree;
    for(int i = 0; i < 100; ++i)
    {
        avltree.Insert(i);
    }

    for(int i = 0; i < 10; ++i)
    {
        avltree.Erase(i);
    }
    if(avltree.IsAVLTree())
    {
        cout << "avltree是二叉搜索树" << endl;
    }
    else
    {
        cout << "avltree不是二叉搜索树" << endl;
    }
    return 0;
}

作者结语

        阿巴阿巴。非常绕的代码,是我旋转。删除思路之中左单旋和右单旋、左右双旋和右左双旋思路的重复度很高,只用看一边的就行。这里多讲了一遍就是想写的更清楚,减少思考。

        总之干货还是有点的,下一篇博客应该就进化成红黑树了。都是树,也就难一点点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2183647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三、I/O控制器

1.主要功能 接受和识别CPU发出的命令(要有控制寄存器) 向CPU报告设备的状态(要有状态寄存器) 数据交换(要有数据寄存器&#xff0c;暂存输入/输出的数据) 地址识别(由I/0逻辑实现) 2.组成 CPU与控制器之间的接口(实现控制器与CPU之间的通信) I/0逻辑(负责识别CPU发出的命…

鸿蒙NEXT开发-ArkUI(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

自定义注解加 AOP 实现服务接口鉴权以及内部认证

注解 何谓注解&#xff1f; 在Java中&#xff0c;注解&#xff08;Annotation&#xff09;是一种特殊的语法&#xff0c;用符号开头&#xff0c;是 Java5 开始引入的新特性&#xff0c;可以看作是一种特殊的注释&#xff0c;主要用于修饰类、方法或者变量&#xff0c;提供某些信…

中英翻译神器!轻松搞定跨文化沟通

大家好&#xff01;今天咱们来聊聊那些你生活中不可或缺的翻译小助手&#xff1b;不论你是个英语小白&#xff0c;还是希望更快地了解外国文献、掌握外媒信息&#xff0c;或者是从事需要大量翻译工作的小伙伴&#xff0c;总有一款翻译工具能帮你省时省力&#xff0c;让你的生活…

DBCP数据库连接池以及在Tomcat中配置JNDI数据源

前言 数据库连接 数据库连接是指在计算机系统中建立起应用程序与数据库之间的连接通道&#xff0c;用于进行数据的读取和写入操作。通过数据库连接&#xff0c;应用程序可以与数据库进行交互&#xff0c;执行各种数据库操作&#xff0c;如查询数据、插入数据、更新数据和删除数…

算法题总结(四)——螺旋矩阵

螺旋矩阵 59、螺旋矩阵二 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输…

2.点位管理开发(续)及设计思路——帝可得后台管理系统

目录 前言一、页面原型二、修改1、页面展示2、新增 3 、总结思路 前言 提示&#xff1a;本篇继续点位管理的改造 一、页面原型 页面展示新增 二、修改 1、页面展示 页面修改&#xff1a;修改标签换行、顺序顺序、地址过长时换行问题&#xff1b; <el-table v-loading…

七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)

七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#xff08;实操详细使用&#xff09; 文章目录 七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#…

愿祖国富强!肌肉水凝胶的奥秘,自协调与光驱动,运动模式大揭秘

大家好&#xff0c;在这个国庆佳节&#xff0c;我们一同感受科技的魅力。今天来了解一种特殊的肌肉样水凝胶&#xff0c;它通过自协调形状变化和摩擦调节&#xff0c;能在光的引导下实现多样运动。这一成果为软机器人发展带来新契机——《Light-steered locomotion of muscle-l…

基于ScriptableObject设计游戏数据表

前言 本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一&#xff0c;在游戏开发中&#xff0c;我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索&#xff0c;并且结合自定义的本地持久化存储方式…

一级建造师备考攻略及一建各科老师推荐(各科四大金刚)

吐血整理:真正的实战派一建名师推荐! 考过的同学一定都知道推荐的老师YYDS! 一级建造师各科老师推荐: 《法规》名师:王欣、王竹梅、陈印、关涛 其他老师:房超、蔡恒、刘丹、武海峰、陈洁、安国庆、桂林 《管理》名师:宿吉南、龙炎飞、李向国、朱俊文 其他老师:缴广才、陈晨…

跟《经济学人》学英文:2024年09月28日这期 The curse of the Michelin star

The curse of the Michelin star Restaurants awarded the honour are more likely to close, research finds 原文&#xff1a; The twelve new restaurants added to the New York Michelin Guide this month, serving up cuisine ranging from “haute French” to “eco…

9.数据结构与算法-单链表,循环链表和双向链表的比较////顺序表和链表的比较

单链表&#xff0c;循环链表和双向链表的时间效率比较 顺序表和链表的区别 存储密度

HarmonyOS Next应用开发——自定义组件的使用

自定义组件的使用 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与…

达梦数据库开启归档模式

目录 一、什么是归档模式&#xff1f; 二、开启归档模式的步骤 1、创建归档目录 2、进入dm数据库bin目录 3、登录数据库 4、关闭数据库 5、启动数据库到Mount状态 6、增加本地归档日志文件 7、开启归档 8、启动数据库 9、验证是否开启成功 三、开启归档模式的优…

Lj视频下载器 1.1.37 简洁高效的视频下载工具

Lj视频下载器是一个功能强大的视频下载器&#xff0c;支持直接添加视频地址或 m3u8 资源地址&#xff0c;可以从网页中自动提取视频进行下载。支持多种视频格式&#xff0c;包括 m3u8&#xff0c;并能自动检测并移除广告片段。 大小&#xff1a;19M 百度网盘&#xff1a;https…

Linux CentsOS定时删除一个目录下(包含子目录)的改动时间大于12小时的文件

Shell脚本 文件目录如下图 ** 查找/ai/img/目录下的所有文件** find /ai/img/ -type f查找/ai/img/目录下的所有上次改动时间大于720分钟(12小时)的文件 12 小时&#xff0c;也就是 720 分钟。所以&#xff0c;我们可以使用 -mmin 720 来查找修改时间超过 720 分钟&#xff08;…

uniapp 微信小程序 微信支付

本章的内容我尽量描述的细致一些&#xff0c;哪里看不懂给我评论就可以&#xff0c;我看到进行回复 微信支付大致分为4步&#xff0c;具体看后端设计 1. 获取code 2. 根据code获取openid 3. 根据openid&#xff0c;以及部分订单相关数据&#xff0c;生成prepayId (预支付交易会…

免费 Oracle 各版本 离线帮助使用和介绍

文章目录 Oracle 各版本 离线帮助使用和介绍概要在线帮助下载离线文档包&#xff1a;解压离线文档&#xff1a;访问离线文档&#xff1a;导航使用&#xff1a;目录介绍Install and Upgrade&#xff08;安装和升级&#xff09;&#xff1a;Administration&#xff08;管理&#…

交通场景多目标检测系统源码分享

交通场景多目标检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Comput…