实现AVL树

目录

平衡因子

AVL树的概念

节点结构的定义

AVL树函数声明

AVL树的插入

AVL树的验证


前面的博客介绍了搜索二叉树,也提到了搜索二叉树的效率问题,当插入数据有序或者接近有序时,搜索二叉树就会退化成单支树,查找时间复杂度就变成O(N)了,因此大佬们在搜索二叉树的基础上做了一些改进,使得查找效率保持在O(logN)。本篇博客的内容也是搜索二叉树的进阶,如果对搜索二叉树还有疑问的朋友,建议先看看前面的博客 搜索二叉树-CSDN博客

平衡因子

上面说到,单纯的搜索二叉树有可能出现左右子树高度差过大导致查找效率低下,而AVL树引入了一个新的概念,就是平衡因子,每颗树的平衡因子就是右子树高度-左子树高度,可能为正/负/零

AVL树的概念

一棵 AVL 树或者是空树,或者是具有以下性质的二叉搜索树:
1.它的左右子树都是 AVL
2左右子树高度之差 ( 简称平衡因子 ) 的绝对值不超过 1(-1/0/1)
注意:之所以要求平衡因子的绝对值不超过1,而不是平衡因子必须是0,是因为有些节点个数无法
保证左右子树高度一样

节点结构的定义

注意:我们下面实现的AVL树,实现的是kv模型,也就是存储key值和对应的value值, 模拟map

cpp 复制代码
template <class K, class V>
struct AVLTreeNode
{
	//三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent; //方便进行AVL树进行调平
	pair<K, V> _kv; //存储键值对
	int _bf; //平衡因子(balance factor)

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		,_bf(0)
	{}
};

AVL树函数声明

cpp 复制代码
template <class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	void RotateL(Node* parent); //左单旋
	void RotateR(Node* parent); //右单旋
	void RotateLR(Node* parent); //左右双旋
	void RotateRL(Node* parent); //右左双旋
	bool insert(const pair<K, V>& kv); //插入
	void InOrder(); //中序遍历
	int Height(); //求二叉树高度
	bool IsBalance();//判断是否是AVL树
private:
	void _InOrder(Node* root);
	int _Height(Node* root); 
	bool _IsBalance(Node* root);
private:
	Node* _root;
};

AVL树的插入

插入的主逻辑和搜索二叉树基本一样,不一样的就是平衡因子的变化 以及 如果插入后不满足AVL树平衡的特点,需要调平衡。

cpp 复制代码
bool insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	//剩余代码---平衡因子的改变以及AVL树调平衡

	return true;
}

插入后当前子树平衡因子的变化

1.插入到树的左边,平衡因子 --

2.插入到树的右边,平衡因子++

注意:

1.插入数据之后,只会影响从整棵树的根到插入节点的这条路径上的节点(插入节点的祖先)的平衡因子(也只是可能会影响)

插入节点后对祖先的平衡因子(bf)影响

判断插入新的节点之后祖先的平衡因子是否变化,关键点就是看当前子树的高度是否变化,如果高变化了,就会向上继续影响祖先,高度不变,就不会影响祖先

注意:下面的parent是相对的,需要向上更新时,parent就会变化

1.parent的bf更新后是0,说明更新之前是-1/1, 更新之前一边高,一边低,而新插入节点填在了低的一边,所以更新后当前子树的高度不变,不用往上继续更新

2.parent的bf更新后是1/-1,说明更新之前是0, 也就时间更新之前左右子树一样高,插入节点之后一边高一边低,当前子树的高度发生变化,就影响了祖先,要继续往上更新, 更新到什么时候为止呢???

①更新到parent的平衡因子是0了,就停下来!

②更新到整棵树的根了,就停下了!

3.parent的bf更新是2/-2,说明更新之后AVL树失衡了, 需要调平衡
调平衡的方法:

1.左单旋

2.右单旋

3.右左双旋

4.左右双旋
左单旋 --- **新节点插入较高右子树的右侧---**cur->_bf == 1 && parent->bf == 2

具体图:

抽象图:

左单旋代码

cpp 复制代码
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		Node* parentParent = parent->_parent;

		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}

			subR->_parent = parentParent;
		}

		parent->_bf = subR->_bf = 0;
	}

右单旋 --- **新节点插入较高左子树的左侧 ---**cur->_bf == -1 && parent->_bf == -2

右单旋代码

cpp 复制代码
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subL;
			}
			else
			{
				parentParent->_right = subL;
			}

			subL->_parent = parentParent;
		}

		subL->_bf = parent->_bf = 0;
	}

右左双旋 --- **新节点插入较高右子树的左侧 ---**parent->_bf == 2 && cur->_bf == -1

注:为了方便演示,我们将上述讲解右单旋/左单旋抽象图中的b树进行拆分

右左双旋三种情况:

①新节点插入到b

subRL平衡因子更新成-1

②新节点插入到c

subRL平衡因子更新成1

③60自己就是新增

subRL平衡因子更新成0

右左双旋代码

从上面三张图可以看到,可以根据插入新节点之后subRL的平衡因子的值来判断是哪种情况!

cpp 复制代码
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

		if (bf == 0)
		{
			// subRL自己就是新增
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			// subRL的左子树新增
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1)
		{
			// subRL的右子树新增
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

左右双旋 --- **新节点插入较高左子树的右侧 ---**parent->_bf == -2 && cur->_bf == 1

左右双旋三种情况:

①新节点插入到b

②新节点插入到c

③60自己就是新增

左右双旋代码

cpp 复制代码
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf; //subLR不可能为nullptr,因为subL的平衡因子是1

		//1、以subL为旋转点进行左单旋
		RotateL(subL);

		//2、以parent为旋转点进行右单旋
		RotateR(parent);

		//3、更新平衡因子
		if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false); //在旋转前树的平衡因子就有问题
		}
	}

AVL树插入代码

cpp 复制代码
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

AVL树的验证

中序遍历

cpp 复制代码
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

求二叉树高度

cpp 复制代码
	int Height()
	{
		return _Height(_root);
	}
private:
	int _Height(Node* root)
	{
		if (root == nullptr) return 0;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

判断是否是AVL树

cpp 复制代码
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	bool _IsBalance(Node* root)
	{
		if (root == nullptr) return true;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

测试AVL

cpp 复制代码
#include <iostream>
#include <assert.h>
using namespace std;

#include "AVL.h"

void test1()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}

	t.InOrder();
	cout << t.IsBalance() << endl;
}

void test2()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}

	t.InOrder();
	cout << t.IsBalance() << endl;
}

#include <vector>
void test3()
{
	const int N = 30;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand());
		cout << v.back() << endl;
	}

	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	cout << t.IsBalance() << endl;
}

int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}
相关推荐
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导1 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
️南城丶北离2 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工2 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
唐诺8 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨9 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
菜鸡中的奋斗鸡→挣扎鸡9 小时前
滑动窗口 + 算法复习
数据结构·算法