声明
本文主要参考自以下文章,先表示感谢,标为原创是因为二者的内容中的精华部分,在这篇文章中都有转载,不好标榜转自其中任何一个,因此选择在此声明。再次表示感谢!
文章目录
概述
介绍数据结构中的平衡二叉树。
1. 平衡二叉树概要
1.1 平衡二叉树由来
在二叉搜索树的查找性能分析中,我们有提到,在最最坏的情况下,也就是当先后插入的关键字有序时,构成的二叉排序树蜕变为单支树,树的深度为其平均查找长度 ( n + 1 ) / 2 ^{(n+1)}/_2 (n+1)/2(和顺序查找相同)。换句话说,它退化成了一个链表。在这种情况下,查找一个节点的时间复杂度是 O ( n ) O(n) O(n)。
为了避免这种情况的发生,我们希望可以有一种算法,将我们的不平衡的二叉排序树转化为平衡二叉排序树(AVL树的查找平均复杂度是 O ( l o g ( n ) ) O(log(n)) O(log(n)),这样就可以让我们的二叉排序树结构最优化。
1.2 AVL 名字由来
AVL树的名字来源于它的发明作者G.M. Adelson-Velsky 和 E.M. Landis。
AVL树是最先发明的
自平衡二叉查找树
(Self-Balancing Binary Search Tree,简称平衡二叉树)。
1.3 AVL 定义
它或者是一颗空树,或者具有以下性质的二叉搜索树:
- 它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1
- 它的左子树和右子树都是一颗平衡二叉树。
平衡二叉树,必须是二叉搜索树
1.4 相关概念
1.4.1 平衡因子
节点的平衡因子
(Balance Factor) = 节点的左子树高度
- 节点的右子树高度
- 对于平衡二叉树,BF的取值范围为
[-1,1]
。 - 如果发现任一节点的BF值不在此范围,则需要对树进行调整。
1.4.2 最小不平衡子树
定义
:距离插入节点最近的,且平衡因子的绝对值大于1的节点为根的子树.。
示例:
> 在图三中,左边二叉树的节点45的BF = 1,插入节点43后,节点45的BF = 2。节点45是距离插入点43最近的BF不在[-1,1]范围内的节点,因此以节点45为根的子树为最小不平衡子树。
2. AVL树的平衡调整
转载自平衡二叉树,侵删。
2.1 AVL树的平衡调整示例
如何时构成的二叉排序树编程平衡二叉树呢?先看一个具体的例子
图a
:一颗空树也算是平衡二叉树图b
:只有一个结点13的树也算是平衡二叉树图c
:在图b的基础上插入新的结点24之后,仍然是平衡二叉树,只是根结点的平衡因子从0变到了-1(左子树的深度为0减去右子树的深度1等于-1)图d -> 图e
:在图c的基础上再插入一个结点37,这个时候整棵树出现了不平衡现象,根结点13的平衡因子从-1变成了-2。我们想要让这课树平衡,而且要保证该树二叉排序树的性质,那么我们只要将根结点13换为24,结点13作为结点24的左子树,这棵树就又会回到平衡状态,即图e。我们把这种对树做向左逆时针“旋转”的操作称为单向左旋平衡处理。左旋之后,我们发现13、24、37结点的平衡因子都变为0。而且仍然保持着二叉排序树的特性图f -> 图g -> 图h
: 当我们继续插入结点90之后,二叉树仍然平衡,只是24、37两个结点的平衡因子变为了-1,再次插入53结点之后,结点37的平衡因子BF由-1变为-2,这意味着该排序树中出现了新的不平衡现象,需要进行调整。但此时由于结点53插在结点90的左子树上,因此不能如上面一样作简单的调整。对于以上结点37为根的子树来说,既要保持二叉排序树的特性,又要平衡,则必须以53结点作为根结点,而使37结点成为它左子树的根,90结点称为它右子树的根。这就好比做了两次旋转,首先我们让37、53、90这棵树单先向右顺时针转变成图g,再像左逆时针变成图h,这样我们的二叉树就能够再次回到平衡状态。对于以上旋转操作我们称为双向旋转(先右后左)平衡处理。
2.2 平衡算法总结
转载自平衡二叉树
看完了上面的例子,我们总结一下二叉排序树的不平衡情况以及如何将其转化为平衡情况。
一般情况下,假设由于在二叉排序树上插入结点而失去平衡的最小子树
根结点的指针为A
(即a是离插入结点最近,且平衡因子绝对值不超过1的祖先结点),则失去平衡后进行调整的规律可以归纳为一下4种情况:
- 单向右旋平衡处理(LL):由于在A的左孩子(L)的左子树(L)上插入新结点,A的平衡因子由1增至2,致使以A为根结点的子树失去平衡,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B顺时针旋转一样。简称LL型旋转
LL型调整的一般形式如下图所示,表示在A的左孩子B的左子树BL(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将A的左孩子B提升为新的根结点;②将原来的根结点A降为B的右孩子;③各子树按大小关系连接(BL和AR不变,BR调整为A的左子树)。
- 单向左旋平衡处理(RR):由于在A的右孩子®的右子树®上插入新结点,A的平衡因子由-1变为-2,致使以A为根结点的子树失去平衡,按照大小关系,结点B应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡,A结点就好像是绕结点B逆时针旋转一样。简称RR型旋转.
RR型调整的一般形式如下图4所示,表示在A的右孩子B的右子树BR(不一定为空)中插入结点(图中阴影部分所示)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将A的右孩子B提升为新的根结点; ②将原来的根结点A降为B的左孩子;③各子树按大小关系连接(AL和BR不变,BL调整为A的右子树)。
3. 双向旋转平衡处理(LR):由于在A的左孩子(L)的右子树®上插入新结点,A的平衡因子由1增加到2,致使A为根结点的子树失去平衡,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。即需要进行两次旋转(先左旋后右旋)操作。简称LR型旋转
LR型调整的一般形式如下图6所示,表示在A的左孩子B的右子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将B的左孩子C提升为新的根结点;②将原来的根结点A降为C的右孩子;③各子树按大小关系连接(BL和AR不变,CL和CR分别调整为B的右子树和A的左子树)。
- 双向旋转平衡处理(RL):由于在A的右孩子®的左子树(L)上插入新结点,A的平衡因子由1增加到2,致使A为根结点的子树失去平衡,按照大小关系,结点C应作为新的根结点,其余两个节点分别作为左右孩子节点才能平衡。即需要进行两次旋转(先右旋后左旋)操作。下图是RL型的最简单形式。简称RL型旋转 (上图F)
RL型调整的一般形式如下图8所示,表示在A的右孩子B的左子树(根结点为C,不一定为空)中插入结点(图中两个阴影部分之一)而导致不平衡( h 表示子树的深度)。这种情况调整如下:①将B的左孩子C提升为新的根结点;②将原来的根结点A降为C的左孩子;③各子树按大小关系连接(AL和BR不变,CL和CR分别调整为A的右子树和B的左子树)。
2.3 平衡调整总结
2.3.1 实例
2.4 两种示例
2.4.0 实例
选取一组数据分别为2,1,0,3,4,5,6,9,8,7的10个结点来构造平衡二叉树。
- 首先数据为2的结点作为根结点插入,接着插入1,仍是平衡的,再插入0是,2的平衡因子变为2,此时出现了不平衡,因此需要进行调整,最低不平衡结点为2,属于LL型,调整过程如图1所示。
- 接着插入3,是平衡的,再插入4,此时出现了不平衡,结点 1 和 2 的平衡因子都为 -2,结点2为最低不平衡结点,属于RR型,调整过程如图2所示
- 接着插入5,此时结点 1 的平衡因子为 -2,导致不平衡,结点1为最低不平衡结点,属于RR型,调整如图3所示。
- 接着插入6,此时结点4的平衡因子为 -2,导致不平衡,结点4为最低不平衡结点,属于RR型,调整如图4所示。
- 接着插入9,是平衡的,再插入8,此时结点 3、5、6 的平衡因子都为 -2,导致不平衡,结点6为最低不平衡结点,属于RL型,调整如图5所示。
- 插入7,此时结点3、5的平衡因子为 -2,导致不平衡,最低不平衡结点为5,属于RL型,调整如图6所示。
2.4.1 结点中使用平衡因子
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
/* 二叉树的二叉链表结点结构定义 */
typedef struct BiTNode /* 结点结构 */
{
int data; /* 结点数据 */
int bf; /* 结点的平衡因子 */
struct BiTNode *lchild, *rchild; /* 左右孩子指针 */
} BiTNode, *BiTree;
/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree *P)
{
BiTree L;
L = (*P)->lchild; /* L指向P的左子树根结点 */
(*P)->lchild = L->rchild; /* L的右子树挂接为P的左子树 */
L->rchild = (*P);
*P = L; /* P指向新的根结点 */
}
/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0 */
void L_Rotate(BiTree *P)
{
BiTree R;
R = (*P)->rchild; /* R指向P的右子树根结点 */
(*P)->rchild = R->lchild; /* R的左子树挂接为P的右子树 */
R->lchild = (*P);
*P = R; /* P指向新的根结点 */
}
#define LH + 1 /* 左高 */
#define EH 0 /* 等高 */
#define RH - 1 /* 右高 */
/* 对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/* 本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild; /* L指向T的左子树根结点 */
switch (L->bf)
{ /* 检查T的左子树的平衡度,并作相应平衡处理 */
case LH: /* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */
(*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH: /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */
Lr = L->rchild; /* Lr指向T的左孩子的右子树根 */
switch (Lr->bf)
{ /* 修改T及其左孩子的平衡因子 */
case LH: (*T)->bf = RH;
L->bf = EH;
break;
case EH: (*T)->bf = L->bf = EH;
break;
case RH: (*T)->bf = EH;
L->bf = LH;
break;
}
Lr->bf = EH;
L_Rotate(&(*T)->lchild); /* 对T的左子树作左旋平衡处理 */
R_Rotate(T); /* 对T作右旋平衡处理 */
}
}
/* 对以指针T所指结点为根的二叉树作右平衡旋转处理, */
/* 本算法结束时,指针T指向新的根结点 */
void RightBalance(BiTree *T)
{
BiTree R, Rl;
R = (*T)->rchild; /* R指向T的右子树根结点 */
switch (R->bf)
{ /* 检查T的右子树的平衡度,并作相应平衡处理 */
case RH: /* 新结点插入在T的右孩子的右子树上,要作单左旋处理 */
(*T)->bf = R->bf = EH;
L_Rotate(T);
break;
case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */
Rl = R->lchild; /* Rl指向T的右孩子的左子树根 */
switch (Rl->bf)
{ /* 修改T及其右孩子的平衡因子 */
case RH: (*T)->bf = LH;
R->bf = EH;
break;
case EH: (*T)->bf = R->bf = EH;
break;
case LH: (*T)->bf = EH;
R->bf = RH;
break;
}
Rl->bf = EH;
R_Rotate(&(*T)->rchild); /* 对T的右子树作右旋平衡处理 */
L_Rotate(T); /* 对T作左旋平衡处理 */
}
}
/* 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */
/* 数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */
/* 失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree *T, int e, Status *taller)
{
if (!*T)
{ /* 插入新结点,树“长高”,置taller为TRUE */
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e; (*T)->lchild = (*T)->rchild = NULL; (*T)->bf = EH;
*taller = TRUE;
}
else
{
if (e == (*T)->data)
{ /* 树中已存在和e有相同关键字的结点则不再插入 */
*taller = FALSE; return FALSE;
}
if (e < (*T)->data)
{ /* 应继续在T的左子树中进行搜索 */
if (!InsertAVL(&(*T)->lchild, e, taller)) /* 未插入 */
return FALSE;
if (*taller) /* 已插入到T的左子树中且左子树“长高” */
switch ((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,需要作左平衡处理 */
LeftBalance(T); *taller = FALSE; break;
case EH: /* 原本左、右子树等高,现因左子树增高而使树增高 */
(*T)->bf = LH; *taller = TRUE; break;
case RH: /* 原本右子树比左子树高,现左、右子树等高 */
(*T)->bf = EH; *taller = FALSE; break;
}
}
else
{ /* 应继续在T的右子树中进行搜索 */
if (!InsertAVL(&(*T)->rchild, e, taller)) /* 未插入 */
return FALSE;
if (*taller) /* 已插入到T的右子树且右子树“长高” */
switch ((*T)->bf) /* 检查T的平衡度 */
{
case LH: /* 原本左子树比右子树高,现左、右子树等高 */
(*T)->bf = EH; *taller = FALSE; break;
case EH: /* 原本左、右子树等高,现因右子树增高而使树增高 */
(*T)->bf = RH; *taller = TRUE; break;
case RH: /* 原本右子树比左子树高,需要作右平衡处理 */
RightBalance(T); *taller = FALSE; break;
}
}
}
return TRUE;
}
int main(void)
{
int i;
int a[10] = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 };
BiTree T = NULL;
Status taller;
for (i = 0; i < 10; i++)
{
InsertAVL(&T, a[i], &taller);
}
printf("本样例建议断点跟踪查看平衡二叉树结构");
return 0;
}
2.4.2 节点中使用高度(较少)
#include<stdio.h>
#include<stdlib.h>
typedef struct Node
{
int key;
struct Node *left;
struct Node *right;
int height;
}BTNode;
int max(int a, int b);
int height(struct Node *N)
{
if (N == NULL)
return 0;
return N->height;
}
int max(int a, int b)
{
return (a > b) ? a : b;
}
BTNode* newNode(int key)
{
struct Node* node = (BTNode*)malloc(sizeof(struct Node));
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 1;
return(node);
}
BTNode* ll_rotate(BTNode* y)
{
BTNode *x = y->left;
y->left = x->right;
x->right = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
BTNode* rr_rotate(BTNode* y)
{
BTNode *x = y->right;
y->right = x->left;
x->left = y;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
int getBalance(BTNode* N)
{
if (N == NULL)
return 0;
return height(N->left) - height(N->right);
}
BTNode* insert(BTNode* node, int key)
{
if (node == NULL)
return newNode(key);
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
else
return node;
node->height = 1 + max(height(node->left), height(node->right));
int balance = getBalance(node);
if (balance > 1 && key < node->left->key) //LL型
return ll_rotate(node);
if (balance < -1 && key > node->right->key) //RR型
return rr_rotate(node);
if (balance > 1 && key > node->left->key) //LR型
{
node->left = rr_rotate(node->left);
return ll_rotate(node);
}
if (balance < -1 && key < node->right->key) //RL型
{
node->right = ll_rotate(node->right);
return rr_rotate(node);
}
return node;
}
BTNode * minValueNode(BTNode* node)
{
BTNode* current = node;
while (current->left != NULL)
current = current->left;
return current;
}
BTNode* deleteNode(BTNode* root, int key)
{
if (root == NULL)
return root;
if (key < root->key)
root->left = deleteNode(root->left, key);
else if (key > root->key)
root->right = deleteNode(root->right, key);
else
{
if ((root->left == NULL) || (root->right == NULL))
{
BTNode* temp = root->left ? root->left : root->right;
if (temp == NULL)
{
temp = root;
root = NULL;
}
else
*root = *temp;
free(temp);
}
else
{
BTNode* temp = minValueNode(root->right);
root->key = temp->key;
root->right = deleteNode(root->right, temp->key);
}
}
if (root == NULL)
return root;
root->height = 1 + max(height(root->left), height(root->right));
int balance = getBalance(root);
if (balance > 1 && getBalance(root->left) >= 0) //LL型
return ll_rotate(root);
if (balance > 1 && getBalance(root->left) < 0) //LR型
{
root->left = rr_rotate(root->left);
return ll_rotate(root);
}
if (balance < -1 && getBalance(root->right) <= 0) //RR型
return rr_rotate(root);
if (balance < -1 && getBalance(root->right) > 0) //Rl型
{
root->right = ll_rotate(root->right);
return rr_rotate(root);
}
return root;
}
void preOrder(struct Node *root)
{
if (root != NULL)
{
printf("%d ", root->key);
preOrder(root->left);
preOrder(root->right);
}
}
int main()
{
BTNode *root = NULL;
root = insert(root, 9);
root = insert(root, 5);
root = insert(root, 10);
root = insert(root, 0);
root = insert(root, 6);
root = insert(root, 11);
root = insert(root, -1);
root = insert(root, 1);
root = insert(root, 2);
printf("前序遍历:\n");
preOrder(root);
/* The constructed AVL Tree would be
9
/ \
1 10
/ \ \
0 5 11
/ / \
-1 2 6
*/
root = deleteNode(root, 10);
/* The AVL Tree after deletion of 10
1
/ \
0 9
/ / \
-1 5 11
/ \
2 6
*/
printf("\n");
printf("前序遍历:\n");
preOrder(root);
return 0;
}