平衡二叉树(AVLTree)

AVLTree

  • 1、树的分类
  • 2、平衡二叉树
    • 2.1、构建一个平衡二叉树
    • 2.2、删除节点
    • 2.3、搜索方式
      • 2.3.1、广度优先搜索(BFS)
      • 2.3.2、深度优先搜索(DFS)

1、树的分类

树形结构是编程当中特别常见的一种数据结构。比如电脑中的文件管理系统就大量用到树结构。
![在这里插入图片描述](https://img-
##blog.csdnimg.cn/direct/2f412f2f7b144da3b4193557dc388274.png#pic_center =600x400)
该图取之于这篇文章 硬核总结!真二叉树、满二叉树、完全二叉树的性质与概念,这篇文章对一些基础的树总结的很好。所以我也不赘诉了。

2、平衡二叉树

平衡二叉树的基础是二叉搜索树,对于一个二叉搜索树而言,新插入的数字若小于根节点则放在左边,反之放在右边。但如果插入的数字一直小于或大于根节点,那这个树,就和链表差不多了。如图,我按顺序插入(1, 0, 3, 2, 4, 5, 6)。
在这里插入图片描述
本来二叉树的实现就是为了减低搜索的时间复杂度的,而如果树构建得和单链表差不多,那便失去了意义。所以二叉平衡搜索树,又称平衡二叉树应运而生。
平衡二叉树要求左子树的高度和右子树的高度之不能超过1。

2.1、构建一个平衡二叉树

正如前面所言,一个平衡二叉树,首先得是一个二叉搜索树,然后再要求左右子树高度差不超一。于是人们总结出四种会导致不平衡的情况。
1、LL型:即左子树的左子树增高了一层,从而导致不平衡。
在这里插入图片描述
上图是一个简单的模型,实际上为了解决不平衡的问题,要解决的事情远不止怎么简单。下面是一个更为通用的模型。我不仅需要把a变成b的右子树,还需要把原来b的右子树变成a的左子树,然后还要让原来指向a的指针指向b。这是一个颇为麻烦的事情,最难的部分是要改变根节点上级指针。
在这里插入图片描述
2、LR型:左子树的右子树添加了一层导致不平衡。直接上图。
在这里插入图片描述
另外两种类型就是RR和RL了,逻辑和上述两种都是一样的。再写代码的时候甚至可以把所以left和right调换然后就可以实现LL到RR的转换或LR到RL的转换。而我懒得画图就不再画了。
直接上代码。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
#define n 9

struct Node
{
    int data;
    Node* right;
    Node* left;

    Node() {};
    Node(int x): data(x) {};            //constructor
};

class AVLTree
{
public:
    Node* root;
    AVLTree(){
        root = nullptr;
    };
    int get_height(Node* node);
    void append_data(Node** node, int num);       //append a number into the AVL tree
    void rrRotation(Node** root);
    void rlRotation(Node** root);
    void llRotation(Node** root);
    void lrRotation(Node** root);
};

int AVLTree::get_height(Node* node)
{
    if(node == nullptr) return 0;
    return max(get_height(node->left), get_height(node->right)) + 1;
}

void AVLTree::append_data(Node** node, int data)
{
    if(*node == nullptr){
        *node = new Node(data);
        (*node)->right = nullptr;
        (*node)->left = nullptr;
    }
    else if(data < (*node)->data){
        append_data(&(*node)->left, data);
        if(abs(get_height((*node)->right) - get_height((*node)->left)) > 1){
            if(data < (*node)->left->data) llRotation(node);
            else lrRotation(node);
        }
    }
    else{
        append_data(&(*node)->right, data);
        if(abs(get_height((*node)->right) - get_height((*node)->left)) > 1){
            if(data > (*node)->right->data) rrRotation(node);
            else rlRotation(node);
        }
    }
}

void AVLTree::llRotation(Node** root)
{
    Node* temp = (*root)->left;
    (*root)->left = temp->right;
    temp->right = *root;
    *root = temp;
}

void AVLTree::lrRotation(Node** root)
{
    Node* temp = (*root)->left->right;
    (*root)->left->right = temp->left;
    temp->right = *root;
    Node* temp2 = temp->left;
    temp->left = (*root)->left;
    (*root)->left = temp2;
    *root = temp;
}

void AVLTree::rrRotation(Node** root)
{
    Node* temp = (*root)->right;
    (*root)->right = temp->left;
    temp->left = *root;
    *root = temp;
}

void AVLTree::rlRotation(Node** root)
{
    Node* temp = (*root)->right->left;
    (*root)->right->left = temp->right;
    temp->left = *root;
    Node* temp2 = temp->right;
    temp->right = (*root)->right;
    (*root)->right = temp2;
    *root = temp;
}

int randomArray[n] = {16, 3, 7, 11, 9, 26, 18, 14, 15};

int main()
{
    class AVLTree mytree;
    
    for(int i = 0; i < n; i++)
       mytree.append_data(&mytree.root, randomArray[i]);

    printf("the height of tree is: %d\n", mytree.get_height(mytree.root));
    system("pause");
    return 0;
}

在这个代码实现中,最令我头疼的是二级指针这里。我一开始不太明白用二级指针的意义,然后尝试了许多次才不得不使用它。
因为在我插入一个节点之后,我是通过反向遍历的方式去查找哪个节点失衡了。但是此时有一个问题,当我得知这个节点失衡之后,我并不清楚它的上一个节点再哪里,因此我无法改变上一个节点所存储的指针。有点绕,我画个图。
如下面两个图所示,如果b这个节点失衡了,但又因为我用的是反向遍历,所以我并不知道a这个节点的地址。故而我无法改变a->right的指向。而为了解决这个问题,我不得不引入二级指针,让我得到b的地址的同时,也得知a.right的地址。
在这里插入图片描述
在这里插入图片描述
具体逻辑是任何对b的操作,都是先取得&a.right再解引用。这样相当于知道b便知道a.right的地址。
在这里插入图片描述
以上,如果不动手实践的话,大概是看不明白的。。。

2.2、删除节点

下面这个图表红的三个框,分别对应着删除一个节点的三种情况。
情况一:节点位于末端,直接删除即可;
情况二:节点有一个孩子,那么删除之后需要重新建立起父子连接;
情况三:节点有两个孩子,此时有两种策略。第一种策略是把右孩子,即右子树的最小值复制到自己身上。然后把被复制的那个节点删除。第二种策略是把左子树的最大值复制到自己身上,然后把被复制的那个节点删除。
在这里插入图片描述
下面这个图是遇到情况三之后采取策略一的示意图。看到这里,可能许多人还存在一个疑惑,难道不怕2下面还有孩子吗?答案是否定的,因为右子树的最小值,即为最值,自然就是没有孩子的节点了。所以此时删除2,只需要切换到情况一即可。
在这里插入图片描述

2.3、搜索方式

2.3.1、广度优先搜索(BFS)

如图,广度优先搜索是从根节点到末节点,一层一层这些遍历下去的。实现起来也并不复杂,只需要设置一个FIFO容器,比如queue。不断的从容器中读取节点即可。
在这里插入图片描述
queue内部的示意图大概是这样,但要注意,其实这些值不会同时出现在queue当中。
在这里插入图片描述
代码实现如下:

void bfs(Node** root)
{
    Node* temp = nullptr;
    queue<Node*> Q;
    if(*root != nullptr)   Q.push(*root);
    else return;
    
    while(!Q.empty())
    {
        temp = Q.front();
        if(temp->left != nullptr)    Q.push(temp->left);
        if(temp->right != nullptr)    Q.push(temp->right);
        cout << temp->data << endl;···
        Q.pop();
    }
}

2.3.2、深度优先搜索(DFS)

深度优先搜索分为前序遍历、中序遍历和后序遍历。主要还是依靠递归来实现。在代码上只是改动输出值的位置,相对容易。

void dfs_preorder(Node** node)			//前序
{
    if(*node == nullptr) return;
    cout << (*node)->data << endl;
    dfs_preorder(&((*node)->left));
    dfs_preorder(&((*node)->right));
}

void dfs_inorder(Node** node)			//中序
{
    if(*node == nullptr) return;
    dfs_preorder(&((*node)->left));
    cout << (*node)->data << endl;
    dfs_preorder(&((*node)->right));
}

void dfs_postorder(Node** node)		//后序
{
    if(*node == nullptr) return;
    dfs_preorder(&((*node)->left));
    dfs_preorder(&((*node)->right));
    cout << (*node)->data << endl;
}

分别看一下前序、中序、后序的打印顺序吧。注意,在以下这些图中,节点中的数字代表的是打印顺序。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

模拟BACnet设备(八)

文章目录 前言模拟呼梯设备的功能前期准备——xml文件的编写创建工程&#xff0c;建立BACnet模拟设备如何将设备的对象列表打包发送呢&#xff1f;被订阅的属性值变化时&#xff0c;如何主动通知对方&#xff1f;读写属性值完整代码小结 前言 前面一到七篇&#xff0c;从理论&…

[Collection与数据结构] PriorityQueue与堆

1. 优先级队列 1.1 概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能带有优先级&#xff0c;一般出队列时&#xff0c;可能需要优先级高的元素先出队列&#xff0c;该中场景下&#xff0c;使用队列显然…

Rust - 引用和借用

上一篇章末尾提到&#xff0c;如果仅仅支持通过转移所有权的方式获取一个值&#xff0c;那会让程序变得复杂。 Rust 能否像其它编程语言一样&#xff0c;使用某个变量的指针或者引用呢&#xff1f;答案是可以。 Rust 通过 借用(Borrowing) 这个行为来达成上述的目的&#xff0…

深入探索GDB:Linux下强大的调试神器

目录 一、GDB简介&#xff1a;源码级调试的基石 二、GDB基础操作&#xff1a;从入门到熟练 启动与基本命令 三、GDB进阶功能&#xff1a;解锁更深层次的调试能力 1. 回溯追踪&#xff1a;洞察调用栈 2. 动态内存检测&#xff1a;揪出内存问题 3. 条件断点与观察点&#…

JavaSE——程序逻辑控制

1. 顺序结构 顺序结构 比较简单&#xff0c;按照代码书写的顺序一行一行执行。 例如&#xff1a; public static void main(String[] args) {System.out.println(111);System.out.println(222);System.out.println(333);} 运行结果如下&#xff1a; 如果调整代码的书写顺序 , …

C++:继承作业题

1. 关于以下菱形继承说法不正确的是&#xff08; &#xff09; &#xfeff;class B {public: int b;};class C1: public B {public: int c1;};class C2: public B {public: int c2;};class D : public C1, public C2 {public: int d;};A.D总共占了20个字节B.B中的内容总共在D…

PE文件格式

PE文件格式 PE头&#xff1a;DOS头DOS存根NT头NT头&#xff1a;文件头NT头&#xff1a;可选头 节区头.text(代码)(节区头).data(数据)(节区头).rdata.idata&#xff0c;导入表 最后给出一个PE文件的16进制编辑器中的截图&#xff0c;找到其中每一个头的信息&#xff0c;和导入表…

2015NOIP普及组真题 3. 求和

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1971 核心思想&#xff1a; 本题的约束条件有两个&#xff1a; 条件1、colorx colorz 条件2、x、y、z的坐标满足 y − x z − y&#xff08;即 y 在 x 和 z 的中心位置&#xff09; …

scipy csr_matrix: understand indptr

See https://stackoverflow.com/questions/52299420/scipy-csr-matrix-understand-indptr

Esp8266 - USB开关分享(开源)

文章目录 简介推广自己gitee项目地址:嘉立创项目地址&#xff1a;联系我们 功能演示视频原理图嘉立创PCB开源地址原理图PCB预览 固件烧录代码编译烧录1. 软件和驱动安装2. 代码编译1. 安装所需要的依赖库文件2. 下载源代码3. 烧录代码 使用说明1. 设备配网2. 打开设备操作页面3…

NAT的知识点和实现

1.NAT的作用&#xff1a; &#xff08;1&#xff09;、把内网私网IP转换公网IP&#xff1b; &#xff08;2&#xff09;、隐藏内网&#xff0c;起到保护内网作用&#xff1b; &#xff08;3&#xff09;、适当的缓解的IPv4地址空间枯竭&#xff1b; &#xff08;4&#xff…

[RTOS 学习记录] 复杂工程项目的管理

[RTOS 学习记录] 复杂工程项目的管理 这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记&#xff0c;记录目的是为了个人后续回顾复习使用。 前置内容&#xff1a; 工程管理工具make及makefile 文章目录 1 批处理文件与makefile的综合使用1.1 批处理文件…

Qt实现XYModem协议(五)

1 概述 XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据&#xff0c;并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K&am…

4月23号总结

java实现发送邮件 在做聊天室项目的时候&#xff0c;由于需要发送邮箱验证码&#xff0c;所以自己查找了这方面的内容。 首先需要在Maven里面依赖 <dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><versio…

英伟达AI系列免费公开课

英伟达公开课官网地址 Augment your LLM Using Retrieval Augmented Generation Building RAG Agents with LLMs langchain的workflow: ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c90cb157c9c84bb5b3da380ec56f5c2a.png Generative AI Explained

Linux系统中安装MySQL

1、在电脑中安装虚拟机 2、df -h查看光盘是否挂载&#xff0c;没挂载用mount -o ro /dev/sr0 /media命令挂载 3、进入etc/yum.repos.d目录查看仓是否配置&#xff0c;若配置进行下一一步&#xff0c;未配置则进行配置 配置软件仓库 [rootlocalhost yum.repos.d]# vim rhle.r…

Linux中文件描述符与重定向的深入探索

目录 1. 理解C语言的文件操作函数 2. 操作系统的文件操作接口 3. 文件描述符详解和其内核本质 4. 如何理解Linux下一切皆文件 5. Linux中的重定向 5.1 输出重定向 5.2 追加重定向 5.3 输入重定向 6. 结合文件描述符理解重定向 7.重定向的系统调用 在Linux操作系统中&a…

springboot整合mybatis-plus模版

1.创建springboot项目 Maven类型Lombok依赖Spring Web 依赖MySQL Driver依赖pom.xml&#xff1a;<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/…

上门废品回收小程序,互联网回收拥有哪些特点?

随着社会的进步&#xff0c;人们的生活水平不断提高&#xff0c;产生的可回收物也在不断上升&#xff0c;每年垃圾站都能产生大量的可回收物&#xff0c;这也造成了资源的浪费。 目前&#xff0c;加快发展回收模式&#xff0c;提高我国回收效率成为了当下回收市场发展的重要方…

[笔试强训day04]

文章目录 WY22 Fibonacci数列NC242 单词搜索BC140 杨辉三角 WY22 Fibonacci数列 WY22 Fibonacci数列 #include<iostream> #include<cmath>using namespace std;int n;int main() {cin>>n;int a0,b1,c1;while(n>c){ab;bc;cab;}int ansmin(n-b,c-n);cout&l…