平衡二叉树和红黑树
平衡二叉树(AVL树)
定义
平衡二叉树(AVL树)是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。
将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF。平衡二叉树上所有结点的平衡因子只可能是-1,0,1三种。
距离插入结点最近的且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡子树。
思想
在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
当最小不平衡子树根结点的平衡银子BF是大于1时,就右旋;小于-1时就左旋。
代码
#include<stdio.h>
#include<stdlib.h>
typedef struct AVLNode
{
int data;
int height;
struct AVLNode* left;
struct AVLNode* right;
}Node;
#define HEIGHT(node) ((node == NULL)? 0 : (((Node*)(node))->height ))
#define MAX(a,b) ((a > b) ? (a) : (b))
int get_height(Node* node)
{
return HEIGHT(node);
}
/*插入左孩子的左子树-右旋*/
//传入参数为最小失衡结点tree,对tree进行右旋
Node* left_left(Node* tree)
{
//结点调整
Node* k = tree->left;//保存tree的左孩子,k将是最终的父节点
tree->left = k->right;//将k的右孩子接到tree的左子树
k->right = tree;//tree作为k的右子树
//高度调整(这里指深度:用左右子树来判断)
k->height = MAX(get_height(k->left), get_height(k->right)) + 1;
tree->height = MAX(get_height(tree->left), get_height(tree->right)) + 1;
return k;
}
/*插入右孩子的右子树-左旋*/
//传入参数为最小失衡结点tree,对tree进行左旋
Node* right_right(Node* tree)
{
//结点调整
Node* k = tree->right;
tree->right = k->left;
k->left = tree;
//高度调整
k->height = MAX(get_height(k->left), get_height(k->right)) + 1;
tree->height = MAX(get_height(tree->left), get_height(tree->right)) + 1;
return k;
}
/*插入左孩子的右子树-先左旋再右旋*/
//对tree->left左旋(left_left),对tree右旋(right_right)
Node* left_right(Node* tree)
{
tree->left = right_right(tree->left);
tree = left_left(tree);
return tree;
}
/*插入右孩子的左子树-先右旋再左旋*/
//对tree->right右旋(left_left),对tree左旋
Node* right_left(Node* tree)
{
tree->right = left_left(tree->right);
tree = right_right(tree);
return tree;
}
/*创建一棵树,根结点为node*/
Node* create(int key)
{
Node* node = (Node*)malloc(sizeof(Node));
//此处可判断是否创建成功,我省略了
node->data = key;
node->left = NULL;
node->right = NULL;
node->height = 0;
return node;
}
/*往根节点为tree的树中插入一个值key*/
//插入位置同二叉排序树的逻辑,大于向右找位置,小于向左找位置
Node* Insert(Node* tree, int key)
{
//如果为空,就创建一棵树
if (tree == NULL)
{
Node* node = create(key);
tree = node;
}
//向左子树插入
else if (key < tree->data)
{
//递归寻找插入位置
tree->left = Insert(tree->left, key);
//判断是否失衡
if (get_height(tree->left) - get_height(tree->right) == 2)
{
//判断插入位置在左孩子的左子树还是右子树
if (key < tree->left->data)
tree = left_left(tree);
else
tree = left_right(tree);
}
}
//向右子树插入
else if (key > tree->data)
{
tree->right = Insert(tree->right, key);
if (get_height(tree->right) - get_height(tree->left) == 2)
{
if (key > tree->right->data)
tree = right_right(tree);
else
tree = right_left(tree);
}
}
else
printf("不允许插入重复的值\n");
//重新调整二叉树深度
tree->height = MAX(get_height(tree->left), get_height(tree->right)) + 1;
return tree;
}
/*查找结点*/
Node* search(Node* tree, int key)
{
if (tree == NULL || tree->data == key)
return tree;
else if (key < tree->data)
search(tree->left, key);
else
search(tree->right, key);
}
/*找到替换结点-左子树的最右边*/
Node* mininum(Node* tree)
{
if (tree == NULL)
return NULL;
while (tree->left)
tree = tree->left;
return tree;
}
/*前序遍历*/
void pre_order(Node* tree)
{
if (tree)
{
printf("%d ", tree->data);
pre_order(tree->left);
pre_order(tree->right);
}
}
/*中序遍历*/
void in_order(Node* tree)
{
if (tree)
{
in_order(tree->left);
printf("%d ", tree->data);
in_order(tree->right);
}
}
int main()
{
//第一种情况-左孩子的左子树
Node* tree1 = NULL;
int a1[] = { 13, 8, 15, 3, 10};
int l1 = sizeof(a1) / sizeof(int);
for (int i = 0; i < l1; i++)
{
tree1 = Insert(tree1, a1[i]);
}
printf("第一种情况-左孩子的左子树\n");
printf("前序遍历:");
pre_order(tree1);
printf("\n");
printf("中序遍历:");
in_order(tree1);
printf("\n");
printf("根结点的深度为:%d\n\n",tree1->height);
printf("插入1\n");
tree1 = Insert(tree1, 1);
printf("前序遍历:");
pre_order(tree1);
printf("\n");
printf("中序遍历:");
in_order(tree1);
printf("\n");
printf("根结点的深度为:%d\n\n", tree1->height);
//第二种情况-右孩子的右子树
Node* tree2 = NULL;
int a2[] = { 13, 8, 15, 14, 16 };
int l2 = sizeof(a2) / sizeof(int);
for (int i = 0; i < l2; i++)
{
tree2 = Insert(tree2, a2[i]);
}
printf("第二种情况-右孩子的右子树\n");
printf("前序遍历:");
pre_order(tree2);
printf("\n");
printf("中序遍历:");
in_order(tree2);
printf("\n");
printf("根结点的深度为:%d\n\n", tree2->height);
tree2 = Insert(tree2, 20);
printf("插入20\n");
printf("前序遍历:");
pre_order(tree2);
printf("\n");
printf("中序遍历:");
in_order(tree2);
printf("\n");
printf("根结点的深度为:%d\n\n", tree2->height);
//第三种情况-左孩子的右子树
Node* tree3 = NULL;
int a3[] = { 13, 8, 15, 3, 10 };
int l3 = sizeof(a3) / sizeof(int);
for (int i = 0; i < l3; i++)
{
tree3 = Insert(tree3, a3[i]);
}
printf("第三种情况-左孩子的右子树\n");
printf("前序遍历:");
pre_order(tree3);
printf("\n");
printf("中序遍历:");
in_order(tree3);
printf("\n");
printf("根结点的深度为:%d\n\n", tree3->height);
tree3 = Insert(tree3, 9);
printf("插入9\n");
printf("前序遍历:");
pre_order(tree3);
printf("\n");
printf("中序遍历:");
in_order(tree3);
printf("\n");
printf("根结点的深度为:%d\n\n", tree3->height);
//第四种情况 - 右孩子的左子树
Node* tree4 = NULL;
int a4[] = { 13, 8, 18, 15, 20 };
int l4 = sizeof(a4) / sizeof(int);
for (int i = 0; i < l4; i++)
{
tree4 = Insert(tree4, a4[i]);
}
printf("第四种情况-右孩子的左子树\n");
printf("前序遍历:");
pre_order(tree4);
printf("\n");
printf("中序遍历:");
in_order(tree4);
printf("\n");
printf("根结点的深度为:%d\n\n", tree4->height);
tree4 = Insert(tree4, 14);
printf("插入14\n");
printf("前序遍历:");
pre_order(tree4);
printf("\n");
printf("中序遍历:");
in_order(tree4);
printf("\n");
printf("根结点的深度为:%d\n\n", tree4->height);
}
结果
根据以上四种插入方式,旋转方式总结如下:
插入方式 描述 旋转方式
LL 在结点A的左孩子的左子树插入导致A失衡 右旋
RR 在结点A的右孩子的右子树插入导致A失衡 左旋
LR 在结点A的左孩子的右子树插入导致A失衡 先左旋再右旋
RL 在结点A的右孩子的左子树插入导致A失衡 先右旋再左旋
红黑树
定义
红黑树(Red Black Tree)是一种自平衡的二叉搜索树,与AVL树类似,在其上进行的插入、删除、查找操作的平均时间复杂度均为O(logn)。
但与AVL树不同的是,红黑树的平衡不是非常严格的平衡(即左右子树高度差不超过1),它牺牲了部分平衡性来换取了插入、删除时的少量旋转操作。
性质:
1、节点是红色或黑色。
2、根节点是黑色。
3、每个叶节点(NIL节点,空节点)是黑色的。
4、每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)。
5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。(这个黑结点数量也被称为黑高)
基于这5个性质,红黑树能够保证从根结点到叶子结点最长深度不会超过最短深度的2倍,从而保证了红黑树的平衡性。