• 作者:Cukor Zhong

    文章目录

    • 什么是平衡二叉树
    • 平衡二叉树的基本特点
    • 为什么会出现平衡二叉树
    • 二叉树四种不平衡的情况
    • C语言实现平衡二叉树

什么是平衡二叉树

平衡二叉树是具有平衡属性的有序二叉树所谓的平衡即当前树的左右子树高度差的绝对值不超过1。因为平衡二叉树是由苏联数学家Adelson-VelskiiLandis提出,所以又称为AVL树。

平衡二叉树的基本特点

  1. 是特殊的有序二叉树
  2. 左右子树高度差的绝对值不超过1
  3. 左右子树仍然是平衡二叉树

为什么会出现平衡二叉树

在学习平衡二叉树之前必定已经学过有序二叉树,有序二叉树的结构特点就是将数据有序的排好,查找起来快,但是有序二叉树有一个缺点,就是当节点呈现的状态是一边倒,那查找数据的时候就没有发挥出二叉树折半查找的优势了,这个时候是线性的查找(类似于链表的查找)。平衡二叉树就是解决有序二叉树一边倒的问题。如果有序二叉树是平衡的,那么查找数据就很快。时间复杂度为O(logn)O(logn)O(logn)。这样就充分发挥了二叉树的优势。

二叉树四种不平衡的情况

当树的左右子树高度差的绝对值大于1的时候就需要进行旋转操作,将不平衡的树变成平衡的树。以下是会出现的四种不平衡的情况

  • 左左不平衡
  • 右右不平衡
  • 左右不平衡
  • 右左不平衡

左左不平衡旋转成平衡状态:

右右不平衡旋转成平衡状态:

左右不平衡旋转成平衡状态:

右左不平衡旋转成平衡状态:

上面是图解这四种不平衡状态旋转成平衡状态的情况。

C语言实现平衡二叉树

平衡二叉树的结构体描述:

#define Ty int  //以整型数据为例
typedef struct Node
{Ty data;             //数据int height;          //高度struct Node* LChild; //左子树struct Node* RChild; //右子树
}Node,AVLTree;

初始化函数:

AVLTree* creatAVLTree(Ty data)
{AVLTree* tree = (AVLTree*)malloc(sizeof(AVLTree));assert(tree);tree->data = data;tree->height = 0;tree->LChild = NULL;tree->RChild = NULL;return tree;
}

辅助宏函数:

//辅助函数
#define HEIGHT(x) ((x==NULL)?(-1):(x->height))
#define MAX(a,b)  ((a>b)?(a):(b))
//获取树的新高度
#define GET_NEW_HEIGHT(x) (MAX(HEIGHT(x->LChild),HEIGHT(x->RChild)) + 1)

使用宏函数的好处是运行过程中不需要进行函数压栈的操作,效率快一点。

前序遍历平衡二叉树:

//前序打印
void show_pre(AVLTree* root)
{if(root==NULL)return;printf("data:%d\theight:%d\n",root->data,root->height);show_pre(root->LChild);show_pre(root->RChild);
}

使用前序遍历能更好地看出节点的高度,更方便还原平衡二叉树。

四种不平衡状态旋转的算法实现:

算法核心思想:找到新根的位置,然后进行对应的调整,最后返回新根的地址,这样就实现了旋转的操作,因为旋转后节点的高度改变了,所以在返回之前先调整一下节点的高度。

例如:左左不平衡进行旋转操作

因为是左左不平衡,所以新根的位置是当前根的左子树,那就使用一个指针(newRoot)去接收当前根的左子树,然后使劲将当前根拉下来,让新根代替当前根的位置,那就必须将当前根的LChild指向newRoot的右子树(因为newRoot不一定是空的所以不能直接让curRoot→LChild指向空)。最后就是将newRoot→RChild指向curRoot这样就把位置调整好了。在返回newRoot之前把curRootnewRoot的高度调整一下。保持树的准确性。

其他的不平衡情况也是类似的操作。

//出现不平衡的情况
//左左不平衡
Node *LL_Rotation(Node *curRoot)
{Node *newRoot = curRoot->LChild;curRoot->LChild = newRoot->RChild;newRoot->RChild = curRoot;curRoot->height = GET_NEW_HEIGHT(curRoot);newRoot->height = GET_NEW_HEIGHT(newRoot);return newRoot;
}//右右不平衡
Node *RR_Rotation(Node *curRoot)
{Node *newRoot = curRoot->RChild;curRoot->RChild = newRoot->LChild;newRoot->LChild = curRoot;curRoot->height = GET_NEW_HEIGHT(curRoot);newRoot->height = GET_NEW_HEIGHT(newRoot);return newRoot;
}
//左右不平衡
Node *LR_Rotation(Node *curRoot)
{curRoot->LChild = RR_Rotation(curRoot->LChild);return LL_Rotation(curRoot);
}
//右左不平衡
Node *RL_Rotation(Node *curRoot)
{curRoot->RChild = LL_Rotation(curRoot->RChild);return RR_Rotation(curRoot);
}

平衡二叉树的插入操作:

插入操作需要考虑到四种情况:

  1. 当前节点是空的
  2. 要插入进来的数据比当前节点的数据小
  3. 要插入进来的数据比当前节点的数据大
  4. 要插入进来的数据和当前节点的数据一样大

情况一的解决方案:直接申请一个节点内存。

情况二的解决方案:递归往左边跑,然后跑到对应的位置就申请内存,插入完成后判断需不需要调整。

情况三的解决方案:递归往右边跑,然后跑到对应的位置就申请内存,插入完成后判断需不需要调整。

情况四的解决方案:因为我们做的是一棵没有重复数据的平衡二叉树所以遇到这种情况的时候不进行插入操作。当然如果做的是一棵可以有重复数据的平衡二叉树,那遇到这种情况的时候可以个人的想法放左边放右边都可以。

源代码:

//插入数据
Node *insertNode(Node *curRoot, Ty data)
{//插入分有四个大情况if (curRoot == NULL)            //当前节点是空的curRoot = creatAVLTree(data); //如果是空就直接创建一个新的节点else if (data < curRoot->data)   //要插入的数据比当前节点的数据小{//往左边跑curRoot->LChild = insertNode(curRoot->LChild, data);//插入完成之后,判断需不需要调整树if (HEIGHT(curRoot->LChild) - HEIGHT(curRoot->RChild) == 2)//因为插入的位置在左边,所以插入完成之后,左子树的高度大于等于右子树高度curRoot = data < curRoot->LChild->data ? LL_Rotation(curRoot) : LR_Rotation(curRoot);}else if (data > curRoot->data) //要插入的数据比当前节点的数据大{//往右边跑curRoot->RChild = insertNode(curRoot->RChild, data);if (HEIGHT(curRoot->RChild) - HEIGHT(curRoot->LChild) == 2)//因为插入的位置在右边,所以插入完成之后,右子树的高度大于等于左子树高度curRoot = data > curRoot->RChild->data ? RR_Rotation(curRoot) : RL_Rotation(curRoot);}else //要插入的数据和当前节点的数据一样大printf("无法插入数据\n");//获取新高度curRoot->height = GET_NEW_HEIGHT(curRoot);return curRoot; //插入完成之后返回当前节点的指针
}

平衡二叉树的删除操作:

删除操作也是要考虑四个大情况:

  1. 当前节点是空的
  2. 要删除的数据比当前数据小
  3. 要删除的数据比当前数据大
  4. 要删除的数据和当前数据一样大

情况一的解决方案:没有删除的必要了,结束掉函数

情况二的解决方案:往左边递归找到对应位置,然后进行删除操作

情况三的解决方案:往右边递归找到对应位置,然后进行删除操作

情况四的解决方案:针对这个情况又要分为两个小情况

  • 当前节点的左子树和右子树都存在
  • 当前节点的左右子树至多有一个存在

具体解决方案看代码和注释

源代码:

//查找节点
//找最大节点
Node *maxNode(Node *curRoot)
{if (curRoot == NULL)return NULL;//往右边找while (curRoot->RChild)curRoot = curRoot->RChild;return curRoot;
}//找最小节点
Node *minNode(Node *curRoot)
{if (curRoot == NULL)return NULL;while (curRoot->LChild)curRoot = curRoot->LChild;return curRoot;
}//删除数据
Node *deleteNode(Node *curRoot, Ty data)
{//删除数据有四个大情况if (curRoot == NULL)   //当前节点是空的return NULL;       //删除了个寂寞直接结束掉整个函数if (data < curRoot->data) //要删除的数据比当前节点的数据小{//往左边跑curRoot->LChild = deleteNode(curRoot->LChild, data);//获取新高度curRoot->height = GET_NEW_HEIGHT(curRoot);//判断需不需要调整if (HEIGHT(curRoot->RChild) - HEIGHT(curRoot->LChild) == 2)curRoot = HEIGHT(curRoot->RChild->LChild) > HEIGHT(curRoot->RChild->RChild) ? RL_Rotation(curRoot) : RR_Rotation(curRoot);}else if (data > curRoot->data) //要删除的数据比当前节点的数据大{//往右边跑curRoot->RChild = deleteNode(curRoot->RChild, data);curRoot->height = GET_NEW_HEIGHT(curRoot);if (HEIGHT(curRoot->LChild) - HEIGHT(curRoot->RChild) == 2)curRoot = HEIGHT(curRoot->LChild->RChild) > HEIGHT(curRoot->LChild->LChild) ? LR_Rotation(curRoot) : LL_Rotation(curRoot);}else{ //要删除的数据和当前节点的数据一样大//针对于curRoot这个节点做删除操作//主要有两个主要的情况if (curRoot->LChild && curRoot->RChild) // curRoot有左子树和右子树{//先判断左右子树的高度,将高度比较高的子树的节点拿到上面来if (HEIGHT(curRoot->LChild) > HEIGHT(curRoot->RChild)){ //左子树的高度比右子树的高度高//找到左子树的最大节点Node *max = maxNode(curRoot->LChild);//找到之后就将max的数据替换curRoot的数据curRoot->data = max->data;//赋值完成之后继续递归,然后释放掉max对应的节点,不能直接在这里释放,因为要调整树的高度curRoot->LChild = deleteNode(curRoot->LChild, max->data);}else{ //左子树的高度小于等于右子树的高度//找到右子树的最小节点Node *min = minNode(curRoot->RChild);curRoot->data = min->data;curRoot->RChild = deleteNode(curRoot->RChild, min->data);}}else //上一种情况的否定,即curRoot没有子树或者只有一边{//释放内存Node *temp = curRoot;// curRoot拿到存在的子树curRoot = curRoot->LChild ? curRoot->LChild : curRoot->RChild;free(temp);}}return curRoot; //删除完成之后就返回当前节点
}

主函数测试:

int main()
{AVLTree *tree = NULL;for (int i = 1; i < 10; i++)tree = insertNode(tree, i);show_pre(tree); //前序打印树printf("----------------------------\n");//删除6这个节点tree = deleteNode(tree, 6);show_pre(tree);printf("程序结束\n");return 0;
}

运行结果:

data:4  height:3
data:2  height:1
data:1  height:0
data:3  height:0
data:6  height:2
data:5  height:0
data:8  height:1
data:7  height:0
data:9  height:0
----------------------------
data:4  height:3
data:2  height:1
data:1  height:0
data:3  height:0
data:7  height:2
data:5  height:0
data:8  height:1
data:9  height:0
程序结束

删除前和删除后的平衡二叉树:

数据结构之平衡二叉树C语言版相关推荐

  1. 《数据结构与算法 C语言版》—— 3.8习题

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第3章,第3.8节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 3.8习题 1名 ...

  2. 《数据结构与算法 C语言版》—— 2.5上机实验

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第2章,第2.5节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 2.5上机实验 实 ...

  3. 《数据结构与算法 C语言版》—— 2.7习题

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第2章,第2.7节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 2.7习题 1描 ...

  4. 构建线性表的c语言代码,数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码...

    1.运行环境 这里说明一下这里所有的C语言代码都是基于code::blocks 20.03编译运行的.当然一些其他集成开发环境应该也是可以的,个人不太喜欢功能太过强大的IDE,因为那同样意味着相关设置 ...

  5. 数据结构c语言版题库编程,数据结构习题库(c语言版)

    <数据结构习题库(c语言版)>由会员分享,可在线阅读,更多相关<数据结构习题库(c语言版)(104页珍藏版)>请在人人文库网上搜索. 1.wages in arrears. 2 ...

  6. 数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码

    数据结构严蔚敏C语言版-线性表顺序存储结构(顺序表)C语言实现相关代码 1.运行环境 2.准备工作 1)项目构建 1>新建一个SeqList项目 2>新建两个文件Sources和Heade ...

  7. c语言数据结构算法设计题,数据结构题集(C语言版)算法设计题答案[].doc

    数据结构题集(C语言版)算法设计题答案[].doc 第一章 绪论 1.16 void print_descending(int x,int y,int z)// 按从大到小顺序输出三个数 { scan ...

  8. 数据结构题及c语言版实验报告排序,数据结构二叉排序树实验报告

    <数据结构二叉排序树实验报告>由会员分享,可在线阅读,更多相关<数据结构二叉排序树实验报告(7页珍藏版)>请在装配图网上搜索. 1.实验报告课程名:数据结构(C语言版)实验名: ...

  9. 图书信息管理系统(数据结构顺序表,c语言版)

    图书信息管理系统 顺序表 一.实验题目 二.工具环境 三.实验问题 问题: 四.实验代码 五.解决方法 方法: 一.实验题目 图书信息管理系统 出版社有一些图书数据,为简单起见,在此假设每种图书只包括 ...

  10. 数据结构 笔记--向量 C++ 语言版 邓俊辉老师

    邓俊辉老师的书. 1 有数组为什么还需要向量? 几乎所有程序设计语言中都会有数组,程序设计语言的开发者将数组作为一种内置的数据类型. 数组在刚开始初始化的时候就已经固定了长度,也可以依照下标查找,但还 ...

最新文章

  1. RDKit | RDKit 中的RECAP进行分子裂解
  2. 数据挖掘经典算法——先验算法
  3. 零基础学Python-爬虫-1、网络请求Requests【网络操作理论基础与实践·请认真看看理论,理论基础决定后期高度】
  4. jdk1.8 idea 项目报错spring验证不通过
  5. Spring Boot笔记-新增嵌入式tomcat配置(修改tomcat中conf/server.xml)
  6. 7添加静态路由 hat red_win7系统怎么使用dos命令添加静态路由
  7. 用户修改了信息jwt服务器怎么识别,django使用JWT保存用户登录信息
  8. 特斯拉好事连连:自动驾驶新硬件性能提升10倍,两名新董事昨晚上任,股价涨5.6%...
  9. 更轻量级的Semaphore、AutoResetEvent、ThreadPool
  10. 题解 BZOJ 1912 luogu P3629 [APIO2010]巡逻 (树的直径)
  11. 5. 高性能MySQL --- 创建高性能索引
  12. AI 算法工程师面试高频 100 题(附答案详解)
  13. 5G无线关键技术 — 大规模天线技术
  14. 毕设论文word转pdf(错误!未定义书签。 已解决)
  15. 基于php+mysql的大学生四六级英语考试报名成绩管理
  16. CentOS 7 时区设置
  17. 放慢脚步是为了走得更快
  18. VproC#混合编程,加载图片
  19. python爬虫爬取图片并存入本地
  20. 光纤软件测试,20m光纤速度怎么测试 20m光纤速度测试方法【详解】

热门文章

  1. 西门子G120变频器介绍
  2. ScreenFlow 录制Mac电脑声音
  3. 如何使用Arduino 舵机SG90
  4. 中国人大网络教育计算机考试题,第九章计算机多媒体技术20秋人大测试题
  5. matlab 伽马校正曲线,【图像处理知识复习】02伽马校正matlab,C++实现
  6. CSS Li点击有蓝色浮层
  7. 基于神经网络的房价预测,BP神经网络预测房价
  8. bandicam的延迟问题和画质问题
  9. i7 8750h支持linux,6核神U!i7-8750H游戏本评测:碾压7代
  10. COMSOL仿真分析视频教程