AVL树的插入与旋转算法解析
来源:互联网 发布:长安张宝林怎么样知乎 编辑:程序博客网 时间:2024/05/16 19:13
1、AVL树的基本概念:
AVL树又称为平衡二叉排序(搜索)树,AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 “An algorithm for the organization of information” 中发表了它。分解开来看:AVL树是一棵二叉树进一步是一棵二叉排序树、并且AVL树涉及平衡因子这个概念。节点的平衡因子是它的右子树与左子树的高度(深度)之差 。如果一个节点的平衡因子为-1、0或1(即平衡因子绝对值不大于1) 则该节点被认为是平衡的;而对于一棵树,任意一个节点都是平衡的那么这棵树也是平衡的。在我们向AVL树中插入一个节点时,可能会打破该树的平衡性,所以就需要重启对其进行平衡操作。
举个简单的例子:
在未进行插入操作以前,对于节点45来说,其左子树高度为2,右子树为1,平衡因子绝对值等于1(之后的均用左右子树之差的绝对值表示平衡因子,不需再强调),所以此时是平衡的。但是对于第一种插入情况:插入的值小于根节点,因此插入了高度本就大的左子树中,平衡因子绝对值被增大到2因此不再是平衡二叉排序树。
2、引起AVL树的失衡的四种情况:
既然AVL树的平衡会被打破,那么我们需要考虑的就是恢复AVL树的平衡,关于平衡的恢复有四种情况,分为单旋转两种和双旋转两种。所谓单旋转就是只需要旋转一次,而双旋转则是需要旋转两次。为什么会有单双之分?旋转的过程又是怎样的?首先来看会引起失衡的四种情况(每种情况的标题是之后会用到的解决方案):
(1)、单向右旋平衡处理(RR):
插入节点导致左边过重,向“右”平衡(旋转)。如图,插入新节点后失重(丢失平衡):
(插入10(小于18)与插入20(大于18但小于32)属于同一种情况RR,之后我们会以插入10为例分析旋转算法)
(2)、单向左旋平衡处理(LL):
同样,60(左)和80(右)对于旋转来说也是一样的。
(3)、双向旋转平衡处理(LR先左后右):
根节点的左子树的右子树也有两种情况,但都是一种解决方法,我们以右插为例。
(4)、双向旋转平衡处理(RL先右后左):
根节点的右子树的左子树也有两种情况,我们以左插为例。
3、解决失衡的妙招——旋转:
既然已经了解了会引起失衡的原因以及相关情况,我们就可以根据这四种情况来分析相关的旋转算法了(由于单旋转RR与LL思想是相似的,而双旋转RL和LR的思想也是相似的,所以我们分别以RR和LR做重点分析):
(1)、单旋转RR:
(2)、双旋转LR:
为什么向左子树的左子树中插入是单旋转,而往左子树的右子树中插入是双旋转呢?其实很简单:一次旋转之后达不到平衡(因为对LR的情况来说,新节点插入在右子树时,若还采用原有的单旋转会将本来的一边失重情况转到另一边失重,达不到目的),所以想到去再旋转一次(就像求表达式极限时,用一次洛必达法则求不出,就会想到对第一次的结果再用一次洛必达法则而去求二阶导)。但是这里的二次旋转并不是对同一个根节点旋转两次(右旋转第一次平衡不了,对根节点再左旋转一次会恢复到最初的不平衡状态,大家可以自己试一试就知道了),那么我们看看LR的实际旋转过程:
而RL这跟LR也是相似的,只需将遇到right的地方换成left,遇到left的地方换成right即可。
4、插入算法的测试代码:
#include<stdio.h>#include<stdlib.h>#include<time.h>#include<string.h>typedef struct _node{ int data; int high;//记录当前节点深度 struct _node* left; struct _node* right;}NODE;/*创建新节点*/NODE *construct_node(int data){ NODE * tmp = (NODE *)malloc(sizeof(NODE)); memset(tmp, 0, sizeof(NODE)); tmp->high = 0;//新节点高度为0 tmp->data = data; tmp->left = tmp->right = NULL; return tmp;}/*中序遍历*/void inorder_traversal(NODE * root){ if(!root) return ; NODE * tmp = root; if(tmp){ inorder_traversal(tmp->left); printf("%d(%d) ",tmp->data, tmp->high);//打印深度可以不打印,因为有中序与先序可以还原之后再判断效果 inorder_traversal(tmp->right); }}/*先序遍历*/void preorder_traversal(NODE * root){ if(!root) return ; NODE * tmp = root; if(tmp){ printf("%d(%d) ",tmp->data, tmp->high); preorder_traversal(tmp->left); preorder_traversal(tmp->right); }}int hight(NODE * root){ if(!root) return -1; else return root->high;}int max(int l, int r){ return (l > r ? l : r);}/*这四个旋转函数非NULL在调用之前就已经判断*/NODE *single_rotate_left(NODE * root)//整个树是左旋的单旋转{ NODE * tmp = root->left; root->left = tmp->right; tmp->right = root; //旋转后深度随之变化,需要修改深度 root->high = max(hight(root->left), hight(root->right)) + 1; tmp->high = max(hight(tmp->left), hight(tmp->right)) + 1; return tmp;//返回新的子树根节点}NODE *single_rotate_right(NODE * root)//整个树是右旋的单旋转{ NODE * tmp = root->right; root->right = tmp->left; tmp->left = root; root->high = max(hight(root->left), hight(root->right)) + 1; tmp->high = max(hight(tmp->left), hight(tmp->right)) + 1; return tmp;}NODE *double_rotate_left(NODE * root)//整个树是左旋的双旋转{ root->left = single_rotate_right(root->left);//先将右子树右旋并接收改变之后的右子树的根节点 return single_rotate_left(root);//再将整个树左旋}NODE *double_rotate_right(NODE * root)//整个树是右旋的双旋转{ root->right = single_rotate_left(root->right);//先将左子树左旋并接收改变之后的左子树的根节点 return single_rotate_right(root);//再将整个树右旋}/*先根据二叉排序树规则插入,插完后进行平衡判断*/NODE * insert_node(NODE * root, NODE * newnode){ if(!root) return newnode; NODE * tmp = root; if(tmp->data > newnode->data){ tmp->left = insert_node(tmp->left, newnode);//递归向左子树插入 //旋转 if(hight(tmp->left) - hight(tmp->right) == 2){ if(tmp->left->data > newnode->data)//插到左子树左边,右旋(单旋转) tmp = single_rotate_left(tmp); else//插到左子树右边,左子树先左旋整个树再右旋(双旋转) tmp = double_rotate_left(tmp); } } else if(tmp->data < newnode->data){ tmp->right = insert_node(tmp->right, newnode);//递归向右子树中插入 //旋转 if(hight(tmp->right) - hight(tmp->left) == 2){ if(tmp->right->data < newnode->data)//插到右子树右边,左旋(单旋转) tmp = single_rotate_right(tmp); else//插到右子树左边,右子树先右旋整个树再左旋(双旋转) tmp = double_rotate_right(tmp); } } tmp->high = max(hight(tmp->left), hight(tmp->right)) + 1; return tmp;}int main(int argc, char *argv[]){ if(argc != 2){ printf("Parameter is failure!\n"); exit(EXIT_FAILURE); } int num = atoi(argv[1]); srand(time(NULL));//用随机数做测试 NODE * root = NULL; int i; //由于程序中没有对相同节点处理,所以测试时可能会有实际插入树中的节点个数少于num的情况 for(i = 0; i<num; i++){ NODE * tmp = construct_node(rand()%1000); root = insert_node(root, tmp); } /*打印出先序与中序遍历结果验证是否符合AVL树的规则创建*/ inorder_traversal(root); printf("\n"); preorder_traversal(root); printf("\n"); return 0;}
我们简单运行测试一下,下图为每次插入后的中序与先序遍历结果(我在循环中打印了每插入一个节点后的中序遍历与先序遍历结果,可以观察每次的插入都满足AVL树的限制):
还原最终的树如图(其结果是满足AVL树特征的):
扩大到节点数为20(第一行为中序、第二行为先序),并还原二叉树后如下图:
- AVL树的插入与旋转算法解析
- AVL 树的插入与旋转
- AVL树的旋转与插入操作
- [数据结构与算法]AVL树的旋转
- AVL树的插入与删除,重点是四种旋转
- AVL树的插入、删除、旋转
- AVL树的插入、删除、旋转
- AVL树的旋转,插入,删除操作
- 【数据结构】AVL树的旋转和插入
- 【AVL树】AVL树的插入操作以及旋转
- 算法-AVL树的插入
- AVL树的插入算法
- avl树的插入(含单旋转,双旋转)
- AVL树的单双旋转解析
- AVL树插入算法
- AVL树的旋转分析与实现
- AVL树的创建与旋转
- avl树的基本概念与节点旋转
- 微信小程序 下拉菜单
- DOS命令讲解
- Server responded" Algorithm negotiation failed"
- 第一章17函数模板
- 1105
- AVL树的插入与旋转算法解析
- 常用的清除浮动的方法
- VC++ 字符串操作学习总结
- FileProvider使用
- ERROR
- position属性的5个值
- 【修正】问题五十五:怎么用ray tracing画Utah teapot (bicubic bezier patches)
- 第一章17重载函数
- Java面试题收集