手撕二叉平衡树

news2025/1/21 1:02:26

今天给大家带来的是平衡树的代码实现,如下:

#pragma once 
#include <iostream>
#include <map>
#include <set>
#include <assert.h>
#include <math.h>
using namespace std;
namespace cc
{
    template<class K, class V>
    struct AVLnode
    {
        int _bf = 0;
        pair<K, V> _val;
        AVLnode<K, V>* _left;
        AVLnode<K, V>* _right;
        AVLnode<K, V>* _parent;
        AVLnode(const pair<K, V>& val = pair<K, V>(), AVLnode<K, V>* left = nullptr, AVLnode<K, V>* right = nullptr, AVLnode<K, V>* parent = nullptr)
            : _val(val)
            , _left(left)
            , _right(right)
            , _parent(parent)
        {}
    };
    template<class K, class V>
    class AVL
    {
    public:
        typedef AVLnode<K, V> node;
        //此parent其实就相当于是旋转点
        void revolveL(node* parent)
        {
            //需要改变的节点
            node* sub = parent->_right;
            node* subl = sub->_left;
            //如果根节点是parent
            if (_root == parent)
            {
                _root = sub;
                sub->_parent = nullptr;
                sub->_left = parent;
                parent->_parent = sub;
                parent->_right = subl;
                if (subl)
                    subl->_parent = parent;
            }
            //此旋转点不是根节点
            else
            {
                node* pparent = parent->_parent;
                if (pparent->_left == parent)
                    pparent->_left = sub;
                else
                    pparent->_right = sub;
                sub->_parent = pparent;
                sub->_left = parent;
                parent->_parent = sub;
                parent->_right = subl;
                if (subl)
                    subl->_parent = parent;
            }
            //旋转完成,更新平衡因子
            sub->_bf = 0;
            parent->_bf = 0;
        }
        void revolveR(node* parent)
        {
            node* sub = parent->_left;
            node* subr = sub->_right;
            if (_root == parent)
            {
                _root = sub;
                sub->_parent = nullptr;
                sub->_right = parent;
                parent->_parent = sub;
                parent->_left = subr;
                if (subr)
                    subr->_parent = parent;
            }
            else
            {
                node* pparent = parent->_parent;
                if (pparent->_left == parent)
                    pparent->_left = sub;
                else
                    pparent->_right = sub;
                sub->_parent = pparent;
                sub->_right = parent;
                parent->_parent = sub;
                parent->_left = subr;
                if (subr)
                    subr->_parent = parent;
            }
            sub->_bf = 0;
            parent->_bf = 0;
        }
        bool insert(const pair<K, V>& x)
        {
            //根节点为空
            if (_root == nullptr)
            {
                _root = new node(x);
                //节点中父指针已经指向nullptr
                return true;
            }
            node* parent = nullptr;
            node* cur = _root;
            while (cur)
            {
                if (x.first < (cur->_val).first)
                {
                    parent = cur;
                    cur = cur->_left;
                }
                else if (x.first > (cur->_val).first)
                {
                    parent = cur;
                    cur = cur->_right;
                }
                else
                    return false;
            }
            cur = new node(x);
            if ((parent->_val).first > x.first)
                parent->_left = cur;
            else
                parent->_right = cur;
            cur->_parent = parent;
            //插入完成,更新平衡因子
            while (parent)
            {
                if (parent->_right == cur)
                    parent->_bf++;
                else if (parent->_left == cur)
                    parent->_bf--;
                //此情况说明cur既不是做孩子也不是右孩子,所以直接报错,说明插入的时候出现了问题
                else
                    assert(false);
                if (abs(parent->_bf) == 0)
                    break;
                else if (abs(parent->_bf) == 1)
                {
                    cur = parent;
                    parent = parent->_parent;
                }
                else if (abs(parent->_bf) == 2)
                {
                    //旋转
                    if (parent->_bf == 2 && cur->_bf == 1)
                    {
                        revolveL(parent);
                        break;
                    }
                    else if (parent->_bf == -2 && cur->_bf == -1)
                    {
                        revolveR(parent);
                        break;
                    }
                    else if (parent->_bf == -2 && cur->_bf == 1)
                    {
                        node* sub = parent->_left;
                        node* subr = sub->_right;
                        int bf = subr->_bf;
                        revolveL(sub);
                        revolveR(parent);
                        if (bf == 0)
                        {
                            parent->_bf = 0;
                            sub->_bf = 0;
                            subr->_bf = 0;
                        }
                        else if (bf == 1)
                        {
                            sub->_bf = -1;
                            parent->_bf = 0;
                            subr->_bf = 0;
                        }
                        else if (bf == -1)
                        {
                            sub->_bf = 0;
                            parent->_bf = 1;
                            subr->_bf = 0;
                        }
                        else
                            assert(false);
                        break;
                    }
                    else if (parent->_bf == 2 && cur->_bf == -1)
                    {
                        node* sub = parent->_right;
                        node* subl = sub->_left;
                        int bf = subl->_bf;
                        revolveR(sub);
                        revolveL(parent);
                        subl->_bf = 0;
                        if (bf == 0)
                        {
                           // subl->_bf = 0;
                            sub->_bf = 0;
                            parent->_bf = 0;
                        }
                        else if (bf == 1)
                        {
                            sub->_bf = 0;
                           // subl->_bf = 0;
                            parent->_bf = -1;
                        }
                        else if (bf == -1)
                        {
                            sub->_bf = 1;
                            //subl->_bf = 0;
                            parent->_bf = 0;
                        }
                        else
                            assert(false);
                        break;
                    }
                    //如果走到这步,说明这棵树的平衡因子有问题
                    else
                        assert(false);
                }
                else
                    assert(false);
            }
            return true;
        }
        int high()
        {
            return _high(_root);
        }
        bool check()
        {
            return _check(_root);
        }
    private:
        node* _root = nullptr;
        bool _check(node* root)
        {
            if (root == nullptr)
                return true;
            int bf = _high(root->_right) - _high(root->_left) ;
            if (bf != root->_bf)
            {
                cout << "bf:不一样" << endl;
                return false;
            }
            cout << "bf:" << bf <<" " << "root:" << (
                root->_val).first << endl;
            return _check(root->_left) && _check(root->_right);
        }
        int _high(node *root)
        {
            if (root == nullptr)
                return 0;
            int left = _high(root->_left);
            int right = _high(root->_right);
            if (left > right)
                return left + 1;
            else
                return right + 1;
        }
    };
}

这个仅仅是平衡树的插入,其实二叉平衡树的插入并不难,逻辑就是那么几个,但是难得是细节处的实现,尤其是平衡因子更新的那一块,特别的容易弄混人,只要记住四种模型就可以了。两种单旋,两种双旋,还是有点难以理解的,下面一一讲解

1.左单旋

左单旋大概的图示这样子的:

如上图,如果没有插入100,那么此时的二叉平衡树是平衡的,但是此时如果插入100,此时30这个节点的平衡因子是2,所以此时需要旋转来降低这棵树的高度,此时就是左旋。其实此处还分为好几种情况,但是这几种情况都是一样的旋转方法,因为都在60这个节点的右子树中,所以此时就把30当做旋转点,让60左旋,在把60这个节点的左子树链接到30的右指针处就好了。

2.右单旋

和左单旋一样,因为新插入的节点导致30这个节点的平衡因子为-2,所以此时就要旋转来降低这棵树的高度,此时是要右旋,这个和左旋一样,30是旋转点,25进行右旋,把25这个节点的右子树连接到30这个节点的左子树处就可以了。

3.先左旋,在右旋

这个就是先左旋,在右旋,也是插入了新的节点导致的。这里没有写节点具体的值是因为,因为40这个节点的右子树或是左子树中的值不确定,所以就用了一个空节点来代替插入的值。其实就是两次单旋的结果,先把40以30为旋转点进行左旋,再把60当旋转点进行右旋,此时就旋转完成。而30与40的子树怎么连接参考左单旋与右单旋的旋转方式

4.先右旋,在左旋

这个模型就是先右旋,在左旋。先把60当旋转点进行旋转,再把30当旋转点进行旋转,至于子树怎么连接,与先左旋,在右旋相同。

以上就是平衡二叉树的旋转方式,下面来总结一下思路:

1.与二叉搜索树的插入一样

2.链接parent指针

3.更新平衡因子

4.如果左右不平衡,那么就开始旋转

增删查时间复杂度:

首先我们知道的是,他是一个二叉树,所以他的高度是log n,而我们在增删查的时候,我们最坏的结果就是要查叶子结点或者所查的节点不在此树,此时它的时间复杂度就是log n,但是他的空间复杂度也是logn,所以个人认为他是以空间换区时间的一种数据结构,但是他的效率确实很高,而对于我们所说的红黑树,其实严格意义来说也是一种logn的算法,但是他没有平衡二叉树这么严谨,平衡二叉树旋转的次数比较多,但是红黑树却没有,旋转其实也是一种消耗,但是旋转的时间复杂度是O(1),严格来说,有消耗,但是也没那么严重,但是对于红黑树来说,就是尽可能不旋转,减少这种消耗。

对于红黑树的代码以及讲解,下一篇会详细讲解,也会对比AVL树和红黑树的优缺点。期待下一篇内容吧!!谢谢大家支持!!!

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

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

相关文章

重装系统全流程

重点&#xff1a; 下载镜像网址&#xff1a;下载 Windows 10 (microsoft.com) 不过不用下载&#xff0c;你的美均相U盘里面有下载好的Win10系统 重点注意&#xff0c;重启后拔优盘&#xff0c;安装时不要联网

sql:SQL优化知识点记录(九)

&#xff08;1&#xff09;小表驱动大表 对sql调优的分析&#xff1a; 排序优化&#xff1a; 数据库的连接方式&#xff0c;里面的数据尽量这样连接&#xff0c;尽量选择第一个方式&#xff0c;因为两个表的连接一共建立5次连接&#xff0c;第二个建立1000次连接&#xff0c;从…

谁该来负责拥塞控制

寻找一种 host 公平而非 packet 公平的方法&#xff0c;有趣的是&#xff0c;CSMA/CD 网络就体现了这种方法。 端到端拥塞控制算法(cca)准不准先不论&#xff0c;仅说让它们运行&#xff0c;被控制的流至少要持续 2 个 RTT&#xff0c;一条持续传输的流是多数 cca 的约束。持续…

12. 自动化项目实战

目录 1. 登录测试 2. 测试首页的帖子列表数不为0 3. 帖子详情页校验 4. 发布帖子 5. 退出登录 自动化项目实施的基本流程如下图所示&#xff1a; 手工测试用例、自动化测试用例。 1. 登录测试 校验登录后主页显示的用户名称和登录时输入的用户名是否相等。 public class…

牛客小白月赛77 D-小Why的密码锁 双哈希做法

这道题没卡自然溢出的哈希&#xff0c;但mod为1e9的哈希被卡了。 双哈希只需把单哈希做法中的lst和ans转换成 <PII&#xff0c; int>类型的即可。 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing nam…

使用Docker安装和部署kkFileView

&#x1f388;1 参考文档 kkFileView官方文档 &#x1f680;2 安装kkFileView 拉取Redis镜像。 docker pull keking/kkfileview启动docker容器。 docker run -it -d -p 8012:8012 keking/kkfileview --restart always解释&#xff1a; docker run redis # 从kkfileview镜像运行…

ArcGIS Pro实践技术应用、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

c语言每日一练(13)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;上学期间将看学业情况更新。 五道选择题&#xff1a; 1、程序运行的结果…

理解 std::thread::join

C多线程并发编程入门&#xff08;目录&#xff09; 本文用最简单易懂的实际案例&#xff0c;讲清楚了 join 的实际内涵&#xff0c;保证你过目不忘。 Hello join 示例 join 函数是我们接触C多线程 thread 遇到的第一个函数。 比如&#xff1a; int main() {thread t(f);t.…

苹果Mac系统如何优化流畅的运行?提高运行速度

Mac系统的稳定性和流畅性一直备受大家称赞&#xff0c;这也是大多数人选择Mac的原因&#xff0c;尽管如此&#xff0c;我们仍不时地对Mac进行优化、调整&#xff0c;以使其比以前更快、更流畅地运行。以下是小编分享给各位的Mac优化方法&#xff0c;记得保存哦~ 一、释放被过度…

linux深入理解多进程间通信(未完)

1.进程间通信 1.1 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件…

MySQL总复习

目录 登录 显示数据库 创建数据库 删除数据库 使用数据库 创建表 添加数据表数据 查询表 添加数据表多条数据 查询表中某数据 增insert 删delete 改update 查select ​ where like ​编辑 范围查找 order by 聚合函数 count max min sum avg g…

创建2个线程并执行(STL/Windows/Linux)

C并发编程入门 目录 STL 写法 #include <thread> #include <iostream> using namespace std;void thread_fun1(void) {cout << "one STL thread 1!" << endl; }void thread_fun2(void) {cout << "one STL thread 2!" <…

ip地址、LINUX、与虚拟机

子网掩码&#xff0c;是用来固定网络号的&#xff0c;例如255&#xff0c;255,255,0&#xff0c;表明前面三段必须为网络号&#xff0c;后面必须是主机号&#xff0c;那么怎么实现网络复用呢&#xff0c;例如使用c类地址&#xff0c;但是正常子网掩码是255&#xff0c;255,255,…

抽象轻松的C语言

#include <stdio.h> /* 预处理指令*/ /* 函数 */ int main() {int log 3.14;printf("hello word * %d\n easy", log);getchar();/* 获取键盘输入的字母&#xff0c;在这个程序中的作用是防止程序瞬间关闭 */return 0; } 上一篇说过&#xff0c;C程序是C语言的…

linux系统中串口驱动框架基本分析(经典)

第一&#xff1a;区分不同的终端类型 串行端口终端&#xff08;/dev/ttySn&#xff09; 串行端口终端&#xff08;Serial Port Terminal&#xff09;是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。 有段时间这些串行端口设备通常被称为终…

CXL寄存器介绍(2)- CXL DVSEC

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

4.0 Spring与Dubbo整合原理与源码分析

#Dubbo# 文章介绍 Dubbo中propertie文件解析以及处理原理Dubbo中@Service注解解析以及处理原理Dubbo中@Reference注解解析以及处理原理1.0 整体架构和流程 应用启动类与配置 public class Application {public static void main(String[] args) throws Exception {Annotation…

Idea中 css 、js 压缩插件会自动生成xxx.min.css、xxx.min.js文件

第一步&#xff1a;下载yuicompressor-2.4.8.jar 下载路径&#xff1a;打开搜索 YUI compressor Maven Repository: Search/Browse/Explore (mvnrepository.com)https://mvnrepository.com/ 找到点击下载即用&#xff08;如下图&#xff09; 创建文件夹&#xff0c;放入idea文…

进制转换(二进制、八进制、十六进制、十进制)

一、进制表示 二进制&#xff1a;每一位只有两种符号表示 -> 0,1 例如 (101011)₂&#xff0c;也可写作101011B&#xff0c;其中B是Binary英文的缩写。八进制&#xff1a; 每一位有8种符号表示(0~7)&#xff0c;例如(1652)₈&#xff0c;也可写作1652O&#xff0c;其中O是O…