平衡二叉树
来源:互联网 发布:access数据库管理工具 编辑:程序博客网 时间:2024/06/08 10:17
平衡二叉树
一、概念
平衡二叉树:
平衡二叉树也是一种二叉排序树,满足:每一个结点的左子树和右子树的高度差不大于1
BF:平衡因子
二叉树结点的左子树深度减去右子树深度的值。若为负,说明左子树深度低于右子树深度;若为正,说明左子树深度高于右子树深度。根据平衡二叉树定义可知,平衡二叉树的平衡因子取值只有三种情况:-1 0 1
【归纳以上,可知】
满足平衡二叉树的两个条件:1)必须是二叉排序树;2)平衡因子只可能为 -1 0 1
最小不平衡树
以距离插入结点最近,且平衡因子的绝对值大于1的结点为根的二叉子树,称为最小不平衡树
平衡二叉树又称AVL树
二、算法实现原理:
在构建平衡二叉树的过程中,判定插入结点是否破坏了平衡性,若是,则找到最小不平衡树,在不破坏二叉排序
树的前提下,调整最小不平衡树结点间的连接关系,进行相应的左旋转操作和右旋转操作,使之成为平衡二叉树
【关于旋转】
1)当最小不平衡树的根节点的BF值为正,且它的左孩子的BF值也为正,则进行一次右旋转操作(顺时针旋转);
2)当最小不平衡树的根节点的BF值为负,且它的右孩子的BF值也为负,则进行一次左旋转操作(逆时针旋转);
简单的左、右旋转如下图所示(后面算法里的单旋情况可参考下图理解,其中F为插入结点):
3)当最小不平衡树的根节点的BF值为正,且它的左孩子的BF值为负,则先以它的左孩子为根进行左旋转,把左孩子的BF值的正负性调整为与它根节点正负性一致后,再以根节点为根进行右旋转;
4)当最小不平衡树的根节点的BF值为负,且它的右孩子的BF值为正,则先以它的右孩子为根进行右旋转,把右孩子的BF值的正负性调整为与它根节点正负性一致后,再以根节点为根进行左旋转。
对应算法里的双旋情况,结合3)4),可参考下图理解:
下面的代码分为五个模块:
左旋转、右旋转、左平衡旋转、右平衡旋转、构建平衡二叉树(插入)
考虑到【关于旋转】里的3)和4)情况,对应到排序二叉树的左支和右支处理方式不同,所以将左平衡旋转和右
平衡旋转分别写了功能模块,方便最终平衡二叉树的构建
三、平衡二叉树应用场合
如果需要查找的集合本身没有顺序,且在频繁查找过程中同时也需要经常地加入或者删除某些元素,则需要构建
一棵二叉排序树,但是不平衡的二叉排序树的查找是非常低效的,因此在构建二叉排序树时,就让它是一棵平衡二叉
树
平衡二叉树的查找时间复杂度为O(logn),插入和删除时间复杂度为O(logn)
四、代码实现(代码理解可参考上面的三个图)
本代码以下图生成的平衡二叉树作为测试例:
代码如下:
/*******************************************************************************************【平衡二叉树】平衡二叉树的查找时间复杂度为O(logn),插入和删除时间复杂度为O(logn)Author:tmwdate:2017-10-28********************************************************************************************/#include <stdio.h>#include <stdlib.h>#include <stdbool.h>/***平衡二叉树结点结构体定义****/typedef struct Balance_BinTreeNode{ int key; int bf; struct Balance_BinTreeNode *lchild; struct Balance_BinTreeNode *rchild;}B_BinTreeNode,*BiTree;/***左旋转操作***///以指针p指向的结点为根,对其进行左旋转操作,旋转完后,*p指向新的根结点void L_rotate( BiTree *p ){ BiTree R; R = (*p)->rchild;//R为*p所指向结点的右孩子 (*p)->rchild = R->lchild; R->lchild = (*p); *p = R; //*p指向新的根节点}/***右旋转操作***///以指针p指向的结点为根,对其进行右旋转操作,旋转完后,*p指向新的根结点void R_rotate( BiTree *p ){ BiTree L; L = (*p)->lchild;//R为*p所指向结点的左孩子 (*p)->lchild = L->rchild; L->rchild = (*p); *p = L;//*p指向新的根节点}/***左支平衡旋转操作***///当新插入的结点插在了原平衡二叉树的左支后,打破了原平衡,形成了最小不平衡二叉树//此时的代码需要对上面所述的【关于旋转】的1)和3)进行实现#define LH 1 //左支高#define EN 0 //等高#define RH -1 //右支高void Left_Part_Balance( BiTree *T )//*T指向最小不平衡树的根节点{ BiTree L; BiTree Lr; L = (*T)->lchild; //L为*T左孩子的根节点 Lr = L->lchild; switch( L->bf ) //检查看新结点插在了L的左孩子还是右孩子 { case LH://此时新结点插在了L的左孩子上,属于【关于旋转】的1),即最小不平衡树的根节点的BF值为正,且它的左孩子的BF值也为正 { //进行一次右旋转操作即可 L->bf = (*T)->bf = EN;//先更新bf值 R_rotate(T); break; } case RH://此时新结点插在了L的右孩子上,属于【关于旋转】的3),即最小不平衡树的根节点的BF值为正,且它的左孩子的BF值为负 //判断是插入L右孩子的左子树还是右子树---虽然都要进行双旋处理,但分开判定是因为双旋后,*T和L的bf有所不同 switch( Lr->bf ) { case LH://新结点插在了L右孩子的左子树上,更新特定值 (*T)->bf = RH; L->bf = EN; break; case RH://新结点插在了L右孩子的右子树上,更新特定值 (*T)->bf = EN; L->bf = LH; break;// case EN:// (*T)->bf = L->bf = EN;// break; } Lr->bf = EN; L_rotate( &((*T)->lchild) );//先左旋的目的是让(*T)->bf与L->bf符号保持一致 R_rotate(T); }}//分左支平衡旋转操作和右支平衡旋转操作处理是因为 左平衡处理的bf>0而右平衡处理的bf<0,对应当做双旋处理时,应//先右旋再左旋,与左平衡处理刚好相反,因此需分成两个函数处理/***右支平衡旋转操作***///当新插入的结点插在了原平衡二叉树的右支后,打破了原平衡,形成了最小不平衡二叉树//此时的代码需要对上面所述的【关于旋转】的2)和4)进行实现void Right_Part_Balance( BiTree *T )//*T指向最小不平衡树的根节点{ BiTree R; BiTree Rl; R = (*T)->rchild; Rl = R->lchild; switch( R->bf ) { case RH://说明新结点插入在*T右孩子根节点的右半支---只需要进行单左旋操作即可 (*T)->bf = R->bf = EN; L_rotate(T); break; case LH://说明新结点插入在*T右孩子根节点的左半支---需要进行双旋操作 switch( Rl->bf ) { case LH://新结点插入在R左孩子根节点的左半支,更新特定值 (*T)->bf = EN; R->bf = RH; break; case RH://新结点插入在R左孩子根节点的右半支,更新特定值 (*T)->bf = RH; R->bf = EN; break;// case EN:// (*T)->bf = R->bf = EN; } Rl->bf = EN; R_rotate( &((*T)->rchild) );//先右旋的目的是让(*T)->bf与L->bf符号保持一致 L_rotate(T); }}/***AVL树的创建***///采用递归的方式//*T指向一棵平衡二叉树(或为空或实平衡)//当树中存在关键字与待插入元素e相同的结点,则返回插入失败false//插入e时,若导致平衡二插树不平衡,则进行相应的左支平衡或右支平衡操作//布尔变量*taller跟踪新树左右是否长高bool Insert_AVL_Tree( BiTree *T , int e , bool *taller ){ if( !*T )//当为空树时插进根节点;当为找到合适位置时,申请内存插进原平衡二叉树 { *T = (BiTree)malloc(sizeof(B_BinTreeNode)); (*T)->bf = EN; (*T)->key = e; (*T)->lchild = (*T)->rchild = NULL; *taller = true; } else //没找到合适的插入位置时 { if( e == (*T)->key )//已存在关键字为e的结点,则返回错误 { *taller = false; return false; } if( e < (*T)->key ) //e比当前结点关键字小,则进入左半支 { if( !Insert_AVL_Tree( &(*T)->lchild , e , taller ) )//左支中存在关键字为e的结点,则返回false return false; if( *taller ) //e插进原平衡二叉树了,接下来判断是否造成不平衡 { switch( (*T)->bf )//检查原平衡度 { case LH://原左支高,现在结点加入左支,需进行左支平衡操作 Left_Part_Balance(T); *taller = false;//*taller值复位 break; case EN://原本平衡,现在结点加入左支,无需旋转,只不过树增高了 (*T)->bf = LH; *taller = true; break; case RH://原右支高,现在结点加入左支,现左右等高 (*T)->bf = EN; *taller = false; break; } } } else//e比当前结点关键字大,则进入右半支 { if( !Insert_AVL_Tree( &(*T)->rchild , e , taller ) )//右支中存在关键字为e的结点,则返回false return false; if( *taller ) { switch( (*T)->bf )//检查原平衡度 { case LH://原左支高,插入右支,现平衡,无需旋转,更新bf,*taller值即可 (*T)->bf = EN; *taller = false; break; case EN://原平衡,插入右支,无需旋转,更新bf,*taller值即可 (*T)->bf = RH; *taller = true; break; case RH://原右支高,插入右支,现需做右支平衡操作,平衡后,更新bf,*taller *taller = false; Right_Part_Balance(T);//在右支平衡操作和左支平衡操作里,已经对(*T)->bf值做了更新,因此这里不重复更新了 break; } } } } return true;}/***AVL树的中序打印检查***/void Print_In_order( BiTree *T ){ if( !*T ) return; else { Print_In_order(&(*T)->lchild); printf("%d ",(*T)->key); Print_In_order(&(*T)->rchild); }}int main(){ int len,i; bool res; int *array; BiTree T=NULL;//T初始化为空 printf("请输入待构建平衡二叉树的数组长度:\n"); scanf("%d",&len); array = (int*)malloc(len*sizeof(int)); printf("请输入数组元素(下面开始构建平衡二叉树,如果构建成功,则返回1,否则返回0\n):"); for( i = 0 ; i < len ; i++ ) { scanf("%d",&array[i]); res = Insert_AVL_Tree(&T,array[i],&res); printf(" %d 插入平衡二叉树状态为:%d\n",array[i],res); } printf("平衡二叉树中序打印结果为:"); Print_In_order(&T); return 0;}
代码运行结果:
- 平衡二叉树平衡法则
- 二叉树--二叉平衡树
- 平衡二叉树的
- 平衡二叉树
- 平衡二叉树
- 平衡二叉树
- 平衡二叉树
- 平衡二叉查找树
- 平衡二叉树 详解
- 平衡二叉树
- 平衡二叉树
- AVL 平衡二叉树
- 平衡二叉树
- 平衡二叉树-红黑树
- 平衡二叉树
- 平衡二叉树
- 平衡二叉树
- 平衡二叉树
- 腾讯发布《2015年度互联网安全报告》 移动支付成重灾区
- 扶持创业不是“囤屯桌子”
- 媒体人创业常遇青春期“综合症” :合伙人,融资及商业运营
- 有家,有X6&X6Plus,快才有意义:《vivo陪你快•乐回家》
- I Think I Need a Houseboat
- 平衡二叉树
- 使用itsdangerous生成确认令牌
- Error calling Driver#connect错误原因
- vs2015配置opencv3.1
- springboot2入门(3-maven相关)
- jsp的两种跳转方式和区别
- 前端面试题(不定期更新)
- P
- 王阳明心学 之 心即理感悟