【前置知识】二叉树和二叉排序树

目录

1.什么是AVL树

2.怎么平衡


前言
对于大量的数据而言,链表的线性访问时间太慢,不宜使用。本章节将会介绍一种简单的数据结构:树(tree),其大部分操作的运行时间平均为O(logN)。在数据结构中树是非常有用的抽象概念,在本篇中我们将讨论二叉平衡树,为后续高阶搜索树打下基础。


1.什么是AVL树

AVL(Adelson-Velskii and Landis)树即二叉平衡树,是历史上第一棵自平衡树,本质上是一棵带有平衡条件的二叉排序树。

想要让AVL树保持平衡的条件有很多种,但这些平衡条件必须容易保持,而且必须保证树的深度是O(logN),最简单的实现是要求左右子树的高度差的绝对值不超过1

2.怎么平衡

我们总是要对一棵树进行增删查改的操作,但是增加或者删除一个节点可能会破坏现有的平衡性质,因此我们更关心的是如何让AVL树保持一个平衡的状态,事实上,这总是可以通过对树进行简单的修正来做到,这个操作我们称为旋转(rotation)。

①左旋

对于如下所示的一棵现在是平衡的AVL树,我们要插入一个新的节点99,根据标准的二叉排序树(以下简称BST)的插入步骤, 节点99会被插入到根节点66的右子树上;

此时我们会发现,根节点66的左子树的高度是1,而它的右子树的高度此时变成了3,显然3与1的差值是大于1的,是不平衡的状态;

对于节点66,我们称之为失衡节点。 而我们要做的:

  1. 将失衡节点A的右孩子B替换到失衡节点的位置;
  2. 如果此时右孩子B还有左孩子C,就把C变成失衡节点A的右孩子;
  3. 失衡节点A变成原先右孩子B的左子树;

我们对失衡节点66执行上述步骤后,得到如下的树,此时树又回到了平衡的状态;

为了方便记忆,对于这种情况我们先称为:右孩子的右子树(节点99插在失衡节点66的右孩子77的右子树88上)。

代码实现也很简单只需按照上述步骤写就行了;

 avlnode* k2 = node->right;node->right = k2->left;k2->left = node;

②右旋

同样的当出现左孩子的左子树的情况时,我们也只需要简单的将上述的步骤反过来,即右旋

  1. 将失衡节点A的左孩子B替换到失衡节点的位置;
  2. 如果此时左孩子B还有右孩子C,就把C变成失衡节点A的左孩子;
  3. 失衡节点A变成原先左孩子B的右子树;

 avlnode* k2 = node->left;node->left = k2->right;k2->right = node;

③先左旋再右旋

但是单一的左旋与单一的右旋一定不是万能的。如果出现了例如左孩子的右子树的情况,我们尝试用单一的右旋操作,是无法实现平衡的(可以自己动手画画),因为这里并没有减少右子树的高度,这时候我们就要先对失衡节点的左孩子左旋,降低右子树的高度,再对失衡节点右旋

至于旋转操作和上面是一样的这里就不再赘述;

④先右旋再左旋

如果出现的是右孩子的左子树的情况,就先对失衡节点的右孩子右旋,降低左子树的高度,再对失衡节点左旋

最后别忘记了,每一次旋转后都要重新计算一下树和每个节点的高度。这里代码实现的大致思路就是通过递归使每个节点自下往上分别+1,而叶子节点高度为0 ,这样我们在确定一个树的节点时只需要知道它子节点的中最大的高度并+1就好

旋转的完整代码如下:

#define HEIGHT(node) ((node == NULL) ? 0 : (((avlnode*)(node))->height))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
//获取节点高度
int getNode_Height(avlnode* node)
{return HEIGHT(node);
}//右旋LL
avltree left_left_rotation(avltree node)//node是失衡节点
{avlnode* k2 = node->left;node->left = k2->right;k2->right = node;//所有的旋转操作都要改变树的高度node->height = MAX(getNode_Height(node->left), getNode_Height(node->right)) + 1;k2->height = MAX(getNode_Height(node->left), getNode_Height(node->right)) + 1;return k2;
}//左旋RR
avltree right_right_rotation(avltree node)//node是失衡节点
{avlnode* k2 = node->right;node->right = k2->left;k2->left = node;//所有的旋转操作都要改变树的高度node->height = MAX(getNode_Height(node->left), getNode_Height(node->right)) + 1;k2->height = MAX(getNode_Height(node->left), getNode_Height(node->right)) + 1;return k2;
}//先左旋再右旋LR
avltree left_right_rotation(avltree node)
{node->left = right_right_rotation(node->left);node = left_left_rotation(node);return node;
}//先右旋再左旋RL
avltree right_left_rotation(avltree node)
{node->right = left_left_rotation(node->right);node = right_right_rotation(node);return node;
}

插入的实现按照BST的标准插入步骤小于插入左边大于插入右边即可,但是同时需要注意判断插入之后当前节点可能存在失衡的情况,这时就要用旋转的操作及时修正;

这里同样是通过递归更新每一个节点的高度。

avltree insert(avltree node, int key)
{//当根节点为NULLif (node == NULL){//创建节点avlnode* root = create(key, NULL, NULL);node = root;}//如果说不为空 考虑往哪边插入//往左子树插入else if (key < node->data){//递归去寻找待插节点的位置node->left = insert(node->left, key);//因为是左边插入 如果有失衡就一定是左边失衡//高度差等于2 就是最小失衡树if (HEIGHT(node->left) - HEIGHT(node->right) == 2){//判断此时是属于哪一种失衡//如果插入的值是在节点的左子树 那么就是左子树的左子树LL的情况if (key < node->left->data){left_left_rotation(node);}else{//否则就是左子树的右子树LR的情况left_right_rotation(node);}}}else if (key > node->data){//递归去寻找要插入的位置node->right = insert(node->right, key);//往右边插入 如果有失衡那么一定是右边失衡if (HEIGHT(node->right) - HEIGHT(node->left) == 2){//判断属于哪一种情况//RRif (key > node->right->data){right_right_rotation(node);}//RLelse{right_left_rotation(node);}}}//重新调整二叉树的深度//树的深度取决于大的那一个//通过这个操作把每一个节点的高度都更新了 因为上面有递归访问了每一个节点node->height = MAX(getNode_Height(node->left), getNode_Height(node->right)) + 1;//插入了一个新节点 所以加1return node;
}

删除的操作就是单纯的对树进行BST的删除,与插入不同的是,插入只可能引起某一个节点的失衡,而删除操作有可能会导致多个节点都失衡,这时候就需要向上检索失衡并修正

和BST的标准删除一样,删除分为三种情况:

  1. 删除的是叶子;
  2. 删除的节点只有一个孩子;
  3. 删除的节点既有左子树又有右子树;

AVL树中的难点就在于删除的操作(情况多且复杂),这里一定一定要先把逻辑理顺了,才去结合代码。

//查找操作
avltree searchNode(avltree node, int key)
{//如果是空树或者要删除的值就是根节点if (node == NULL || node->data == key){return node;}else if(key < node->data){searchNode(node->left, key);}else if (key > node->data){searchNode(node->right, key);}
}//寻找左子树的最大值
avltree findMax(avltree tree)
{if (tree != NULL){while (tree->right){tree = tree->right;}}return tree;
}//删除操作
avltree deleteNode(avltree tree, int key)
{//node是找到的要删除的那一个节点avlnode* node = searchNode(tree, key);if (node == NULL || tree == NULL){//如果要删除的节点为空或者是空树return tree;}//还是要去判断属于哪一种失衡情况//要删除的节点在左子树if (key < tree->data){tree->left = deleteNode(tree->left, key);//判断有没有失衡 //和插入正好相反!如果是在左边删除导致的失衡 那么一定是右子树高if (HEIGHT(tree->right) - HEIGHT(tree->left) == 2){//如果失衡且右子树的左子树存在 那么就是RL的情况if (tree->right->left){tree = right_left_rotation(tree);}else{tree = right_right_rotation(tree);}}}//要删除的节点在右子树else if (key > tree->data){tree->right = deleteNode(tree->right, key);//如果是在右边删除导致的失衡 那么一定是左子树高if (HEIGHT(tree->left) - HEIGHT(tree->right) == 2){//如果失衡且左子树的右子树存在 那么就是LR的情况if (tree->left->right){tree = left_right_rotation(tree);}else{tree = left_left_rotation(tree);}}}//找到了待删除的节点 就删除else{//保证二叉排序树的有序性//如果左右孩子都有的情况 找左孩子的最大值替换要删除的节点if (tree->left && tree->right){avlnode* left_max_node = findMax(tree->left);tree->data = left_max_node->data;//还要删除原先的最大值节点tree->left = deleteNode(tree->left, left_max_node->data);}else{//独子或者无子的情况tree = tree->left ? tree->left : tree->right;}}if(tree){tree->height = MAX(getNode_Height(tree->left), getNode_Height(tree->right)) + 1;//重置每一个节点高度}return tree;
}

最后附上main函数和测试结果:

int main()
{avltree tree = NULL;int arr[] = { 12, 9, 17, 6, 11, 13, 18, 4, 15 }; int size = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < size; i++){tree = insert(tree, arr[i]);}//prev_order(tree);in_order(tree);printf("\n");tree = deleteNode(tree, 18);in_order(tree);system("pause");return 0;
}

如果代码有疑问的同学可以私信问我,一定要先把逻辑理顺!一定要先把逻辑理顺!一定要先把逻辑理顺!重要的话说三遍(;д;),不然后面的高阶搜索树会很吃亏的!

C语言实现二叉平衡树相关推荐

  1. 数据结构源码笔记(C语言):二叉平衡树的相关操作算法

    //二叉平衡树的相关运算 #include<stdio.h> #include<malloc.h> #include<string.h>typedef char I ...

  2. 二叉树--二叉平衡树

    二叉平衡树是二叉树中最为最要的概念之一,也是在语言库或者项目中应用比较广泛的一种特殊的树形结构. 二叉平衡树 AVL树是高度平衡的而二叉树.它的特点是:AVL树中任何节点的两个子树的高度最大差别为1. ...

  3. 数据结构与算法——二叉平衡树(AVL树)详解

    文章目录 AVL树概念 不平衡概况 四种平衡旋转方式 RR平衡旋转(左单旋转) LL平衡旋转(右单旋转) RL平衡旋转(先右后左双旋转) LR平衡旋转(先左后右单旋转) java代码实现 总结 AVL ...

  4. C语言在二叉搜索树找到第k个最小元素(附完整源码)

    C语言在二叉搜索树找到第k个最小元素 C语言在二叉搜索树找到第k个最小元素完整源码(定义,实现,main函数测试) C语言在二叉搜索树找到第k个最小元素完整源码(定义,实现,main函数测试) #in ...

  5. AVL树(二叉平衡树)详解与实现

    公众号文章链接 AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树 ...

  6. 【Python数据结构】——二叉平衡树AVL(查找、构建、删除、插入、打印、遍历)

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/7/28 20:57 # @Author : @linlianqin # @S ...

  7. 二叉平衡树平衡方法(RR、LL、RL、LR)

    二叉平衡树的平衡方法 (RR.LL.RL.LR) 1.RR型 右单旋 右右型:插入节点在不平衡因子节点右子树的右边 右单旋:向左旋转 2.LL型 左单旋 左左型:插入节点在不平衡因子节点的左子树的左边 ...

  8. 【数据结构进阶】二叉平衡树

    一. 二叉平衡树 概念 二叉搜索树有称 二叉排序树,它也可以是一个空树. 如果它的左子树不为空,则左子树上所有结点的值都小于根结点的值 如果他的右子树不为空,则右子树上所有结点的值都大于根结点的值 它 ...

  9. 二叉平衡树(C++)

    背景: 二叉平衡树,就是根据二叉搜索树进行优化,让其速度更加的快,如果读者没有学过二叉搜索树,可以前往以下链接查看资料: http://t.csdn.cn/Sjjjshttp://t.csdn.cn/ ...

最新文章

  1. FPGA边沿检测Verilog实现(包含上升沿,下降沿,双边沿)
  2. 3.2Python的循环结构语句:
  3. Scrapy匹配xpath时tbody标签的问题
  4. Missing artifact net.sf.json-lib:json-lib:jar:2.4错误和Eclipse安装Maven插件错误
  5. 静态网站与动态网站的区别 静态网站生成工具
  6. SQL Server 中的例程分析
  7. 【广东大学生网络攻防大赛-WriteUp(非官方)】Reverse | pyre
  8. 21岁雅虎卖身引发一波怀旧 市值曾高达1250亿美元
  9. Beyond Compare比较class文件
  10. 刘晓震:新浪博客应用架构分享
  11. 苹果官方付费升级内存_vivo用户必看!官方推出扩容服务,最高升级到128G!
  12. css两列等高,css多列等高布局
  13. Linux_29_Linux-Vsftpd
  14. 2021年广东省安全员B证第三批(项目负责人)新版试题及广东省安全员B证第三批(项目负责人)作业模拟考试
  15. 项目进度管理服务器,工程项目进度管理软件Asta Powerproject——成功项目背后的力量...
  16. Golang学习笔记汇总
  17. vue输入查询内容但不查询,切页刷新表格会出现触发查询的不合理情况
  18. pwnable.kr wp leg
  19. 用vue实现小米商城登录注册页面
  20. 人脸识别闸机的应用场景

热门文章

  1. 正宇控股集团丨正宇,扬帆起航
  2. PaperWeekly第44期 | Kaggle求生
  3. Nuxt SSR 服务端渲染 详解
  4. rep()函数的使用
  5. 关于Java基础部分知识的综合应用之成员调度系统的项目
  6. 华为模拟器eNSP V100R003C00(不限速下载)
  7. 实验四: IPv6路由选择协议配置
  8. Lambda表达式的一些相关知识理解
  9. 2021年数学建模国赛湖北赛区推荐国奖名单
  10. Axure RP9 轮播图交互