【树】平衡二叉树(Balanced Binary Tree)

文章介绍了二叉排序树(BST)在数据序列[1,2,3,4,5,6]下可能退化为单链表的问题,导致查询效率降低。为解决这一问题,文章引入了AVL树,一种保持平衡的二叉搜索树,确保查询、插入和删除操作的时间复杂度为O(logn)。AVL树通过平衡因子和旋转操作保持左右子树的高度差不超过1,从而避免退化,提高性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【二叉排序树(BST)树的不足】

现给定一组有序数列[1, 2, 3, 4, 5, 6],要求创建一颗二叉排序树(BST),并分析问题所在!
在这里插入图片描述

由数列[1, 2, 3, 4, 5, 6]构成的BST树,如上图所示。那么这棵树存在的问题有以下几点:

1、左子树全部为空,从形式上看,更像一个单链表,实际上已经退化成一个单链表了 (时间复杂度退化成了O(n))

2、对于插入、删除、修改速度是没有影响,但是这种结构查询效率会明显降低(因为需要依次比较)不能发挥BST的优势,因为每次还需要比较左子树,器查询速度比单链表还慢。

因此为了解决二叉排序树(BST)退化成链表的问题,出现了一种全新的树结构(AVL树),这种结构不仅能防止退化成单链表,还在插入,查找,删除得到了进一步的改善,所使用的时间复杂度和空间复杂度相较于其他树更低!

2、AVL树介绍
1、平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树,可以保证查询效率较高。

2、具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL算法、替罪羊树、Treap、伸展树等。
在这里插入图片描述
如上图左边就是一颗平衡二叉树(AVL树),需要注意的是:平衡二叉树必须是一颗二叉排序树(BST),在BST树的基础上满足左右两个子树的高度差的绝对值不超过 1。

平衡二叉树是一种特殊的二叉搜索树,它的特点是:

每个节点的左右子树的高度差不超过1,也就是说,任意节点的左右子树高度差不会超过1,这样可以保证树的高度不会太高,降低了查找、插入、删除操作的时间复杂度。

平衡二叉树的查找、插入、删除操作的时间复杂度都是O(log n),与树的高度相关,高度越低,时间复杂度越小。

平衡二叉树的实现方式有很多,比如AVL树、红黑树等,它们的性质略有不同,但都保证了树的平衡性,提高了树的性能。

平衡二叉树可以用于实现有序集合、有序映射等数据结构,也可以应用于数据库索引、文件系统等领域。

判断「平衡二叉树」

判断「平衡二叉树」的 2 个条件:

  1. 是「二叉排序树」
  2. 任何一个节点的左子树或者右子树都是「平衡二叉树」(左右高度差小于等于 1)

(1)下图不是「平衡二叉树」因为它不是「二叉排序树」违反第 1 条件
在这里插入图片描述
(2)下图不是「平衡二叉树」因为有节点子树高度差大于 1 违法第 2 条件
在这里插入图片描述
(3)下图是「平衡二叉树」因为符合 1、2 条件
在这里插入图片描述

【平衡因子 BF】

定义:左子树和右子树高度差
计算:平衡因子=左子树高度 - 右子树高度的值
别名:简称 BF(Balance Factor 而不是 Boy Friend)
一般来说 BF 的绝对值大于 1,,平衡树二叉树就失衡,需要「旋转」纠正

【最小不平衡子树】

距离插入节点最近的,并且 BF 的绝对值大于 1 的节点为根节点的子树。
「旋转」纠正只需要纠正「最小不平衡子树」即可
在这里插入图片描述

【二种旋转方式】

左旋
旧根节点为新根节点的左子树
新根节点的左子树(如果存在)为旧根节点的右子树

右旋:
旧根节点为新根节点的右子树
新根节点的右子树(如果存在)为旧根节点的左子树

【4 种旋转纠正类型】

LL 型:插入左孩子的左子树,右旋
RR 型:插入右孩子的右子树,左旋
LR 型:插入左孩子的右子树,先左旋,再右旋
RL 型:插入右孩子的左子树,先右旋,再左旋

在这里插入图片描述

【LL 型失衡】【右旋】

第三个节点「1」插入的 时候,BF(3) = 2,BF(2) = 1 LL 型失衡,右旋,根节点顺时针旋转

(1)最小不平衡子树「右旋」

旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
在这里插入图片描述

【RR 型失衡】【左旋】

第三个节点「3」插入的 时候,BF(1)=-2 BF(2)=-1,RR 型失衡,左旋,根节点逆时针旋转

(1)最小不平衡子树左旋
旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
在这里插入图片描述

【LR 型】

第三个节点「3」插入的 时候,BF(3)=2 BF(1)=-1 LR 型失衡,先「左旋」再「右旋」

(1)最小不平衡子树左子树 {2,1} 先左旋

旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树

在这里插入图片描述
(2)最小不平衡子树 {3,2,1} 再右旋

旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
在这里插入图片描述

【 RL 型】

第三个节点「1」插入的 时候,BF(1)=-2 BF(3)=1 RL 型失衡,先「右旋」再「左旋」

(1)最小不平衡子树根节点右子树{3,2}先右旋

旧根节点(节点 3)为新根节点(节点 2)的右子树
新根节点的 右子树(如果存在)为旧根节点的左子树
在这里插入图片描述
(2)最小不平衡子树 {1,2,3} 再左旋(L)

旧根节点(节点 1)为新根节点(节点 2)的左子树
新根节点的左子树(如果存在)为旧根节点的右子树
在这里插入图片描述

【实例】

接下来我们以 {3,2,1,4,5,6,7,10,9,8} 为实例练习刚刚的 4 种插入方式

(1)依次插入 3、2、1 插入第三个点 1 的时候 BF(3)=2 BF(2)=1,LL 型失衡。

对最小不平衡树 {3,2,1}进行「右旋」
在这里插入图片描述
(2)依次插入 4 ,5 插入 5 点的时候 BF(3) = -2 BF(4)=-1,RR 型失衡

对最小不平衡树 {3,4,5} 进行「左旋」
在这里插入图片描述
(3)插入 6 点的时候 BF(2)=-2 BF(4)=-1 ,RR 型失衡 对最小不平衡树进行「左旋」

旧根节点(节点 2)为新根节点(节点 4)的左子树
新根节点(节点 4)的 左子树(节点 3)为旧根节点的右子树
在这里插入图片描述
新根节点(节点 4)的左子树(节点 3)为旧根节点的右子树
在这里插入图片描述
(4)插入 7 节点的时候 BF(5)=-2, BF(6)=-1 ,RR 型失衡,对最小不平衡树 进行「左旋」

旧根节点(节点 5)为新根节点(节点 6)的左子树
新根节点的左子树(这里没有)为旧根节点的右子树
在这里插入图片描述
(5)依次插入 10 ,9 。插入 9 点的时候 BF(10) = 1,BF(7) = -2 ,RL 型失衡,对先「右旋」再「左旋」

右子树先「右旋」

最小不平衡子树的右子树 {10,9} 先右旋:

旧根节点(节点 10)为新根节点(节点 9)的右子树
新根节点(节点 9)的右子树(这里没有右子树)为旧根节点的左子树
在这里插入图片描述
最小不平衡子树再左旋:

旧根节点(节点 7)为新根节点(节点 9)的左子树
新根节点(节点 9)的左子树(这里没有左子树)为旧根节点的右子树
在这里插入图片描述
(6)最后插入节点 8 ,BF(6)=-2 BF(9)=1,RL 型失衡,先「右旋」再「左旋」

最小不平衡子树的右子树 {9,7,10,8} 先「右旋」

右旋:

旧根节点(节点 9 {9,10})为新根节点(节点 7)的右子树
新根节点(节点 7)的右子树(这里是 节点 8)为旧根节点(节点 9)的左子树
在这里插入图片描述
最小不平衡子树 {6,5,7,9,8,10} 再「左旋」

旧根节点(节点 6 {6,5} )为新根节点(节点 7)的左子树
新根节点的左子树(这里没有)为旧根节点的右子树
在这里插入图片描述
左旋结束
在这里插入图片描述
全部代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef struct node {
	int data;		//数据
	node *left;		//左指针
	node *right;	//右指针
	int height;		//其左右子树最大深度
} avlnode, * avltree;

avlnode *creat_node(int key, avlnode *left, avlnode *right) {
	avlnode *node = new avlnode;
	node->data = key;
	node->left = left;
	node->right = right;
	node->height = 0;
	return node;
}

//获取深度
int get_height(avlnode *node) {
	//如果结点为空就返回0,如果不为空就返回它的深度
	return node == NULL ? 0 : ((avlnode *)(node))->height;
}

//LL 左孩子的左子树(右旋)
avltree left_left_rotation(avltree tree) {
	avlnode *k = tree->left;
	tree->left = k->right;
	k->right = tree;
	tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
	k->height = max(get_height(k->left), get_height(k->right)) + 1;
	return k;
}

//RR 右孩子的右子树(左旋)
avltree right_right_rotation(avltree tree) {
	avlnode *k = tree->right;
	tree->right = k->left;
	k->left = tree;
	tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
	k->height = max(get_height(k->left), get_height(k->right)) + 1;
	return k;
}

//LR 左孩子的右子树(先左旋再右旋)
avltree left_right_rotation(avltree tree) {
	tree->left = right_right_rotation(tree->left);
	tree = left_left_rotation(tree);
	return tree;
}

//RL 右孩子的左子树(先右旋再左旋)
avltree right_left_rotation(avltree tree) {
	tree->right = left_left_rotation(tree->right);
	tree = right_right_rotation(tree);
	return tree;
}

//插入结点
avltree avltree_insertNode(avltree tree, int key) {
	//判断当前结点是否为空,如果为空则创建结点
	if (tree == NULL) {
		avlnode *node = creat_node(key, NULL, NULL);
		tree = node;
	}
	//开始递归寻找插入结点的位置
	else if (key < tree->data) {
		tree->left = avltree_insertNode(tree->left, key);	//先递归插入结点
		if (get_height(tree->left) - get_height(tree->right) == 2) {
			//在这里判断是LL还是LR
			if (key > tree->left->data) {
				tree = left_right_rotation(tree);
			} else {
				tree = left_left_rotation(tree);
			}
		}
	} else if (key > tree->data) {
		tree->right = avltree_insertNode(tree->right, key);	//先递归插入结点
		if (get_height(tree->right) - get_height(tree->left) == 2) {
			//在这里判断是RL还是RR
			if (key < tree->right->data) {
				tree = right_left_rotation(tree);
			} else {
				tree = right_right_rotation(tree);
			}
		}
	} else {
		cout << "不允许插入相同的值" << endl;
	}
	//回溯时,更新结点深度
	tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
	return tree;
}

//找到删除结点的前驱(和二叉排序树类似)
avlnode *mininum_node(avltree tree) {
	if (tree == NULL) {
		return NULL;
	}
	while (tree->right) {
		tree = tree->right;
	}
	return tree;
}

//删除结点
avltree avltree_deleNode(avltree tree, int key) {
	if (tree == NULL) {
		cout << "没有该结点" << endl;
		return tree;
	}

	//要删除的结点在左子树
	if (key < tree->data) {
		tree->left = avltree_deleNode(tree->left, key);
		//判断是否失衡
		if (get_height(tree->right) - get_height(tree->left) == 2) {
			int RL = get_height(tree->right->left), RR = get_height(tree->right->right);
			//判断属于哪种情况
			if (RL > RR) {
				tree = right_left_rotation(tree);
			} else {
				tree = right_right_rotation(tree);
			}
		}
	}
	//要删除的结点在右子树
	else if (key > tree->data) {
		tree->right = avltree_deleNode(tree->right, key);
		//判断是否失衡
		if (get_height(tree->left) - get_height(tree->right) == 2) {
			int LR = get_height(tree->left->right), LL = get_height(tree->left->left);
			//判断属于哪种情况
			if (LR > LL) {
				tree = left_right_rotation(tree);
			} else {
				tree = left_left_rotation(tree);
			}
		}
	}
	//找到了要删除的结点
	else {
		//如果要删除的结点有两个孩子
		if (tree->left && tree->right) {
			//找到删除结点左子树的最大值(和二叉排序树操作类似)
			avlnode *min_node = mininum_node(tree->left);
			tree->data = min_node->data;	//改变要删除结点的值
			tree->left = avltree_deleNode(tree->left, min_node->data);	//找到下一个要删除的结点
		}
		//如果要删除的结点只有一个孩子或者没有孩子
		else {
			tree = tree->left ? tree->left : tree->right;
		}
	}

	//更新结点深度
	if (tree) {
		tree->height = max(get_height(tree->left), get_height(tree->right)) + 1;
	}
	return tree;
}

//中序遍历
void in_order(avltree tree) {
	if (tree) {
		in_order(tree->left);
		cout << tree->data << " ";
		in_order(tree->right);
	}
}

int main() {
	avltree tree = NULL;
	int a[] = { 1, 4, 3, 2, 9, 6, 7, 11, 10, 8 };
	int length = sizeof(a) / sizeof(int);
	for (int i = 0; i < length; i++) {
		tree = avltree_insertNode(tree, a[i]);
	}
	in_order(tree);
	cout << endl;
	tree = avltree_deleNode(tree, 2);
	in_order(tree);
	cout << endl;
	tree = avltree_deleNode(tree, 8);
	in_order(tree);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值