AVL树是一种高度平衡的二分查找树,性质是任何两个子树之间的高度差的绝对值不超过1。我们可以用平衡因子来记录,在每个节点加上一个bf的数据域,记录该节点的左子树高度减去右子树高度的结果。那么,AVL树的每个节点的平衡因子的绝对值就不会超过1。
那么首先,做一些初始化的动作:
/*初始化节点*/
void initOneNode( Node * node, int val ){
(*node).p = NULL;
(*node).left = NULL;
(*node).right = NULL;
(*node).value = val;
(*node).bf = 0;
}
接着是树的两个基本操作——左旋、右旋
这是右旋操作,代码如下
/*右旋函数(不考略x左节点为空,在调用函数前判断,未修订平衡因子)*/
void rightRotate( Node ** root, Node * x ){
Node * y = (*x).left;
(*x).left = (*y).right;
if ( (*y).right != NULL ){
(*(*y).right).p = x;
}
(*y).p = (*x).p;
if ( (*x).p != NULL ){ //原x不是根节点
if ( x == (*(*x).p).left ){
(*(*x).p).left = y;
}else{
(*(*x).p).right = y;
}
}else{
*root = y;
}
(*y).right = x;
(*x).p = y;
}
当然,左旋也是类似的
/*左旋函数(不考略x右节点为空,在调用函数前判断,未修订平衡因子)*/
void leftRotate( Node ** root, Node * x ){
Node * y = (*x).right;
(*x).right = (*y).left;
if ( (*y).left != NULL ){
(*(*y).left).p = x;
}
(*y).p = (*x).p;
if ( (*x).p != NULL ){ //原x不是根节点
if ( x == (*(*x).p).left ){
(*(*x).p).left = y;
}else{
(*(*x).p).right = y;
}
}else{
*root = y;
}
(*y).left = x;
(*x).p = y;
}
还有些地方介绍有左右旋之类的,不用在意,也只是这两个基本操作的复合操作而已。
准备工作做好了,那么就开始插入操作
函数头是
void insertAVL( Node ** root, Node * newNode );
首先是二分查找树的插入操作,这个不在这里多说(这里允许重复值插入,默认插在右子树),代码如下
/*二分查找树的插入操作*/
if ( *root == NULL ){ //如果插入的是第一个节点
*root = newNode;
(**root).bf = 0;
return;
}
Node * tempNode = *root;
Node * nowNode = tempNode;
while ( tempNode != NULL ){
nowNode = tempNode;
if ( (*newNode).value < (*tempNode).value ){
tempNode = (*tempNode).left;
}else{
tempNode = (*tempNode).right;
}
}
(*newNode).p = nowNode;
if ( (*newNode).value < (*nowNode).value ){
(*nowNode).left = newNode;
}else{
(*nowNode).right = newNode;
}
接下来是比较复杂的平衡性修复操作及平衡因子更新操作
思路是这样的,从插入点向根部回溯,若是插在当前节点左子树中,那么当前节点的平衡因子加1,同样,右子树则减1。那么这里可以做一些判断,或是叫做优化:
1、如果当前节点的平衡因子变为0,那么停止操作,因为那说明必定是在当前节点的高度少一的子树上插入节点,所以当前的总高度不变,也就是说对当前节点以上(父、祖父节点等等)的节点没有影响;
2、如果当前节点的平衡因子变为1或-1,当前节点仍处于平衡状态,不需要调整,但以上节点则不一定,所以需要继续回溯;
3、如果当前节点的平衡因子变为2或-2,当前节点不平衡,需要调整;
那么以下找到处理点的代码
nowNode = NULL;
tempNode = (*newNode).p;
while ( tempNode != NULL ){
if ( (*newNode).value < (*tempNode).value ){
(*tempNode).bf ++;
}else{
(*tempNode).bf --;
}
if ( (*tempNode).bf == 0 ){ //必定是在节点tempNode的高度少一的子树上插入节点,所以tempNode的总高度不变
break;
}else if( (*tempNode).bf == 2 || (*tempNode).bf == -2 ){
nowNode = tempNode; //记录当前最小不平衡子树的根节点
break;
}
tempNode = (*tempNode).p;
}
if ( nowNode == NULL ){ //平衡,不需要修复
return;
}
为什么只需要记录当前最小不平衡子树的根节点,这里可以稍作分析。以当前节点的平衡因子是2为例,2必是从1变来,也就是说插入点位于本来高度较大的子树中,高度差由1变2,那么已当前节点为根的子树的高度无疑会加1,那么(可以先看下面操作)看下面的几种调节方法可知,调节后高度均下降了1(看图最快)。所以插入节点前与,插入节点后并调节后的高度相同,也就是说当前节点的父节点或祖父节点等等平衡因子没有变化,也就不需要继续回溯了。
接下来就是具体怎么做了
1、如果当前节点的平衡因子为2
(1)如果当前节点的左孩子的平衡因子为1,说明在当前节点左孩子节点的左子树上插入了节点
操作方式是一个右旋就可以了
可以看出C、D、E的平衡因子没有变化,A、B的平衡因子变为零,整棵子树的高度也没有变化,不会影响到上层
if ( (*(*nowNode).left).bf == 1 ){ //说明在当前节点左孩子节点的左子树上插入了节点
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 0;
rightRotate( root, nowNode );
}
(2)如果当前节点的左孩子的平衡因子为-1,说明在当前节点左孩子节点的右子树上插入了节点
这种情况操作比较简单,先以左孩子为根左旋,再以当前节点为根右旋,但平衡因子调节要分几种情况
a)当前节点的左孩子的左孩子的右孩子的平衡因子为0
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 0;
(*(*(*nowNode).left).right).bf = 0;
b)当前节点的左孩子的左孩子的右孩子的平衡因子为-1
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 1;
(*(*(*nowNode).left).right).bf = 0;
c)当前节点的左孩子的左孩子的右孩子的平衡因子为1
(*nowNode).bf = -1;
(*(*nowNode).left).bf = 0;
(*(*(*nowNode).left).right).bf = 0;
调节平衡因子之后是旋转操作
leftRotate( root, (*nowNode).left );
rightRotate( root, nowNode ); //树的高度减1
2、当前点平衡因子为-2
这个和2的对称操作就行了
我们可以把平衡调整操作集成到一个函数里面,代码如下
void fixupBlance( Node ** root, Node * nowNode ){
if ( (*nowNode).bf == 2 ){ //必有左孩子
if ( (*(*nowNode).left).bf == 0 ){
(*nowNode).bf = 1;
(*(*nowNode).left).bf = -1;
rightRotate( root, nowNode ); //不影响树的高度
}else if( (*(*nowNode).left).bf == 1 ){
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 0;
rightRotate( root, nowNode ); //树的高度减1
}else{ //左孩子平衡因子为-1(左孩子一定有右孩子)
if ( (*(*(*nowNode).left).right).bf == 0 ){
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 0;
(*(*(*nowNode).left).right).bf = 0;
}else if( (*(*(*nowNode).left).right).bf == 1 ){
(*nowNode).bf = -1;
(*(*nowNode).left).bf = 0;
(*(*(*nowNode).left).right).bf = 0;
}else{ //为-1
(*nowNode).bf = 0;
(*(*nowNode).left).bf = 1;
(*(*(*nowNode).left).right).bf = 0;
}
leftRotate( root, (*nowNode).left );
rightRotate( root, nowNode ); //树的高度减1
}
}else if( (*nowNode).bf == -2 ){ //必有右孩子
if ( (*(*nowNode).right).bf == 0 ){
(*nowNode).bf = -1;
(*(*nowNode).right).bf = 1;
leftRotate( root, nowNode ); //不影响树的高度
}else if( (*(*nowNode).right).bf == -1 ){
(*nowNode).bf = 0;
(*(*nowNode).right).bf = 0;
leftRotate( root, nowNode ); //树的高度减1
}else{ //右孩子平衡因子为1(右孩子一定有左孩子)
if ( (*(*(*nowNode).right).left).bf == 0 ){
(*nowNode).bf = 0;
(*(*nowNode).right).bf = 0;
(*(*(*nowNode).right).left).bf = 0;
}else if( (*(*(*nowNode).right).left).bf == 1 ){
(*nowNode).bf = 0;
(*(*nowNode).right).bf = -1;
(*(*(*nowNode).right).left).bf = 0;
}else{ //为-1
(*nowNode).bf = 1;
(*(*nowNode).right).bf = 0;
(*(*(*nowNode).right).left).bf = 0;
}
rightRotate( root, (*nowNode).right );
leftRotate( root, nowNode ); //树的高度减1
}
}
}
/*节点插入函数(小则在左边,大于等于在右边)*/
void insertAVL( Node ** root, Node * newNode ){
/*二分查找树的插入操作*/
if ( *root == NULL ){ //如果插入的是第一个节点
*root = newNode;
(**root).bf = 0;
return;
}
Node * tempNode = *root;
Node * nowNode = tempNode;
while ( tempNode != NULL ){
nowNode = tempNode;
if ( (*newNode).value < (*tempNode).value ){
tempNode = (*tempNode).left;
}else{
tempNode = (*tempNode).right;
}
}
(*newNode).p = nowNode;
if ( (*newNode).value < (*nowNode).value ){
(*nowNode).left = newNode;
}else{
(*nowNode).right = newNode;
}
/*更新平衡因子*/
nowNode = NULL;
tempNode = (*newNode).p;
while ( tempNode != NULL ){
if ( (*newNode).value < (*tempNode).value ){
(*tempNode).bf ++;
}else{
(*tempNode).bf --;
}
if ( (*tempNode).bf == 0 ){ //必定是在节点tempNode的高度少一的子树上插入节点,所以tempNode的总高度不变
break;
}else if( (*tempNode).bf == 2 || (*tempNode).bf == -2 ){
nowNode = tempNode; //记录当前最小不平衡子树的根节点
break;
}
tempNode = (*tempNode).p;
}
if ( nowNode == NULL ){ //平衡,不需要修复
return;
}
/*接下来开始修复被破坏的平衡性*/
fixupBlance( root, nowNode );
};
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来就是AVL树的删除了
自然,首先是二分查找树的删除,不做赘述
函数头
void deletetAVL( Node ** root, Node ** deleteNode )
void deletetAVL( Node ** root, Node ** deleteNode ){
Node * p = NULL;
p = (**deleteNode).p;
if ( (**deleteNode).left == NULL && (**deleteNode).right == NULL ){
if ( p == NULL ){ //该树只有一个节点
delete *deleteNode;
*deleteNode = NULL;
*root = NULL;
return;
}
if ( (**deleteNode).value < (*p).value ){
(*p).left = NULL;
}else{
(*p).right = NULL;
}
}else if( (**deleteNode).left != NULL && (**deleteNode).right == NULL ){
if ( (*p).left == *deleteNode ){ //是父节点的左孩子节点
(*p).left = (**deleteNode).left;
(*(**deleteNode).left).p = p;
}else{
(*p).right = (**deleteNode).left;
(*(**deleteNode).left).p = p;
}
}else if( (**deleteNode).left == NULL && (**deleteNode).right != NULL ){
if ( (*p).left == *deleteNode ){ //是父节点的左孩子节点
(*p).left = (**deleteNode).right;
(*(**deleteNode).right).p = p;
}else{
(*p).right = (**deleteNode).right;
(*(**deleteNode).right).p = p;
}
}else{
Node * tempNode = findSmallestNode( (**deleteNode).right );
swapNodeVal( *deleteNode, tempNode );
*deleteNode = tempNode;
deletetAVL( root, deleteNode );
return;
}
fixupDeleteAVL( root, p, (**deleteNode).value ); //修复平衡
delete *deleteNode;
*deleteNode = NULL;
}
注意到里面有个
fixupDeleteAVL( root, p, (**deleteNode).value );
的修复平衡的操作
那么它就是删除的重点
首先,从删除点开始回溯,逐步修改平衡因子
if ( val < (*nowNode).value ){
(*nowNode).bf --;
}else{
(*nowNode).bf ++;
}
接下来的情况有三种:
1、当前节点平衡因子变为-1或1
这时,说明删除节点以前,当前节点平衡因子是0,那么删除一个节点不会影响该树的高度,那么以上节点仍然平衡,而当前节点也平衡(平衡因子不为2或-2),所以不用调节,直接结束
if ( (*nowNode).bf == 1 || (*nowNode).bf == -1 ){
return;
}
2、当前节点平衡因子变为0
当前节点平衡,但以上节点不能保证,所以继续回溯
3、当前节点平衡因子变为2或-2
调用
fixupBlance( root, nowNode );
原理在插入的时候已说明,但是这里稍稍有点区别,
这里可以稍作分析,已当前节点为2为例,2必是从1变来,那么也就是删除了本来高度要低的子树中的节点,导致高度差由1变为2,同时,也说明以当前节点为根的子树的高度删除前后没有变化,都是以子树高度值大的为自己的高度值。也就是说不用向上回溯了。
bool isOver = false;
if ( (*nowNode).bf == 2 && (*(*nowNode).left).bf == 0 || (*nowNode).bf == -2 && (*(*nowNode).right).bf == 0 ){
isOver = true;
}
fixupBlance( root, nowNode );
if ( isOver ){
return;
}
那么修复代码为
/*节点删除修复函数*/ void fixupDeleteAVL( Node ** root, Node * nowNode, int val ){ while ( nowNode != NULL ){ if ( val < (*nowNode).value ){ (*nowNode).bf --; }else{ (*nowNode).bf ++; } if ( (*nowNode).bf == 1 || (*nowNode).bf == -1 ){ return; } bool isOver = false; if ( (*nowNode).bf == 2 && (*(*nowNode).left).bf == 0 || (*nowNode).bf == -2 && (*(*nowNode).right).bf == 0 ){ isOver = true; } fixupBlance( root, nowNode ); if ( isOver ){ return; } nowNode = (*nowNode).p; } }