3. 二叉平衡树
一、平衡二叉树是带有平衡条件的二叉查找树
平衡条件:平衡二叉树的每个结点的左子树和右子树的高度最多差1。
平衡因子 bf :左子树的高度减去右子树的高度,显然 bf 的取值范围是 [ -1, 1 ] 。每一个结点(在其结点结构中)保留平衡因子 bf 。
/* 平衡二叉树的结点结构 */
struct BinaryTreeNode {
int bf; // 平衡因子,当前结点的左右子树的高度差
int value;
BinaryTreeNode *lChild;
BinaryTreeNode *rChild;
};
补:虽然平衡二叉树能确保树的高度为O(logn),但同时我们对其的插入/删除操作都需保持它的平衡条件。
由于平衡二叉树的平衡条件比较严格,故其维护的代价也较大,一般只适用于查找次数多、插入和删除次数少的情况。
从实际出发,平衡条件较为宽松的红黑树是更好的选择,因为对其进行插入或删除操作付出的代价相对较小。
当然,如果在应用场景中插入和删除操作并不频繁,只是对查找要求较高,那么平衡二叉树还是较优于红黑树。
二、旋转操作——保持平衡二叉树的平衡条件
1. 失衡的四种情况(把当前因失衡而必须重新平衡的结点叫做α)
- 对α的左儿子的左子树进行一次插入(左-左情况,以α为轴右旋)
- 对α的左儿子的右子树进行一次插入(左-右情况,先左旋,再右旋)
- 对α的右儿子的左子树进行一次插入(右-左情况,先右旋,再左旋)
- 对α的右儿子的右子树进行一次插入(右-右情况,以α为轴左旋)


绿色的结点为新加入的结点。虚线连接的三个结点是涉及旋转的结点。虚线的方向代表相应的失衡情况。
2. 示例
【左-右】(涉及的双旋结点为8、9、10)
(1)以8为轴进行左旋(逆时针旋转)

(2)以10为轴进行右旋(顺时针旋转)

(3)得到平衡二叉树

分析:当有多个结点的 | bf | > 1时,需要重新平衡的结点 α 是离新加入结点最近的。
【右-左】(涉及的双旋结点为7、15、16)
(1)以16为轴进行右旋(顺时针旋转)

(2)以7为轴进行左旋(逆时针旋转)

(3)得到平衡二叉树

【右-左】涉及的双旋结点为6、7、15
(1)以15为轴进行右旋(顺时针旋转)

(2)以6为轴进行左旋(逆时针旋转)

(3)得到平衡二叉树

【小结:如何判断是哪种失衡情况】
结点α:离新加入结点最近的且| bf | > 1 的结点 。

以上图为例,结点α为6,然后再观察从结点α到新加入的结点14形成的路径“6-->15-->7-->14”,发现“6-->15-->7”形成的正是右-左这一情况。
3. 代码
// 左旋:借助图来写代码
void LRotate(BinaryTreeNode **pRoot)
{
BinaryTreeNode *pNewRoot = (*pRoot)->rChild; // 新的根结点是当前根结点的右子树
(*pRoot)->rChild = pNewRoot->lChild; // 新的根结点的左子树应挂载到根结点的右子树
pNewRoot->lChild = *pRoot; // 新的根结点的左子树是当前根结点
*pRoot = pNewRoot; // 修改当前根结点的指向
}
// 右旋:借助图来写代码
void RRotate(BinaryTreNode **pRoot)
{
BinaryTreeNode *pNewRoot = (*pRoot)->lChild;
(*pRoot)->lChild = pNewRoot->rChild;
pNewRoot->rChild = *pRoot;
*pRoot = pNewRoot;
}
/* 左平衡:pRoot的左子树高于右子树,需平衡左子树 */
/* 先检查失衡情况是左-左还是左-右
* 左-左情况:(*pRoot)->lChild->bf的值为1(即LH)
* 左-右情况:(*pRoot)->lChild->bf的值为-1(即RH)
*/
void LeftBalance(BinaryTreeNode **pRoot)
{
BinaryTreeNode *lChild, *lChild_rChild; // 左孩子,左孩子的右孩子
lChild = (*pRoot)->lChild;
lChild_rChild = lChild->rChild;
switch(lChild->bf) {
// 左-左情况
case LH:
lChild->bf = (*pRoot)->bf = EH; // 作用和RH情况下的switch语句
RRotate(pRoot); // 以pRoot为轴左旋
break;
// 左-右情况
case RH:
// 该switch语句的作用是更新各个结点的bf值,它们对应调整后的值
switch(lChild_rChild->bf) {
case LH:
(*pRoot)->bf = RH;
lChild->bf = EH;
break;
case RH:
(*pRoot)->bf = EH;
lChild->pf = LH;
break;
case EH:
(*pRoot)->bf = EH;
lChild->pf = EH;
default:
break;
}
lChild_rChild->bf = EH; // 调整后的新的根结点为lChild_rChild
LRotate(&lChild); // 以lChild为轴左旋
RRotate(pRoot); // 以pRoot为轴右旋
break;
}
}
/* 右平衡:pRoot的右子树高于左子树,需平衡右子树 */
/* 先检查失衡情况是右-右还是右-左
* 右-右情况:(*pRoot)->rChild->bf的值为-1(即RH)
* 右-左情况:(*pRoot)->rChild->bf的值为1(即lH)
*/
void RightBalance(BinaryTreeNode **pRoot)
{
BinaryTreeNode *rChild, *rChild_lChild; // 右孩子,右孩子的左孩子
rChild = (*pRoot)->rChild;
rChild_lChild = rChild->lChild;
switch(rChild->bf) {
case RH:
rChild->bf = (*pRoot)->bf = EH;
LRotate(pRoot);
break;
case LH:
switch(rChild_lChild->bf) {
case LH:
(*pRoot)->bf = EH;
rChild->bf = RH;
break;
case RH:
(*pRoot)->bf = LH;
rChild->bf = EH;
break;
case EH:
(*pRoot)->bf = EH;
rChild->bf = EH;
break;
default:
break;
}
rChild_lChild->bf = EH;
RRotate(&rChild);
LRotate(pRoot);
break;
}
}
三、插入操作
/* 往平衡二叉树上插入结点 */
bool insert(BinaryTreeNode **pRoot, int x, bool *isTaller)
{
// 找到插入位置(树中没有值等于x的结点)
if(*pRoot == nullptr) {
*pRoot = new BinaryTreeNode;
(*pRoot)->bf = EH;
(*pRoot)->value = x;
(*pRoot)->lChild = nullptr;
(*pRoot)->rChild = nullptr;
*isTaller = true;
return true;
}
else {
// 树中有值为x的结点,不用插入了
if((*pRoot)->value == x) {
*taller = false;
return false;
}
// 往左子树中插入
if((*pRoot->value) > x) {
// 树中有相同的结点,直接返回
if(!insert(&((*pRoot)->lChild), x, isTaller)) {
*isTaller = false;
return false;
}
// isTaller为真,则意味着树长高了,并且往左子树中插入了结点
if(*isTaller) {
// 检查平衡因子bf,根据相应的情况做出对应的修改和旋转
switch((*pRoot)->bf) {
// LH表示在插入该结点之前,左子树就比右子树高1
// 故插入该结点后,左子树将比右子树高2
case LH:
LeftBalance(pRoot); // 使左子树平衡
*isTaller = false; // 已平衡,令isTaller为假,往上递归就不再进入此语句了
break;
// 在插入该结点之前,右子树比左子树高1
case RH:
(*pRoot)->bf = EH;
*isTaller = false;
break;
// 在插入该结点之前,左子树和右子树一样高
// 故插入该结点后,左子树将比右子树高1
case EH:
(*pRoot)->bf = LH;
*isTaller = true; // 继续往上递归
break;
}
}
}
// 往右子树中插入
else {
if(!insert(&((*pRoot)->rChild), x, isTaller)) {
*isTaller = false;
return false;
}
// isTaller为真,则意味着往右子树中插入了结点
if(*isTaller) {
switch((*pRoot)->bf) {
case LH:
(*pRoot)->bf = EH;
*isTaller = false;
break;
case RH:
RightBalance(pRoot);
*isTaller = false;
break;
case EH:
(*pRoot)->bf = RH;
*isTaller = true;
break;
}
}
}
}
}
四、完整代码
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
#define EH 0 // 等高
#define LH 1 // 左高
#define RH -1 // 右高
/* 二叉平衡树的结点结构 */
struct BinaryTreeNode {
int bf; // 平衡因子,当前结点的左右子树的高度差
int value;
BinaryTreeNode *lChild;
BinaryTreeNode *rChild;
};
// 左旋:借助图来写代码
void LRotate(BinaryTreeNode **pRoot)
{
BinaryTreeNode *pNewRoot = (*pRoot)->rChild; // 新的根结点是当前根结点的右子树
(*pRoot)->rChild = pNewRoot->lChild; // 新的根结点的左子树应挂载到根结点的右子树
pNewRoot->lChild = *pRoot; // 新的根结点的左子树是当前根结点
*pRoot = pNewRoot; // 修改当前根结点的指向
}
// 右旋:借助图来写代码
void RRotate(BinaryTreeNode **pRoot)
{
BinaryTreeNode *pNewRoot = (*pRoot)->lChild;
(*pRoot)->lChild = pNewRoot->rChild;
pNewRoot->rChild = *pRoot;
*pRoot = pNewRoot;
}
/* 左平衡:pRoot的左子树高于右子树,需平衡左子树 */
/* 先检查失衡情况是左-左还是左-右
* 左-左情况:(*pRoot)->lChild->bf的值为1(即LH)
* 左-右情况:(*pRoot)->lChild->bf的值为-1(即RH)
*/
void LeftBalance(BinaryTreeNode **pRoot)
{
BinaryTreeNode *lChild, *lChild_rChild; // 左孩子,左孩子的右孩子
lChild = (*pRoot)->lChild;
lChild_rChild = lChild->rChild;
switch(lChild->bf) {
// 左-左情况
case LH:
lChild->bf = (*pRoot)->bf = EH; // 作用和RH情况下的switch语句
RRotate(pRoot); // 以pRoot为轴左旋
break;
// 左-右情况
case RH:
// 该switch语句的作用是更新各个结点的bf值,它们对应调整后的值
switch(lChild_rChild->bf) {
case LH:
(*pRoot)->bf = RH;
lChild->bf = EH;
break;
case RH:
(*pRoot)->bf = EH;
lChild->bf = LH;
break;
case EH:
(*pRoot)->bf = EH;
lChild->bf = EH;
default:
break;
}
lChild_rChild->bf = EH; // 调整后的新的根结点为lChild_rChild
LRotate(&lChild); // 以lChild为轴左旋
RRotate(pRoot); // 以pRoot为轴右旋
break;
}
}
/* 右平衡:pRoot的右子树高于左子树,需平衡右子树 */
/* 先检查失衡情况是右-右还是右-左
* 右-右情况:(*pRoot)->rChild->bf的值为-1(即RH)
* 右-左情况:(*pRoot)->rChild->bf的值为1(即lH)
*/
void RightBalance(BinaryTreeNode **pRoot)
{
BinaryTreeNode *rChild, *rChild_lChild; // 右孩子,右孩子的左孩子
rChild = (*pRoot)->rChild;
rChild_lChild = rChild->lChild;
switch(rChild->bf) {
case RH:
rChild->bf = (*pRoot)->bf = EH;
LRotate(pRoot);
break;
case LH:
switch(rChild_lChild->bf) {
case LH:
(*pRoot)->bf = EH;
rChild->bf = RH;
break;
case RH:
(*pRoot)->bf = LH;
rChild->bf = EH;
break;
case EH:
(*pRoot)->bf = EH;
rChild->bf = EH;
break;
default:
break;
}
rChild_lChild->bf = EH;
RRotate(&rChild);
LRotate(pRoot);
break;
}
}
/* 往平衡二叉树上插入结点 */
bool insert(BinaryTreeNode **pRoot, int x, bool *isTaller)
{
// 找到插入位置(树中没有值等于x的结点)
if(*pRoot == nullptr) {
*pRoot = new BinaryTreeNode;
(*pRoot)->bf = EH;
(*pRoot)->value = x;
(*pRoot)->lChild = nullptr;
(*pRoot)->rChild = nullptr;
*isTaller = true;
return true;
}
else {
// 树中有值为x的结点,不用插入了
if((*pRoot)->value == x) {
*isTaller = false;
return false;
}
// 往左子树中插入
if((*pRoot)->value > x) {
// 树中有相同的结点,直接返回
if(!insert(&((*pRoot)->lChild), x, isTaller)) {
*isTaller = false;
return false;
}
// isTaller为真,则意味着树长高了,并且往左子树中插入了结点
if(*isTaller) {
// 检查平衡因子bf,根据相应的情况做出对应的修改和旋转
switch((*pRoot)->bf) {
// LH表示在插入该结点之前,左子树就比右子树高1
// 故插入该结点后,左子树将比右子树高2
case LH:
LeftBalance(pRoot); // 使左子树平衡
*isTaller = false; // 已平衡,令isTaller为假,往上递归就不再进入此语句了
break;
// 在插入该结点之前,右子树比左子树高1
case RH:
(*pRoot)->bf = EH;
*isTaller = false;
break;
// 在插入该结点之前,左子树和右子树一样高
// 故插入该结点后,左子树将比右子树高1
case EH:
(*pRoot)->bf = LH;
*isTaller = true; // 继续往上递归
break;
}
}
}
// 往右子树中插入
else {
if(!insert(&((*pRoot)->rChild), x, isTaller)) {
*isTaller = false;
return false;
}
// isTaller为真,则意味着往右子树中插入了结点
if(*isTaller) {
switch((*pRoot)->bf) {
case LH:
(*pRoot)->bf = EH;
*isTaller = false;
break;
case RH:
RightBalance(pRoot);
*isTaller = false;
break;
case EH:
(*pRoot)->bf = RH;
*isTaller = true;
break;
}
}
}
}
}
/* 中序遍历 */
void InOrderTraverse(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr)
return;
InOrderTraverse(pRoot->lChild);
cout << pRoot->value;
InOrderTraverse(pRoot->rChild);
}
/* 求树的高度 */
int GetHeight(BinaryTreeNode *pRoot)
{
if(pRoot == nullptr) return 0;
int leftHeight = GetHeight(pRoot->lChild);
int rightHeight = GetHeight(pRoot->rChild);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
int main()
{
int arr[7] = {4, 3, 2, 7, 6, 8, 5};
BinaryTreeNode *pTree = nullptr;
bool isTaller = false;
for (int i = 0; i < 7; ++i) {
insert(&pTree, arr[i], &isTaller);
}
cout << "提示:平衡二叉树创建完毕!" << endl;
cout << "提示:树的高度为" << GetHeight(pTree) << endl;
cout << "提示:中序遍历平衡二叉树!" << endl;
InOrderTraverse(pTree);
cout << endl;
return 0;
}
测试结果:

上段代码建立的平衡二叉树的最理想高度应为3,但由于平衡二叉树的平衡条件是允许左右子树存在高度差的,故代码运行结果为4。下图为理想情况下应建立的二叉树。

浙公网安备 33010602011771号