c++进阶之----A V L树

温馨提示:本篇文章重在逻辑性,代码部分只要能理解AVL树的逻辑,便可迎刃而解

1.概念

AVL树是由俄罗斯计算机科学家 Georgy Adelson-Velsky 和 Landis 在 1962 年提出的一种自平衡二叉搜索树,其核心特性在于能够在进行插入和删除操作时,通过特定的旋转操作保持树的平衡,从而保证树的查找效率始终高效。

2.平衡因子与平衡条件

刚刚提到,AVL树可以自我保持平衡,那么他是如何保持平衡的呢?

2.1平衡因子(Balance Factor)

AVL树中每个节点的平衡因子定义为该节点右子树的高度减去左子树的高度(也可以左减右,这个数值都是“相对的”,不重要)。平衡因子的取值范围是-1、0、1。当某个节点的平衡因子超出这个范围时,树就失去了平衡,需要进行旋转调整。

2.2平衡条件

AVL树要求所有节点的平衡因子只能是-1、0或1。如果因插入或删除操作导致某个节点的平衡因子超出这个范围,就需要通过旋转操作来重新平衡树。

如下图,是一个合格的AVL树

如下图,是一个不合格的AVL树,其将发生旋转

3.AVL树基本框架的实现

#pragma once
#include<iostream>
#include<utility>
using namespace std;
template<class k,class v>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到
	//实际上,这个指针并不是必须的,只是为了我们后面较为好操作才设置的
	//好与不好是相对的,设置了parent指针就要后期去维护它!!!
	pair<k, v> _kv;
	AVLTreeNode<k, v>* _left;
	AVLTreeNode<k, v>* _right;
	AVLTreeNode<k, v>* _parent;
	int _bf; // balance factor

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

};
template<class k,class v>
class AVLTree
{
	typedef AVLTreeNode<k, v> Node;
public:

private:
	Node* _root = nullptr;
};

 道理跟树是几乎一样的!只不过增加了几个东西而已!

4.插入功能的实现

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)      //插入的值比当前cur值大,去右树
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//找到位置了,将该节点插进去
	//此时,cur为空(由循环得出),parent为最后的那一层的某个节点
	cur = new Node(kv);
	//判断要插入的结点在parent的左边还是右边
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	//别忘了我们的树类似于双向,孩子能找到父亲,父亲也要能找到孩子
	cur->_parent = parnet;   
	// 更新平衡因子(从下往上)
	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)
		{
			// 旋转
		}
		else
		{
			assert(false);
		}
	}
	return true;
}

插入的第一步还是跟以前一样,找位置,然后new结点,插入,不同的是这里需要从新计算一下平衡因子,因为我们要保持这颗树的平衡,为旋转做准备

5.旋转

当AVL树失去平衡时,需要进行旋转操作来恢复平衡。旋转操作主要有四种情况

 5.1 右单旋(向右旋转)

当插入的节点在根节点的左子树的左子树上,导致根节点的平衡因子为-2时,进行右单旋。操作是将“根节点”的左子节点作为新的根节点,原根节点变为新根节点的右子节点,新根节点的原右子树变为原根节点的左子树。

 解释:abc均为一颗树或者结点,且初始状态下保持平衡,现在a树上插入一个结点,导致平衡因子发生变化,树发生右旋转,旋转方式如上文所述,将不平衡的那个结点(bf=2的那个)的左子节点作为新的“根节点”,原bf=2的那个结点变为新“根节点”的右子节点,新“根节点”的原右子树变为原“根节点”的左子树。

可以参照解释和图仔细理解一下,下面我们写右单旋的代码!

// 旋转
if (parent->_bf == -2 && cur->_bf = -1)// 右单旋
{
				RotateR(parent);
}
void RotateR(Node* parent)
{
	//由于我们要对部分结点的关系做改变,所以我们先把他们给固定好,
	//省的用时不好找
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	subL->_right = parent;
	//总体思路如上所述,(这是父亲找孩子,孩子可以为空)
	//但是我们还要考虑一下极端情况
	//即有的子节点为空的情况
	// 
	//如下是孩子找父亲,要考虑孩子是否为空!
	if (subLR)
	{
		subLR->_parent = parent;
	}
	//不平衡的地方已经改完,现在要把它与原来的树给连在一起(嫁接上)
	//这里要考虑两点:
	// 1、我改动的这部分本身就是原来的树
	// 2、我改动的这部分是原来的大树的一个“分支”,
	Node* parentparent = parent->_parent;
	parent->_parent = subL;
	if (parent == _root)  //情况一
	{
		_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;
}

 5.2 左单旋(向左旋转)

与右单旋对称,当插入的节点在根节点的右子树的右子树上,导致根节点的平衡因子为+2时,进行左单旋。操作是将“根节点”的右子节点作为新的根节点,原“根节点”变为新根节点的左子节点,新“根节点”的原左子树变为原“根节点”的右子树。

解释与5.1同理,这里不在赘述!

这是代码部分

else if (parent->_bf == 2 && cur->_bf = 1)// 左单旋
{
	RotateL(parent);
}
void RotateL(Node* parent)
{
	//由于我们要对部分结点的关系做改变,所以我们先把他们给固定好,
	//省的用时不好找
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

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

	//总体思路如上所述,(这是父亲找孩子,孩子可以为空)
	//但是我们还要考虑一下极端情况
	//即有的子节点为空的情况
	// 
	//如下是孩子找父亲,要考虑孩子是否为空!
	if (subRL)
	{
		subRL->_parent = parent;
	}
	//不平衡的地方已经改完,现在要把它与原来的树给连在一起(嫁接上)
	//这里要考虑两点:
	// 1、我改动的这部分本身就是原来的树
	// 2、我改动的这部分是原来的大树的一个“分支”,
	Node* parentparent = parent->_parent;
	parent->_parent = subR;
	if (parent == _root)  //情况一
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else     //情况二
	{
		//看是左支还是右支
		if (parentparent->_left == parent)
		{
			parentparent->_left = subR;
		}
		else
		{
			parentparent->_right = subR;
		}
		subR->_parent = parentparent;
		
	}
	subR->_bf = parent->_bf = 0;
}

5.3 左右双旋

 当插入的节点在“根节点”的左子树的右子树上,导致“根节点”的平衡因子为-2时,先对“根节点”的左子树进行左旋转,再对“根节点”进行右旋转。

 

void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	RotateL(parent->_left);
	RotateR(parent);
	if (bf == -1)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}

}

 注:右左双选同理,当插入的节点在根节点的右子树的左子树上,导致根节点的平衡因子为-2时,先对根节点的右子树进行RR旋转,再对根节点进行LL旋转。

void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(parent->_right);
	RotateL(parent);
	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

6.其他内容代码及汇总

#pragma once
#include<iostream>
#include<utility>
using namespace std;
template<class k,class v>
struct AVLTreeNode
{
	// 需要parent指针,后续更新平衡因子可以看到
	//实际上,这个指针并不是必须的,只是为了我们后面较为好操作才设置的
	//好与不好是相对的,设置了parent指针就要后期去维护它!!!
	pair<k, v> _kv;
	AVLTreeNode<k, v>* _left;
	AVLTreeNode<k, v>* _right;
	AVLTreeNode<k, v>* _parent;
	int _bf; // balance factor

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

};
template<class k,class v>
class AVLTree
{
	typedef AVLTreeNode<k, v> Node;
public:
	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)      //插入的值比当前cur值大,去右树
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//找到位置了,将该节点插进去
		//此时,cur为空(由循环得出),parent为最后的那一层的某个节点
		cur = new Node(kv);
		//判断要插入的结点在parent的左边还是右边
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//别忘了我们的树类似于双向,孩子能找到父亲,父亲也要能找到孩子
		cur->_parent = parnet;   
		// 更新平衡因子(从下往上)
		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)// 右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf = 1)// 左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1) // 左右双旋
				{
					RotateLR(parent);
				}
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}
	void RotateR(Node* parent)
	{
		//由于我们要对部分结点的关系做改变,所以我们先把他们给固定好,
		//省的用时不好找
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;
		//总体思路如上所述,(这是父亲找孩子,孩子可以为空)
		//但是我们还要考虑一下极端情况
		//即有的子节点为空的情况
		// 
		//如下是孩子找父亲,要考虑孩子是否为空!
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//不平衡的地方已经改完,现在要把它与原来的树给连在一起(嫁接上)
		//这里要考虑两点:
		// 1、我改动的这部分本身就是原来的树
		// 2、我改动的这部分是原来的大树的一个“分支”,
		Node* parentparent = parent->_parent;
		parent->_parent = subL;
		if (parent == _root)  //情况一
		{
			_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;
	}
	void RotateL(Node* parent)
	{
		//由于我们要对部分结点的关系做改变,所以我们先把他们给固定好,
		//省的用时不好找
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;
	
		//总体思路如上所述,(这是父亲找孩子,孩子可以为空)
		//但是我们还要考虑一下极端情况
		//即有的子节点为空的情况
		// 
		//如下是孩子找父亲,要考虑孩子是否为空!
		if (subRL)
		{
			subRL->_parent = parent;
		}
		//不平衡的地方已经改完,现在要把它与原来的树给连在一起(嫁接上)
		//这里要考虑两点:
		// 1、我改动的这部分本身就是原来的树
		// 2、我改动的这部分是原来的大树的一个“分支”,
		Node* parentparent = parent->_parent;
		parent->_parent = subR;
		if (parent == _root)  //情况一
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else     //情况二
		{
			//看是左支还是右支
			if (parentparent->_left == parent)
			{
				parentparent->_left = subR;
			}
			else
			{
				parentparent->_right = subR;
			}
			subR->_parent = parentparent;
			
		}
		subR->_bf = parent->_bf = 0;
	}
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		RotateL(parent->_left);
		RotateR(parent);
		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}

	}
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}
	bool IsbalanceTree()
	{
		return _IsBalanceTree(_root);
	}
	int Size()
	{
		return _Size(_root);
	}
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}
	int Height()
	{
		return _Height(_root);
	}
	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;
	}
	bool _IsBalanceTree(Node* root)
	{
		// 空树也是AVL树
		if (nullptr == root)
			return true;
		// 计算pRoot结点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		// pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (abs(diff) >= 2)
		{
			cout << root->_kv.first << "高度差异常" << endl;
			return false;
		}
		if (root->_bf != diff)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}

		return false;
	}
private:
	Node* _root = nullptr;
};

测试文件:

#define _CRT_SECURE_NO_WARNINGS 1

#include"AVL.h"
#include<vector>

// 测试代码
void TestAVLTree1()
{
	AVLTree<int, int> t;
	// 常规的测试用例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	for (auto e : a)
	{
		t.Insert({ e, e });
	}
	t.InOrder();
	cout << t.IsbalanceTree() << endl;
}

// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

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

	// 15:50
	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (size_t i = 0; i < v.size(); ++i)
	{
		t.Insert(make_pair(v[i], v[i]));

		// 手动条件断点
		/*if (i == 20)
		{
			int x = 0;
		}*/

		//cout <<i<<":"<<v[i]<<" "<< t.IsBalanceTree() << endl;
	}

	size_t end2 = clock();
	cout << "Insert:" << end2 - begin2 << endl;
	cout << t.IsbalanceTree() << endl;

	cout << "Height:" << t.Height() << endl;
	cout << "Size:" << t.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	//for (auto e : v)
	//{
	//	t.Find(e);
	//}
	// 随机值
	for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}
	size_t end1 = clock();
	cout << "Find:" << end1 - begin1 << endl;
}

int main()
{
	TestAVLTree2();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值