1 平衡二叉树简介
AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL树得名于它的发明者G. M. Adelson-Velsky和Evgenii Landis,他们在1962年的论文An algorithm for the organization of information 中公开了这一数据结构。 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
2 为什么需要平衡二叉树
首先平衡二叉树是二叉搜索树,而一般的二叉查找树的查询复杂度取决于目标结点到树根的距离(即深度),因此当结点的深度普遍较大时,查询的均摊复杂度会上升。为了实现更高效的查询,产生了平衡树。如下例子
一般的二叉搜索树在构建的时候,容易退化成单链表,导致查找效率降为O(n);而平衡二叉树则会克服这种问题。这里先引入平衡因子的概念,如图:
左边这棵树,左子树为空,右子树高度为3;左子树高度-右子树高度 = -3。
右边这棵树,左子树高度为2,右子树高度为1。左子树高度-右子树高度 = 1。
一颗没有左右子树的结点,高度为1。
还可以发现,对于右边这棵树的每一个结点,其左右子树高度之差的绝对值没有大于1的。所以,如果当一个树或者子树的左右子树高度之差绝对值大于等于2了,我们认为它失衡了,这棵树改一改还能更高效。同时,对于每个结点,以这个结点为根的子树,其左右子树高度之差叫做这个子树或者这个结点的平衡因子BF
。而平衡二叉树是所有结点的平衡因子都小于2。
3 旋转
调整二叉搜索树为平衡二叉树一般是旋转二叉树,分为左旋和右旋,下面分别介绍两者。
3.1 左旋
总共分为3步:
1当前节点root的右子树会作为新树的根结点;
2当前节点root会作为新树根结点的左子树;
3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树。
见下图:
这里树的树高定义为:1 + max(左子树树高, 右子树树高)
代码
// 定义左旋转函数
Node* leftRoate(Node* root) {
// 1当前节点root的右子树会作为新树的根结点
// 2当前节点root会作为新树根结点的左子树
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
// 1当前节点root的右子树会作为新树的根结点
Node* newRoot = root->right;
// t2保存新树根原来的左子树
Node* t2 = newRoot->left;
// 2当前节点root会作为新树根结点的左子树
newRoot->left = root;
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
root->right = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
3.2 右旋
总共分为3步:
1当前节点root的左子树会作为新树的根结点
2当前节点root会作为新树根结点的右子树
3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
代码
// 定义右旋转函数
Node* rightRoate(Node* root) {
// 1当前节点root的左子树会作为新树的根结点
// 2当前节点root会作为新树根结点的右子树
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
// 1当前节点root的左子树会作为新树的根结点
Node* newRoot = root->left;
// t2保存新树根原来的右子树
Node* t2 = newRoot->right;
// 2当前节点root会作为新树根结点的右子树
newRoot->right = root;
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
root->left = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
4 四种需要旋转的情况
想一个问题,会等到不平衡因子到3了再旋转吗?不可能,当不平衡情况刚出现,就要立马进行旋转,也就是说,当不平衡因子是2的时候就要旋转了。
有这么一种情况,插入了一个结点,导致一个子树不平衡了, 导致整个二叉树也不平衡了,该转子树还是整个树?肯定是先转发生不平衡的最小的那个子树,那个子树转好了,说不定整个树就不用转了。当然还有一种情况,就是子树不平衡,但是整个树是平衡的,这样不用说,只用转子树就行了。所以,综上,我们要转的就是发生不平衡的最小的那个二叉树。当最小的不平衡二叉树转平衡了,如果还不平衡,再找下一个最小不平衡的二叉树进行修改。
上面已经介绍了两种基础的旋转情况,后面的4四种导致二叉平衡树失衡的都可以通过上面两种基础的旋转来解决
什么情况可能导致失衡呢?
-
插入
-
删除
插入可能导致哪几种情况的失衡呢?
-
在左孩子的左子树多了结点后导致失衡 LL型
-
在左孩子的右子树多了结点后导致失衡 LR型
-
在右孩子的左子树多了结点后导致失衡 RL型
-
在右孩子的右子树多了结点后导致失衡 RR型
注意:这里我们要把失衡的情况和旋转分开,LL型,LR型等,这里的LR指的是导致失衡的叶子结点在哪个位置上,不是说LL型就是左转再左转。这两个一定要区分开。
总之,我们就是要用上面介绍的两种:左旋
和右旋
来解决出现失衡的这四种情况。
通过穷举法,我们穷举出了一共可能得4种情况,那对应的这4种情况应该怎么修正呢?
首先,插入和删除都会导致不平衡,所以,我们的插入和删除的时候,先按照常规的二叉搜索树的逻辑进行插入和删除,然后,我们检查这个树是不是平衡二叉树,如果不是,找出来是上面四种情况的哪一种导致失衡的情况进行修改就行了。无论是插入还是删除,最终导致失衡的无非都是上面给出的四种情况,所以,接下来我们讨论给出的四种失衡情况该如何修改。
4.1 LL型失衡
描述:最小失衡子树的左孩子的左子树多了个结点导致失衡了。
解决:
-
使用右旋就能解决
右旋步骤:
1当前节点root的左子树会作为新树的根结点
2当前节点root会作为新树根结点的右子树
3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
图1是LL型,可以看到这个树不是平衡二叉树,而首先是以3根结点的二叉树不是平衡二叉树,先调整以3为根结点的二叉树,对其进行右旋,结果如图2所示;此时图2所示的位二叉树仍然不是平衡二叉树,为了方便演示,将其颜色变成图3所示,再对其进行右旋,结果如图4所示。
问题来了,怎么判断是LL型呢?
LL:该结点的平衡因子大于1 && 其左子树的平衡因子大于0
4.2 LR型失衡
描述:最小失衡子树的左孩子的右子树多了个结点导致失衡了。
解决:
-
左子树先进行左旋,这时候就可以看成了LL型
-
然后,进行右旋
问题来了,怎么判断是LR型呢?
LR:该结点的平衡因子大于1 && 其左子树的平衡因子小于0
4.3 RR型失衡
描述:最小失衡子树的右孩子的右子树多了个节点导致失衡了
解决:
-
使用左旋就能解决了
图1是RR型,可以看到这个树不是平衡二叉树,而首先是以2根结点的二叉树不是平衡二叉树,先调整以2为根结点的二叉树,对其进行左旋,结果如图2所示;此时图2所示的位二叉树仍然不是平衡二叉树,为了方便演示,将其颜色变成图3所示,再对其进行左旋,结果如图4所示0。
问题来了,怎么判断是RR型呢?
RR:该结点的平衡因子小于-1 && 其右子树的平衡因子小于0
4.4 RL型失衡
描述:最小失衡子树的右孩子的左子树多了个节点导致失衡了
解决:
-
对右子树先进行右旋,这样就会变成RR型失衡
-
对整个树左旋
问题来了,怎么判断是RR型呢?
RL:该结点的平衡因子小于-1 && 其左子树的平衡因子大于0
4.5 判断旋转
- LL:该结点的平衡因子大于1 && 其左子树的平衡因子大于0
- LR:该结点的平衡因子大于1 && 其左子树的平衡因子小于0
- RR:该结点的平衡因子小于-1 && 其右子树的平衡因子小于0
- RL:该结点的平衡因子小于-1 && 其左子树的平衡因子大于0
5 插入代码
#include <stdio.h>
#include <stdlib.h>
// 定义树的结构
typedef struct Node {
int val; // 数据
int height; // 树高
struct Node* left; // 左子树
struct Node* right; // 右子树
} Node;
// 定义生成新的结点,返回值是指向这个结点的指针
Node* newNode(int val) {
Node* node = (Node*)malloc(sizeof(Node));
node->val = val;
node->height = 1;
node->left = NULL;
node->right = NULL;
return node;
}
// 获取树的高度
int getHeight(Node* node) {
if (node == NULL) {
return 0;
}
return node->height;
}
int max(int a, int b) {
return a > b ? a : b;
}
// 获取平衡因子的函数
int getBalance(Node* node) {
return getHeight(node->left) - getHeight(node->right);
}
// 定义左旋转函数
Node* leftRoate(Node* root) {
// 1当前节点root的右子树会作为新树的根结点
// 2当前节点root会作为新树根结点的左子树
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
// 1当前节点root的右子树会作为新树的根结点
Node* newRoot = root->right;
// t2保存新树根原来的左子树
Node* t2 = newRoot->left;
// 2当前节点root会作为新树根结点的左子树
newRoot->left = root;
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
root->right = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
// 定义右旋转函数
Node* rightRoate(Node* root) {
// 1当前节点root的左子树会作为新树的根结点
// 2当前节点root会作为新树根结点的右子树
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
// 1当前节点root的左子树会作为新树的根结点
Node* newRoot = root->left;
// t2保存新树根原来的右子树
Node* t2 = newRoot->right;
// 2当前节点root会作为新树根结点的右子树
newRoot->right = root;
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
root->left = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
// 定义插入结点的函数
Node* insertNode(Node* node, int key) {
// 先二叉搜索树插入数据
if (node == NULL) {
return newNode(key);
}
if (key < node->val) {
node->left = insertNode(node->left, key);
} else if (key > node->val) {
node->right = insertNode(node->right, key);
} else {
return node;
}
// 到这里都是二叉搜索树的插入操作
// 更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
// 获取当前结点的平衡因子(根据平衡因子决定是否调整)
// LL型失衡-右转(满足:该结点的平衡因子大于1 && 其左子树的平衡因子大于0)
int balance = getBalance(node);
if (balance > 1 && getBalance(node->left) > 0) {
return rightRoate(node);
}
// LR型失衡-对root左子树左旋,再对root进行右旋 (满足:该结点的平衡因子大于1 && 其左子树的平衡因子小于0)
if (balance > 1 && getBalance(node->left) < 0) {
node->left = leftRoate(node->left);
return rightRoate(node);
}
// RR型失衡-左转(满足:该结点的平衡因子小于-1 && 其右子树的平衡因子小于0)
if (balance < -1 && getBalance(node->right) < 0) {
return leftRoate(node);
}
// RL型失衡-对root右子树右旋,再对root进行左旋 (满足:该结点的平衡因子小于-1 && 其左子树的平衡因子大于0)
if (balance < -1 && getBalance(node->right) > 0) {
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
// 先序遍历
void preOrder(Node* root) {
if (root == NULL) {
return;
}
printf("%d ", root->val);
preOrder(root->left);
preOrder(root->right);
}
// 中序遍历
void midOrder(Node* root) {
if (root == NULL) {
return;
}
midOrder(root->left);
printf("%d ", root->val);
midOrder(root->right);
}
// 查找函数
Node* findNode(Node* root, int key, int* counter) {
Node* curNode = root;
while (curNode != NULL) {
if (key < curNode->val) {
curNode = curNode->left;
(*counter)++;
} else if (key > curNode->val) {
curNode = curNode->right;
(*counter)++;
} else {
return curNode;
}
}
return NULL;
}
// 测试函数
void test() {
Node* root = NULL;
root = insertNode(root, 10);
root = insertNode(root, 20);
root = insertNode(root, 30);
root = insertNode(root, 40);
root = insertNode(root, 50);
root = insertNode(root, 60);
root = insertNode(root, 70);
int counter = 0;
Node* result = findNode(root, 70, &counter);
printf("找了%d次\n", counter);
printf("-----------先序遍历----------\n");
preOrder(root);
printf("\n-----------中序遍历----------\n");
midOrder(root);
}
int main(int argc, char const *argv[])
{
test();
return 0;
}
测试
6 删除节点
// 删除节点
// 前半部分是二叉搜索树的删除,后面是插入节点导致失衡,稍作调整
Node* deleteNode(Node* node, int key) {
if (node == NULL) {
return node;
}
if (node->val > key) {
node->left = deleteNode(node->left, key);
} else if (node->val < key) {
node->right = deleteNode(node->right, key);
} else if (node->val == key) {
// 情况1,叶子结点
if (node->left == NULL && node->right == NULL) {
Node* temp = node;
node = NULL;
free(temp);
} else if (node->left == NULL && node->right != NULL) {
// 情况2:只有右子树
Node* temp = node;
node = node->right;
free(temp);
} else if (node->left != NULL && node->right == NULL) {
// 情况3:只有左子树
Node* temp = node;
node = node->left;
free(temp);
} else if (node->left != NULL && node->right != NULL) {
// 情况4:左右子树都有
// 找到右子树上最左边的结点来接替该结点
Node* curNode = node->right;
while (curNode->left != NULL) {
curNode = curNode->left;
}
node->val = curNode->val;
node->right = deleteNode(node->right, curNode->val);
}
}
// 删除完成,接下来是调整
if (node == NULL) {
return node;
}
// 更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
// 计算平衡因子
int balance = getBalance(node);
// LL型-右转
if (balance > 1 && getBalance(node->left) >= 0) {
return rightRoate(node);
}
// LR型-对node左子树左旋,再对node进行右旋
if (balance > 1 && getBalance(node->left) < 0) {
node->left = leftRoate(node->left);
return rightRoate(node);
}
// RR型-左转
if (balance < -1 && getBalance(node->right) <= 0) {
return leftRoate(node);
}
// RL型-对node右子树右旋,再对node进行左旋
if (balance < -1 && getBalance(node->right) > 0) {
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
测试
#include <stdio.h>
#include <stdlib.h>
// 定义树的结构
typedef struct Node {
int val; // 数据
int height; // 树高
struct Node* left; // 左子树
struct Node* right; // 右子树
} Node;
// 定义生成新的结点,返回值是指向这个结点的指针
Node* newNode(int val) {
Node* node = (Node*)malloc(sizeof(Node));
node->val = val;
node->height = 1;
node->left = NULL;
node->right = NULL;
return node;
}
// 获取树的高度
int getHeight(Node* node) {
if (node == NULL) {
return 0;
}
return node->height;
}
int max(int a, int b) {
return a > b ? a : b;
}
// 获取平衡因子的函数
int getBalance(Node* node) {
return getHeight(node->left) - getHeight(node->right);
}
// 定义左旋转函数
Node* leftRoate(Node* root) {
// 1当前节点root的右子树会作为新树的根结点
// 2当前节点root会作为新树根结点的左子树
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
// 1当前节点root的右子树会作为新树的根结点
Node* newRoot = root->right;
// t2保存新树根原来的左子树
Node* t2 = newRoot->left;
// 2当前节点root会作为新树根结点的左子树
newRoot->left = root;
// 3如果新的树根,原来有左子树,原来的左子树就作为旧根结点的右子树
root->right = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
// 定义右旋转函数
Node* rightRoate(Node* root) {
// 1当前节点root的左子树会作为新树的根结点
// 2当前节点root会作为新树根结点的右子树
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
// 1当前节点root的左子树会作为新树的根结点
Node* newRoot = root->left;
// t2保存新树根原来的右子树
Node* t2 = newRoot->right;
// 2当前节点root会作为新树根结点的右子树
newRoot->right = root;
// 3如果新的树根,原来有右子树,原来的右子树就作为旧根结点的左子树
root->left = t2;
// 更新树高 root和newRoot
// 左子树树高 和 右子树树高最大值 + 1
root->height = 1 + max(getHeight(root->left), getHeight(root->right));
newRoot->height = 1 + max(getHeight(newRoot->left), getHeight(newRoot->right));
return newRoot;
}
// 定义插入结点的函数
Node* insertNode(Node* node, int key) {
// 先二叉搜索树插入数据
if (node == NULL) {
return newNode(key);
}
if (key < node->val) {
node->left = insertNode(node->left, key);
} else if (key > node->val) {
node->right = insertNode(node->right, key);
} else {
return node;
}
// 更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
// 获取当前结点的平衡因子(根据平衡因子决定是否调整)
// LL型失衡-右转(满足:该结点的平衡因子大于1 && 其左子树的平衡因子大于0)
int balance = getBalance(node);
if (balance > 1 && getBalance(node->left) > 0) {
return rightRoate(node);
}
// LR型失衡-对root左子树左旋,再对root进行右旋 (满足:该结点的平衡因子大于1 && 其左子树的平衡因子小于0)
if (balance > 1 && getBalance(node->left) < 0) {
node->left = leftRoate(node->left);
return rightRoate(node);
}
// RR型失衡-左转(满足:该结点的平衡因子小于-1 && 其右子树的平衡因子小于0)
if (balance < -1 && getBalance(node->right) < 0) {
return leftRoate(node);
}
// RL型失衡-对root右子树右旋,再对root进行左旋 (满足:该结点的平衡因子小于-1 && 其左子树的平衡因子大于0)
if (balance < -1 && getBalance(node->right) > 0) {
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
// 先序遍历
void preOrder(Node* root) {
if (root == NULL) {
return;
}
printf("%d ", root->val);
preOrder(root->left);
preOrder(root->right);
}
// 中序遍历
void midOrder(Node* root) {
if (root == NULL) {
return;
}
midOrder(root->left);
printf("%d ", root->val);
midOrder(root->right);
}
// 查找函数
Node* findNode(Node* root, int key, int* counter) {
Node* curNode = root;
while (curNode != NULL) {
if (key < curNode->val) {
curNode = curNode->left;
(*counter)++;
} else if (key > curNode->val) {
curNode = curNode->right;
(*counter)++;
} else {
return curNode;
}
}
return NULL;
}
// 删除节点
// 前半部分是二叉搜索树的删除,后面是插入节点导致失衡,稍作调整
Node* deleteNode(Node* node, int key) {
if (node == NULL) {
return node;
}
if (node->val > key) {
node->left = deleteNode(node->left, key);
} else if (node->val < key) {
node->right = deleteNode(node->right, key);
} else if (node->val == key) {
// 情况1,叶子结点
if (node->left == NULL && node->right == NULL) {
Node* temp = node;
node = NULL;
free(temp);
} else if (node->left == NULL && node->right != NULL) {
// 情况2:只有右子树
Node* temp = node;
node = node->right;
free(temp);
} else if (node->left != NULL && node->right == NULL) {
// 情况3:只有左子树
Node* temp = node;
node = node->left;
free(temp);
} else if (node->left != NULL && node->right != NULL) {
// 情况4:左右子树都有
// 找到右子树上最左边的结点来接替该结点
Node* curNode = node->right;
while (curNode->left != NULL) {
curNode = curNode->left;
}
node->val = curNode->val;
node->right = deleteNode(node->right, curNode->val);
}
}
// 删除完成,接下来是调整
if (node == NULL) {
return node;
}
// 更新树高
node->height = 1 + max(getHeight(node->left), getHeight(node->right));
// 计算平衡因子
int balance = getBalance(node);
// LL型-右转
if (balance > 1 && getBalance(node->left) >= 0) {
return rightRoate(node);
}
// LR型-对node左子树左旋,再对node进行右旋
if (balance > 1 && getBalance(node->left) < 0) {
node->left = leftRoate(node->left);
return rightRoate(node);
}
// RR型-左转
if (balance < -1 && getBalance(node->right) <= 0) {
return leftRoate(node);
}
// RL型-对node右子树右旋,再对node进行左旋
if (balance < -1 && getBalance(node->right) > 0) {
node->right = rightRoate(node->right);
return leftRoate(node);
}
return node;
}
// 测试函数
void test() {
Node* root = NULL;
root = insertNode(root, 10);
root = insertNode(root, 20);
root = insertNode(root, 30);
root = insertNode(root, 40);
root = insertNode(root, 50);
root = insertNode(root, 60);
root = insertNode(root, 70);
int counter = 0;
Node* result = findNode(root, 70, &counter);
printf("找了%d次\n", counter);
printf("-----------先序遍历----------\n");
preOrder(root);
printf("\n-----------中序遍历----------\n");
midOrder(root);
counter = 0;
root = deleteNode(root, 10);
root = deleteNode(root, 20);
root = deleteNode(root, 30);
result = findNode(root, 40, &counter);
printf("找了%d次\n", counter);
printf("-----------先序遍历----------\n");
preOrder(root);
printf("\n-----------中序遍历----------\n");
midOrder(root);
}
int main(int argc, char const *argv[])
{
test();
return 0;
}
结果
参考文献
[1]平衡树 - 维基百科,自由的百科全书 (wikipedia.org)
[2]【平衡二叉树(AVL树)】这个视频解决你关于平衡二叉树的所有问题_哔哩哔哩_bilibili