【C++】AVL树

个人主页zxctscl
如有转载请先通知

文章目录

  • [1. 底层结构](#1. 底层结构)
  • [2. AVL树的概念](#2. AVL树的概念)
  • [3. AVL树节点的定义](#3. AVL树节点的定义)
  • [4. AVL树的插入](#4. AVL树的插入)
  • [5. AVL树的旋转](#5. AVL树的旋转)
  • [6. AVL树的验证](#6. AVL树的验证)
  • [7. AVLTree.h](#7. AVLTree.h)

1. 底层结构

前面对map/multimap/set/multiset进行了简单的介绍: 【C++】map和set

,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

2. AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下 。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年

发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

因为有些节点的数量,像2和4,做不到高度差相等,所以规则就退而求其次,左右高度差不超过1。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1) 平衡因子并不是必须的,只是它的一种实现方式。

平衡因子=右子树高度-左子树高度

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。

3. AVL树节点的定义

AVL树节点的定义:

cpp 复制代码
template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& data)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _data(data), _bf(0)
	{}
	AVLTreeNode<T>* _pLeft; // 该节点的左孩子
	AVLTreeNode<T>* _pRight; // 该节点的右孩子
	AVLTreeNode<T>* _pParent; // 该节点的双亲
	T _data;
	int _bf; // 该节点的平衡因子
};

4. AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么

AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子(更新插入节点的祖先节点的平衡因子)
    (1)插入到父亲的左边,父亲平衡因子减减
    (2)插入到父亲的右边,父亲平衡因子加加
    (3)如果父亲节点的平衡因子等于0,父亲所在子树高度不变,不再继续往上更新,插入结束。
    (4)如果父亲的平衡因子是1或者-1,父亲所在子树高度变了,继续往上更新
    (5)如果父亲的平衡因子是2或者-2,父亲所在子树已经不平衡了,需要旋转处理
    更新中不可能出现其它值,插入之前树是AVL树,平衡因子是-1 0 1,加加减减最多出现(3)(4)(5)情况。

插入一个节点,先判断,如果root为空,就先插入第一个节点:

cpp 复制代码
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

用cur指向root,插入比root大往右走,是比较pair的first,比root小往左走:

cpp 复制代码
		Node* cur = _root;
		Node* parent = nullptr;
		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;
		}
		else
		{
			parent->_left = cur;
		}
		

但是这里是三叉链,这里得和父亲链接起来:cur->_parent = parent;

这里得更新平衡因子:插入到父亲的左边,父亲平衡因子减减;插入到父亲的右边,父亲平衡因子加加;平衡因子等于0,父亲所在子树高度不变,更新结束。平衡因子是1或者-1,父亲所在子树高度变了,继续往上更新,父亲的平衡因子是2或者-2,父亲所在子树已经不平衡了

cpp 复制代码
	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)
			{
				// 当前子树出问题了,需要旋转平衡一下


				break;
			}
			else
			{
				// 理论而言不可能出现这个情况
				assert(false);
			}
		}

5. AVL树的旋转

这里用抽象图来代表某一类:

abc代表高度为h的AVL子树:

在a这里插入新节点,a的高度变化从h变成h+1,就会把30的平衡因子更新为-1;

具象图:

  1. h==0:

    30这里新增一个节点。

  2. h==1:

    在20的左边或者右边新增都会引发旋转

  3. h==2

    高度为2的AVL树有三种:

    b和c是x/y/z中的任意一种

    a只能是z这种情况,如果a是x这种情况:

    那么当a插入一个节点,像下面这样,高度不变,就不会往上更新:

如果长这样:它自己就不旋转了:

所以a一定就是下面这种形状,插入一个节点才会引发旋转:

当h==2时候,a插入一个节点,就会引发旋转

b和c三种形状都可以,而插入可以选择4个节点中任何一个,所以

  1. h==3
    高度为3最满的就是下面这样,4个节点可能是满的C44,四个节点中任意选三,四个节点中任意选两个,四个节点中任意选一个,这些都可能是高度为h的情况。光树就可能有15种。

a如果是下面这种情况:就有8种插入新节点的可能:

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,

使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1. 新节点插入较高左子树的左侧---左左:右单旋(左边高,往右边压)

b变成60的左边,30<b子树<60<c子树

所以当h==1时候,也是同样的右边压

旋转后的a c位置没变,把60变到30右边,b变到60左边:

此时代码就是:

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

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

		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}

		parent->_bf = subL->_bf = 0;
	}
  1. 新节点插入较高右子树的右侧---右右:左单旋

现在右边高,让subRL变成30的右边,30<b<60<c:

右边高,左边旋:

代码和右单旋类似:

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

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

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

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

像下面这样的情况,发现右边高,就左单旋,而出来结果导致左边高,再右单旋,发现结果和刚开始一样。

上面这个图并不是纯粹的右边高,它是右边高,左边高,不像纯粹的右边高(下面图这样):

这不是单纯的右边高,右边高,左边高

以8为旋转点,进行右边单旋啊,经过这个单旋,变成单纯的右边高。在意parent为旋转点进行左单选:

  1. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再

考虑平衡因子的更新。

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

		int bf = subLR->_bf;

		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);
		}

	}
  1. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

总结:

假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR
    当pSubR的平衡因子为1时,执行左单旋
    当pSubR的平衡因子为-1时,执行右左双旋
  2. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL
    当pSubL的平衡因子为-1是,执行右单旋
    当pSubL的平衡因子为1时,执行左右双旋
    旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新
cpp 复制代码
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

6. AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    (1)每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    (2)节点的平衡因子是否计算正确
cpp 复制代码
	  int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}


		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			/*int leftHeight= _Height(root->_left);
			int rightHeight = _Height(root->_right);*/

			return max(_Height(root->_left), _Height(root->_right)) + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);

			if (abs(leftHeight - rightHeight) >= 2)//不平衡
			{
				cout << root->_kv.first << endl;
				return false;
			}
			
			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << endl;
				return false;
			}

			return _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}

测试用例:

cpp 复制代码
void TestAVLTree2()
{
	const int N = 100000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

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

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

	cout << "Insert:" << end2 - begin2 << endl;
	//cout << t.IsBalance() << 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;
}

AVL树的性能:

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

7. AVLTree.h

cpp 复制代码
#pragma once
#include<assert.h>
#include<vector>


template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;

	int _bf;  // balance factor

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

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// logN
	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;
		}
		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)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(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;
	}



	Node* 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 cur;
			}
		}

		return nullptr;
	}


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}


	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}

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


	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppNode = parent->_parent;

		parent->_parent = subR;

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

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

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;

		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);
		}
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}


private:
  	  int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

		int _Height(Node* root)
		{
			if (root == nullptr)
				return 0;
			/*int leftHeight= _Height(root->_left);
			int rightHeight = _Height(root->_right);*/

			return max(_Height(root->_left), _Height(root->_right)) + 1;
		}

		bool _IsBalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int leftHeight = _Height(root->_left);
			int rightHeight = _Height(root->_right);

			if (abs(leftHeight - rightHeight) >= 2)//不平衡
			{
				cout << root->_kv.first << endl;
				return false;
			}
			
			// 顺便检查一下平衡因子是否正确
			if (rightHeight - leftHeight != root->_bf)
			{
				cout << root->_kv.first << endl;
				return false;
			}

			return _IsBalance(root->_left)
				&& _IsBalance(root->_right);
		}


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

			_InOrder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second << endl;
			_InOrder(root->_right);
		}
private:
	Node* _root = nullptr;
};

void TestAVLTree1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert({ e,e });
		/*cout <<"Insert"<<e<<"->" << t1.IsBalance() << endl;*/
	}

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

void TestAVLTree2()
{
	const int N = 100000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

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

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

	cout << "Insert:" << end2 - begin2 << endl;
	//cout << t.IsBalance() << 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;
}
相关推荐
两个人的幸福online几秒前
记录一次 用php 调用ai用stream返回
开发语言·php
漂流瓶66666627 分钟前
Scala的模式匹配变量类型
开发语言·后端·scala
夏天吃哈密瓜32 分钟前
Scala中的正则表达式01
大数据·开发语言·后端·正则表达式·scala
2401_8337880534 分钟前
Scala的模式匹配(2)
java·开发语言
Lbs_gemini060335 分钟前
C++研发笔记14——C语言程序设计初阶学习笔记12
c语言·开发语言·c++·笔记·学习
ac-er88881 小时前
GD库如何根据颜色生成纯色背景图
开发语言·php
叁散2 小时前
PTA--数据结构预习报告: 考试排名汇总
数据结构
悠悠龙龙2 小时前
框架模块说明 #05 权限管理_03
java·开发语言·spring
开心羊咩咩3 小时前
Idea 2024.3 突然出现点击run 运行没有反应,且没有任何提示。
java·ide·intellij-idea
waterme1onY3 小时前
IDEA中MAVEN的一些设置问题
java·maven·intellij-idea