参考:
- AVL-平衡二叉树的原理和实现 by 超级小小黑
- 王道《数据结构》
- 查找——平衡二叉树的实现(代码超详细注释) 前半部分的《大话数据结构》讲得还可以
由于我写博客只是存档用,只描述下实现思路,及给出一下例子验证实现的正确性:
实现难点:
- 平衡二叉树只是
一种特殊的二叉排序树
,其左右子树的高度差小于1。这里讲了二叉排序树的基本操作和知识:
【数据结构X.9】二叉排序树(BST)的插入、查找(非递归),递归算法、中序遍历二叉排序树,输出有序树,层序遍历BST树、创建一颗二叉排序树、二叉排序树的删除 - 左旋、右旋、左旋后右旋、右旋后左旋的理解:实际就是通过左右旋转,使得平衡树始终保持平衡。平衡树神奇的一点就在于,即使整棵树乱七八糟的,只要找到
距离插入节点最近的平衡因子为2的祖先节点
,然后围绕它进行旋转即可。部分平衡了,这样的平衡就可以维持到整棵树。因为平衡二叉树的平衡因子 B a l a n c e F a c t o r Balance \ Factor Balance Factor的定义就是 ∣ 左 子 树 高 度 − 右 子 树 高 度 ∣ < 1 |左子树高度-右子树高度|<1 ∣左子树高度−右子树高度∣<1所以只要从插入节点开始向上查找,找到失衡节点,然后将其平衡掉就行。
这样的例子现实生活中也会有:
【社会中突然出现了捣乱分子,维稳人员进行维稳后,只要稳住这一部分人,又使得社会是和谐社会了 = =】
只需要处理捣乱的那一部分(平衡因子为2),就可以保证树是平衡的。这是由平衡二叉树的递归性质决定的
- 那么如何从插入节点开始向上查找,找到失衡节点呢?由于递归性质,我们知道,
从一个根节点,如果能访问到某个叶子节点,那么肯定会经过其一个个祖先节点
。所以,只需要在递归的过程中,一边递归,一边记录当前节点的高度,并且计算平衡因子,直到找到平衡因子为2的节点为止。 - 如何判断是哪一种类型的旋转:出现了平衡因子为2的节点B,可以根据其平衡因子和左右节点的平衡因子的关系来判断是LL、RR、LR、RR类型。显然
平衡因子(Balance Factor)= 左子树高度-右子树高度
,那么——
倘若 B F = − 2 BF=-2 BF=−2那么必然有左子树高度比右子树高度小2,新的结点肯定是插入了右子树了,
①当节点B的左孩子的平衡因子为1,那么肯定是左边的高度比右边的高度大1,肯定是插入在了右子树的左边,也就是RL类型
,
②当节点B的左孩子的平衡因子为-1,那么肯定是右边的高度比左边的高度大1,肯定是插入在了右子树的右边,也就是RR类型
,
以此类推…… - 二叉树的删除过程的理解:【数据结构X.9】二叉排序树(BST)的插入、查找(非递归),递归算法、中序遍历二叉排序树,输出有序树,层序遍历BST树、创建一颗二叉排序树、二叉排序树的删除
简单来说,就是: 1. 需 删 除 的 是 叶 子 或 单 孩 子 节 点 , 直 接 用 其 孩 子 ( 叶 子 节 点 的 孩 子 是 N U L L ) 替 换 掉 要 删 除 的 节 点 1.需删除的是叶子或单孩子节点,直接用其孩子(叶子节点的孩子是NULL)替换掉要删除的节点 1.需删除的是叶子或单孩子节点,直接用其孩子(叶子节点的孩子是NULL)替换掉要删除的节点 2. 找 到 右 子 树 最 小 的 元 素 ( 也 就 是 后 继 节 点 ) , 替 代 待 删 除 节 点 2.找到右子树最小的元素(也就是后继节点),替代待删除节点 2.找到右子树最小的元素(也就是后继节点),替代待删除节点
输入:
例子1:
5
66
12
87
99
43
123
0
例子2:
3
7
8
9
10
15
0
实现函数:
#include <iostream>
#include <math.h>
#include <queue>
typedef struct AVLNode
{
int data; //数据大小
int height; //树的深度
int bf; //平衡因子
AVLNode *lchild, *rchild;
} AVLNode, *AVLTree;
/**
* 层序遍历BST树
*/
void VisitLevelAVLTree(AVLTree tr)
{
std::cout << "\n层次访问树: " << std::endl;
std::queue<AVLNode *> q;
AVLNode *p = tr, *r = tr;
q.push(p);
while (!q.empty())
{
p = q.front();
q.pop();
if (p)
{
std::cout << p->data << " height: " << p->height << " bf:" << p->bf << " ";
if (p->lchild)
q.push(p->lchild);
if (p->rchild)
q.push(p->rchild);
if (p == r)
{
std::cout << std::endl;
r = q.back();
}
}
}
}
void VisitAVLTree(AVLTree tr)
{
if (tr)
{
VisitAVLTree(tr->lchild);
std::cout << tr->data << " height: " << tr->height << " bf:" << tr->bf << " \n";
VisitAVLTree(tr->rchild);
}
}
/***
* 获取节点从叶子到该节点的的高度
*/
int GetHeight(AVLNode *tr)
{
if (tr)
{
return tr->height;
}
return 0;
}
/***
* 计算节点的平衡因子
*/
int CalCulateBF(AVLNode *tr)
{
if (tr)
{
return GetHeight(tr->lchild) - GetHeight(tr->rchild);
}
return 0;
}
/**
* 右旋
*/
void R_Rotate(AVLTree &tr)
{
AVLNode *tmp = tr->lchild;
tr->lchild = tmp->rchild;
tmp->rchild = tr;
//旋转完毕后,更新下高度值
tr->height = 1 + std::max(GetHeight(tr->lchild), GetHeight(tr->rchild));
tr->bf = CalCulateBF(tr);
tmp->height = 1 + std::max(GetHeight(tmp->lchild), GetHeight(tmp->rchild));
tmp->bf = CalCulateBF(tmp);
tr = tmp;
}
/**
* 左旋
*/
void L_Rotate(AVLTree &tr)
{
AVLNode *tmp = tr->rchild;
tr->rchild = tmp->lchild;
tmp->lchild = tr;
//旋转完毕后,更新下高度值
tr->height = 1 + std::max(GetHeight(tr->lchild), GetHeight(tr->rchild));
tr->bf = CalCulateBF(tr);
tmp->height = 1 + std::max(GetHeight(tmp->lchild), GetHeight(tmp->rchild));
tmp->bf = CalCulateBF(tmp);
tr = tmp;
}
/***
* 左旋后右旋
*/
void LR_Rotate(AVLTree &tr)
{
L_Rotate(tr->lchild);
R_Rotate(tr);
}
/***
* 右旋后左旋
*/
void RL_Rotate(AVLTree &tr)
{
R_Rotate(tr->rchild);
L_Rotate(tr);
}
/***
* 往平衡二叉树插入节点
*/
bool InsertAVLTreeNode(AVLTree &tr, int x)
{
if (!tr)
{
tr = (AVLNode *)malloc(sizeof(AVLNode));
tr->rchild = tr->lchild = NULL;
tr->data = x;
tr->height = 1;
tr->bf = 0;
}
else if (x < tr->data)
{
InsertAVLTreeNode(tr->lchild, x);
}
else if (x > tr->data)
{
InsertAVLTreeNode(tr->rchild, x);
}
else
{
return false; //树里有这个节点了,不插入
}
//每次插入完节点后,都自底向上地计算节点的高度和平衡因子。
//可以知道,由于只有插入侧发生了变化,所以只需要依次去更新其路径上的节点的平衡因子和高度即可
//正好,在递归计算的过程中,递归栈中也只会访问插入节点的祖先节点们
tr->height = 1 + std::max(GetHeight(tr->lchild), GetHeight(tr->rchild)); //插入的节点高度自身加1,
int balanceFactor = CalCulateBF(tr);
tr->bf = balanceFactor;
if (balanceFactor >= 2 && CalCulateBF(tr->lchild) >= 0)
{
std::cout << "右旋";
R_Rotate(tr);
}
else if (balanceFactor >= 2 && CalCulateBF(tr->lchild) < 0)
{
std::cout << "LR_Rotate";
LR_Rotate(tr);
}
else if (balanceFactor <= -2 && CalCulateBF(tr->rchild) <= 0)
{
std::cout << "左旋";
L_Rotate(tr);
//VisitLevelAVLTree(tr);
}
else if (balanceFactor <= -2 && CalCulateBF(tr->rchild) > 0)
{
std::cout << "RL_Rotate";
RL_Rotate(tr);
}
else
{
std::cout << "不旋 ";
}
return true;
}
/***
* 获取根节点tr的最小节点
*/
AVLNode *GetMinNode(AVLTree tr)
{
if (tr->lchild == NULL)
return tr;
return GetMinNode(tr->lchild);
}
/**
* 创建一颗二叉树
*/
AVLTree CreateBSTree()
{
int input = 5;
AVLTree tr = NULL;
std::cout << "请输入树的节点值,输入0结束: ";
std::cin >> input;
while (input)
{
InsertAVLTreeNode(tr, input);
std::cout << " "
<< "目前层序遍历BST树序列:\n";
VisitLevelAVLTree(tr);
std::cout << std::endl;
std::cout << "请输入树的节点值,输入0结束: ";
std::cin >> input;
}
return tr;
}
/***
* 获取某值所在的节点指针
*/
AVLNode *GetAVLNode(AVLTree tr, int x)
{
if (tr)
{
if (x == tr->data)
{
return tr;
}
else if (x < tr->data)
{
return GetAVLNode(tr->lchild, x);
}
else if (x > tr->data)
{
return GetAVLNode(tr->rchild, x);
}
}
return NULL;
}
/**
* 从平衡树删除数字x的节点
* 删除成功,返回节点,否则,返回NULL
* 当我们删除一个节点的时候,待删除节点左右子树有一个为空,
* 我们只需要将其不为空的子树的根节点提到待删除元素的位置即可。
* 如果其左右子树都不为空,则将其右子树最小(或者左子树最大)的元素提到待删除节点处。
*/
AVLNode *RemoveAVLKey(AVLTree &tr, int x)
{
if (!tr)
{
return NULL;
}
AVLNode *retNode;
if (x < tr->data) //x小于根,向左找
{
tr->lchild = RemoveAVLKey(tr->lchild, x);
retNode = tr;
}
else if (x > tr->data) //x大于根,向右找
{
tr->rchild = RemoveAVLKey(tr->rchild, x);
retNode = tr;
}
else
{
if (!tr->lchild || !tr->rchild) //是叶子节点和单孩子,直接替换掉要删除的节点
{
std::cout << "删除叶子和单孩子节点 " << x << std::endl;
AVLNode *tmp;
if (!tr->lchild)
{ // 待删除节点左子树为空,直接将右孩子替代当前节点
tmp = tr->rchild;
tr->rchild = NULL;
retNode = tmp;
}
else
{ // 待删除节点右子树为空,直接将左孩子替代当前节点
tmp = tr->lchild;
tr->lchild = NULL;
retNode = tmp;
}
}
else if (tr->lchild && tr->rchild)
{
//找到右子树最小的元素,替代待删除节点
std::cout << "删除双孩子节点 " << x << std::endl;
AVLNode *minNode = GetMinNode(tr->rchild); //找到一个后继节点
if (minNode)
{
std::cout << "找到" << x << "后继节点:" << minNode->data << std::endl;
minNode->rchild = RemoveAVLKey(tr->rchild, minNode->data); //删除tr的右子树里的minNode所在节点
minNode->lchild = tr->lchild;
//minNode->data = tr->data;
tr->lchild = tr->rchild = NULL;
retNode = minNode;
}
}
}
if (retNode == NULL)
return NULL;
retNode->height = 1 + std::max(GetHeight(retNode->lchild), GetHeight(retNode->rchild));
int balanceFactor = CalCulateBF(retNode);
tr->bf = balanceFactor;
if (balanceFactor >= 2 && CalCulateBF(retNode->lchild) >= 0)
{
std::cout << "右旋";
R_Rotate(retNode);
}
else if (balanceFactor >= 2 && CalCulateBF(retNode->lchild) < 0)
{
std::cout << "LR_Rotate";
LR_Rotate(retNode);
}
else if (balanceFactor <= -2 && CalCulateBF(retNode->rchild) <= 0)
{
std::cout << "左旋";
L_Rotate(retNode);
//VisitLevelAVLTree(tr);
}
else if (balanceFactor <= -2 && CalCulateBF(retNode->rchild) > 0)
{
std::cout << "RL_Rotate";
RL_Rotate(retNode);
}
else
{
std::cout << "不旋 ";
}
return retNode;
}
/**
* 从平衡树删除数字x的节点
* 删除成功,返回节点,否则,返回-1
*/
int Remove_Key(AVLTree &tr, int x)
{
AVLNode *node = GetAVLNode(tr, x);
if (node != NULL)
{
tr = RemoveAVLKey(tr, x);
VisitLevelAVLTree(tr);
return node->data;
}
else if (!node)
{
std::cout << "没有找到待删除节点 :" << x << std::endl;
return -1;
}
}
调用代码:
#include "AVLTree.h"
int main()
{
AVLTree tr = CreateBSTree();
VisitAVLTree(tr);
Remove_Key(tr,99);
Remove_Key(tr,15);
Remove_Key(tr,77);
Remove_Key(tr,43);
Remove_Key(tr,87);
Remove_Key(tr,5);
}
其他的一些相关的代码:
- 查找插入路径上,距离插入节点最近的平衡因子绝对值大于1的节点
- 二叉平衡树的高度和平衡因子计算
/*
* 查找插入路径上,距离插入节点最近的平衡因子绝对值大于1的节点
*
* 思路:利用简单的后序遍历的性质,后序遍历过程中,
* 栈内会保存全部的根到目标节点的路径,
* 返回 最先找到的祖先中的平衡因子为2的值
*/
AVLNode *FindNearBalValNode(AVLTree tr, int x)
{
std::stack<AVLNode *> s;
AVLNode *r = tr, *p = tr;
while (p || !s.empty())
{
if (p)
{
s.push(p);
p = p->lchild;
}
else
{
p = s.top();
if (p->rchild && p->rchild != r)
{
//有右孩子,而且没有访问过右孩子
p = p->rchild;
s.push(p);
p = p->lchild;
}
else
{
if (p->data == x) //找到了当前插入的数值,此时,stack栈里保存了根节点到p的全部路径节点
{
std::cout << "root: ";
while (!s.empty())
{
p = s.top();
std::cout << p->data << " depth: " << p->height << " baVal:" << p->bf << " \n";
if (std::abs(p->bf) >= 2) //如果平衡因子大于或等于2,返回这个根节点
return p;
s.pop();
}
return NULL;
}
s.pop();
r = p;
p = NULL;
}
}
}
return NULL;
}
/**
* 二叉平衡树的高度和平衡因子计算
*/
int CalcuBalanceValDepth(AVLTree tr)
{
if (tr)
{
int hl = CalcuBalanceValDepth(tr->lchild) + 1;
int hr = CalcuBalanceValDepth(tr->rchild) + 1;
tr->height = std::max(hl, hr);
tr->bf = hl - hr;
return tr->height;
}
else
{
return 0;
}
}