平衡二叉树的创建、插入、删除
一、平衡二叉树(AVL树)
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
二、构建一颗平衡二叉树
下面将逐一讲解构建平衡二叉树所需要的基本操作。
- 求二叉树的深度
这是一个通过递归实现的简单函数,直接放代码。
//求二叉树的深度
int TreeDepth(BiNode* T)
{
if (T == NULL)
return 0;
else
{
int l = TreeDepth(T->lchild);
int r = TreeDepth(T->rchild);
return l > r ? l + 1 : r + 1;
}
}
- 二叉排序树插入操作
二叉排序树的插入操作将是我们构建平衡二叉树的基本操作,也很简单,直接放代码,不懂的话,可以去翻一下数据结构教材的查找这一章。(代码中的注释都是自己写的,而且修改代码的过程中可能并没有同步修改注释,所以读者觉得有的注释看起来不是很对,这是正常的)
//二叉排序树插入算法,插入的位置一定是叶子节点
int BST_Insert(BiNode*& T, int key)
{
//如果原树为空,记新插入的节点为根节点,或者是找到了叶子节点
if (T == NULL)
{
T = new BiNode;
T->data = key;
return 1; //返回1,表示插入成功
}
else if (T->data == key)
{
return 0; //在二叉树中,已经有关键字值为key的元素,插入失败
}
else if (T->data > key)
{
return BST_Insert(T->lchild, key);
}
else
{
return BST_Insert(T->rchild, key);
}
}
- 寻找最小失衡子树
这里就需要着重强调一下,我们构建二叉树时,是从一个根节点开始,利用上面介绍的插入操作,将数据一个个的插入二叉树的过程。
而每次向二叉树中插入一个数据,我们就会对二叉树是否失衡做一次检查,如果失衡就立刻纠正(纠正的部分下面会讲),检查和纠正的操作一直存在于整个二叉树的构建过程中。
这也就意味着,每次插入一个节点,最多只可能差生一棵不平衡的子树。而且显而易见的是,不平衡的子树一定在整颗二叉树最深的一个分支上(这个如果想不明白,建议再看一下教材)。根据这两个条件,我们就可以设计算法,找到这个最小的不平衡的子树:
- 从根节root点开始,计算根节点的左右子树的高度,lh = 左子树的高度、rh = 右子树的高度,得出:d = lh - rh。 如果d >= 2,说明最小的不平衡的子树在左子树上,如果d <= -2,则反过来。
- 如果是第一种情况,我们将对左子树的根节点做和root结点一样的判断,反之亦然,显然这也是一个递归函数。
- 重复1~2,最终,我们会记录最后一个|d| > 2的结点的位置信息,实现的方式是我们在递归地过程,以引用的方式传入一个指针MinTree,让MinTree始终指向|d| > 2的结点。
- 当递归找到最后一个|d| > 2 的结点的时候,还会再进行最后一次递归操作,不过在这个时候,由于|d| < 2,所以MinTree的指向不会再被修改,依旧指向最小的不平衡子树的根节点。但是我们可以在最后一次递归时对不平衡的子树的具体类型做出判断:首先,我们在递归地过程中会维护一个flag,记录以MinTree为根节点的不平衡子树中更深的是左子树还是右子树,假设这个更深的子树是minMinTree,之后在最后一次递归中,动过d是等于1还是-1来判断minMinTree的左子树和右子树中更深的是哪一个,它就是当前结点插入的位置,由此我们就可以知道这颗不平衡的子树是LL、LR、RR、RL中的哪一种了。
下面是代码实现:
//寻找最小失衡子树
void FindMinUnbalanceTree(BiNode* root, BiNode*& MinRoot, int& flag)
{
//找到root的左子树和右子树的高度
int l = TreeDepth(root->lchild);
int r = TreeDepth(root->rchild);
//最小子树在root的左子树中
if (l - r >= 2)
{
MinRoot = root;
flag = -1; //标志这是左子树过高导致不平衡
FindMinUnbalanceTree(root->lchild, MinRoot, flag);
}
//最小子树在root的右子树中
else if (l - r <= -2)
{
MinRoot = root;
flag = -2; //标志这是右子树过高导致不平衡
FindMinUnbalanceTree(root->rchild, MinRoot, flag);
}
//具体判断是LL、LR、RR、RL中的哪一种
else
{
//是LL
if (flag == -1 && l - r == 1)
{
flag = 1;
}
//是LR
else if(flag == -1 && l - r == -1)
{
flag = 2;
}
//是RR
else if (flag == -2 && l - r == -1)
{
flag = 3;
}
//是RL
else if(flag == -2 && l - r == 1)
{
flag = 4;
}
}
}
- 纠正失衡的最小子树
这个就很容易了,数据结构教材中有专门的介绍,但是我在实现上做了略微的改动,因为我的二叉树节点中只有指向两个孩子结点的指针,无法通过MinTree结点找到其父节点,因此我是在逻辑上实现对应的操作。
代码如下所示:
//构建平衡二叉树,在构建二叉排序树的过程中保持树的平衡
void CreateAVL(BiNode*& A, int flag)
{
//A是最小不平衡子树的根节点
BiNode* B; //A的孩子节点
BiNode* C; //B的孩子节点
//LL型
if (flag == 1)
{
B = A->lchild;
//结点B的右上旋
int Adata = A->data;
A->data = B->data;
B->data = Adata;
A->lchild = B->lchild;
B->lchild = B->rchild;
B->rchild = A->rchild;
A->rchild = B;
}
//LR型
else if (flag == 2)
{
B = A->lchild;
C = B->rchild;
//结点C的左上旋
int Bdata = B->data;
B->data = C->data;
C->data = Bdata;
B->rchild = C->rchild;
C->rchild = C->lchild;
C->lchild = B->lchild;
B->lchild = C;
//结点B的右上旋
int Adata = A->data;
A->data = C->data;
C->data = Adata;
A->lchild = C->lchild;
C->lchild = C->rchild;
C->rchild = A->rchild;
A->rchild = C;
}
//RR型
else if (flag == 3)
{
B = A->rchild;
//结点B的左上旋
int Adata = A->data;
A->data = B->data;
B->data = Adata;
A->rchild = B->rchild;
B->rchild = B->lchild;
B->lchild = A->lchild;
A->lchild = B;
}
//RL型
else if (flag == 4)
{
B = A->rchild;
C = B->lchild;
//结点C的右上旋
int Bdata = B->data;
B->data = C->data;
C->data = Bdata;
B->lchild = C->lchild;
C->lchild = C->rchild;
C->rchild = B->rchild;
B->rchild = C;
//结点C的左上选
int Adata = A->data;
A->data = C->data;
C->data = Adata;
A->rchild = C->rchild;
C->rchild = C->lchild;
C->lchild = A->lchild;
A->lchild = C;
}
}
- 平衡二叉树的构造
结合上面的操作,我们就可以构造出一颗平衡二叉树。
//二叉排序树的构造
void Create_BST(BiNode*& T, int arrayKey[], int n)
{
T = NULL; //初始时,T为空树
int i = 0;
BiNode* MinRoot;
int flag;
while (i < n)
{
BST_Insert(T, arrayKey[i]);
//构造平衡二叉树
MinRoot = NULL; //初始化为空
flag = -8; //初始化为负值
FindMinUnbalanceTree(T, MinRoot, flag); //寻找最小的不平衡子树
CreateAVL(MinRoot, flag); //调整该子树
i++;
}
}
- 全部代码
这里还添加了删除操作,在删除节点时,也有可能造成二叉树的不平衡,所以每删除一个节点,我们都要对二叉树做出调整,与插入操作不同的是,删除操作引起的不平衡可能会向上传导,很好理解,我们在对最小的不平衡的二叉树做出调整的时,会导致这个二叉树的高度减1,从而可能会导致其祖先节点的平衡因子的绝对值大于1,所以我们这个这时候就不能只是进行1次纠正操作,具体见代码实现。而寻找最小不平衡子树及其类型的操作是不变的,都是找到MinTree之后,找到其最高的儿子,以及最高的孙子,通过孙子所处的位置,来判断是四种类型中的哪一个。
#include<iostream>
using namespace std;
//定义二叉树的节点
struct BiNode {
int data = -1; //数据域
BiNode* lchild = NULL, * rchild = NULL; //左右孩子节点
};
//求二叉树的深度
int TreeDepth(BiNode* T)
{
if (T == NULL)
return 0;
else
{
int l = TreeDepth(T->lchild);
int r = TreeDepth(T->rchild);
return l > r ? l + 1 : r + 1;
}
}
//寻找最小失衡子树
void FindMinUnbalanceTree(BiNode* root, BiNode*& MinRoot, int& flag)
{
//找到root的左子树和右子树的高度
int l = TreeDepth(root->lchild);
int r = TreeDepth(root->rchild);
//最小子树在root的左子树中
if (l - r >= 2)
{
MinRoot = root;
flag = -1; //标志这是左子树过高导致不平衡
FindMinUnbalanceTree(root->lchild, MinRoot, flag);
}
//最小子树在root的右子树中
else if (l - r <= -2)
{
MinRoot = root;
flag = -2; //标志这是右子树过高导致不平衡
FindMinUnbalanceTree(root->rchild, MinRoot, flag);
}
//具体判断是LL、LR、RR、RL中的哪一种
else
{
//是LL
if (flag == -1 && l - r == 1)
{
flag = 1;
}
//是LR
else if(flag == -1 && l - r == -1)
{
flag = 2;
}
//是RR
else if (flag == -2 && l - r == -1)
{
flag = 3;
}
//是RL
else if(flag == -2 && l - r == 1)
{
flag = 4;
}
}
}
//构建平衡二叉树,在构建二叉排序树的过程中保持树的平衡
void CreateAVL(BiNode*& A, int flag)
{
//A是最小不平衡子树的根节点
BiNode* B; //A的孩子节点
BiNode* C; //B的孩子节点
//LL型
if (flag == 1)
{
B = A->lchild;
//结点B的右上旋
int Adata = A->data;
A->data = B->data;
B->data = Adata;
A->lchild = B->lchild;
B->lchild = B->rchild;
B->rchild = A->rchild;
A->rchild = B;
}
//LR型
else if (flag == 2)
{
B = A->lchild;
C = B->rchild;
//结点C的左上旋
int Bdata = B->data;
B->data = C->data;
C->data = Bdata;
B->rchild = C->rchild;
C->rchild = C->lchild;
C->lchild = B->lchild;
B->lchild = C;
//结点B的右上旋
int Adata = A->data;
A->data = C->data;
C->data = Adata;
A->lchild = C->lchild;
C->lchild = C->rchild;
C->rchild = A->rchild;
A->rchild = C;
}
//RR型
else if (flag == 3)
{
B = A->rchild;
//结点B的左上旋
int Adata = A->data;
A->data = B->data;
B->data = Adata;
A->rchild = B->rchild;
B->rchild = B->lchild;
B->lchild = A->lchild;
A->lchild = B;
}
//RL型
else if (flag == 4)
{
B = A->rchild;
C = B->lchild;
//结点C的右上旋
int Bdata = B->data;
B->data = C->data;
C->data = Bdata;
B->lchild = C->lchild;
C->lchild = C->rchild;
C->rchild = B->rchild;
B->rchild = C;
//结点C的左上选
int Adata = A->data;
A->data = C->data;
C->data = Adata;
A->rchild = C->rchild;
C->rchild = C->lchild;
C->lchild = A->lchild;
A->lchild = C;
}
}
//二叉排序树插入算法,插入的位置一定是叶子节点
int BST_Insert(BiNode*& T, int key)
{
//如果原树为空,记新插入的节点为根节点,或者是找到了叶子节点
if (T == NULL)
{
T = new BiNode;
T->data = key;
return 1; //返回1,表示插入成功
}
else if (T->data == key)
{
return 0; //在二叉树中,已经有关键字值为key的元素,插入失败
}
else if (T->data > key)
{
return BST_Insert(T->lchild, key);
}
else
{
return BST_Insert(T->rchild, key);
}
}
//二叉排序树的构造
void Create_BST(BiNode*& T, int arrayKey[], int n)
{
T = NULL; //初始时,T为空树
int i = 0;
BiNode* MinRoot;
int flag;
while (i < n)
{
BST_Insert(T, arrayKey[i]);
//构造平衡二叉树
MinRoot = NULL; //初始化为空
flag = -8; //初始化为负值
FindMinUnbalanceTree(T, MinRoot, flag); //寻找最小的不平衡子树
CreateAVL(MinRoot, flag); //调整该子树
i++;
}
}
//二叉排序树的查找
BiNode* BST_Search(BiNode* T, int key, BiNode*& pre, int& flag)
{
pre = NULL;
flag = -1; //初始化为-1
while (T != NULL && key != T->data)
{
if (T->data < key)
{
if (T->rchild != NULL)
{
pre = T;
flag = 0; //pre是T的右孩子
}
T = T->rchild;
}
else
{
if (T->lchild != NULL)
{
pre = T->lchild;
flag = 1; //pre是T的左孩子
}
T = T->lchild;
}
}
return T;
}
//找到右子树最左边的孩子节点
int BST_deleteHelp(BiNode*& T)
{
int p;
BiNode* TPre = NULL;
while (T->lchild != NULL)
{
TPre = T;
T = T->lchild;
}
//是叶子节点,直接删除
if (T->lchild == NULL && T->rchild == NULL)
{
p = T->data;
delete T;
TPre->lchild = NULL;
return p;
}
//有右子树
else if (T->lchild == NULL && T->rchild != NULL)
{
p = T->data;
BiNode* temp = T->rchild;
//从逻辑上删除
T->data = temp->data;
T->lchild = temp->lchild;
T->rchild = temp->rchild;
delete temp;
return p;
}
}
//二叉排序树的删除,在删除的过程中,我们同样需要维持平衡
bool BST_delete(BiNode*& T, int key)
{
//先找到这个节点
BiNode* T1Pre; //T1的父节点
int flag;
BiNode* T1 = BST_Search(T, key, T1Pre, flag);
//二叉树T中不存在这个结点
if (T1 == NULL)
return false;
//是叶子节点,直接删除
if (T1->lchild == NULL && T1->rchild == NULL)
{
delete T1;
if (flag == 0)
T1Pre->rchild = NULL;
else if (flag == 1)
T1Pre->lchild = NULL;
}
//只有右子树
else if (T1->lchild == NULL && T1->rchild != NULL)
{
BiNode* p = T1;
T1 = T1->rchild;
delete p;
}
//只有左子树
else if (T1->lchild != NULL && T1->rchild == NULL)
{
BiNode* p = T1;
T1 = T1->lchild;
delete p;
}
//既有左子树,又有右子树
else
{
int p = BST_deleteHelp(T->rchild);
T1->data = p; //只需要对这个值做一个交换就可以从逻辑上删除之前的点
}
//维持平衡
BiNode* MinRoot;
int flag1 = 0;
//flag的值没有发生变化,说明二叉树已经平衡了
while (flag1 != -8)
{
MinRoot = NULL; //初始化为空
flag1 = -8; //初始化为负值
FindMinUnbalanceTree(T, MinRoot, flag1); //寻找最小的不平衡子树
CreateAVL(MinRoot, flag1); //调整该子树
}
return true;
}
void test()
{
BiNode* T = NULL;
int arrayKey[] = {1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 10};
int n = sizeof(arrayKey) / sizeof(int);
//构造二叉排序树
Create_BST(T, arrayKey, n);
//查找操作
int key = 2;
BiNode* pPre;
int flag;
BiNode* p = BST_Search(T, key, pPre, flag);
if (p == NULL)
{
cout << "查找失败" << endl;
}
else
{
cout << "查找成功:" << p->data << endl;
}
//删除操作
int dkey = 3;
bool b = BST_delete(T, dkey);
p = BST_Search(T, dkey, pPre, flag);
if (p == NULL)
{
cout << "删除成功" << endl;
}
else
{
cout << "删除失败" << endl;
}
}
int main()
{
test();
system("pause");
return 0;
}
OK,看到这里,相信你已经可以构建一棵平衡二叉树了。