C++AVL树

目录:

  • AVL树的概念
    • AVL树节点的定义
      • 更新平衡因子
  • AVL树的旋转
    • AVL树的验证
      • AVL的整体实现
    • AVL树的删除
    • AVL树的性能
  • 总结

AVL树的概念

AVL树:二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
特性:
1.它的左右子树都是AVL树
2.左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
C++AVL树_第1张图片
图中我们可以看到平衡因子等于右子树的高度减左子树的高度,比如节点3的平衡因子就是1-2=-1,所以节点3的平衡因子就是-1。

AVL树节点的定义

template<class k , class v>
struct AVLTreeNode
{
	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)//上面的平衡因子受到下面影响,所以初始化为0
	{}
};

更新平衡因子

先按照二叉搜索树的规则将节点插入到AVL树中, 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树 的平衡性。调整的话就是左边插入一个节点,平衡因子减减;右边插入,平衡因子就要加加,但新节点插入后,平衡因子可能存在三种情况:

  1. 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足 AVL树的性质,插入成功。
  2. 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负1,此时以parent为根的树的高度增加,需要继续向上更新。
  3. 如果parent的平衡因子为正负2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理。

AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。
旋转的目的:
一.让这颗子树的高度差不超过1
二.旋转过程在保持它是搜索树
三.更新调整孩子的平衡因子
四.降低这颗子树的高度跟插入前保持一致

根据节点插入位置的不同,AVL树的旋转分为四种:
1.新节点插入较高右子树的右侧—右右:左单旋C++AVL树_第2张图片
通过上面的抽象图我们看到a/b/c是高度为h的AVL树,c插入节点后,导致高度变化为h+1,右边就高了需要往左边压,就会发生左单转。
左单旋的旋转规则是:节点60的左边b调整到节点30的右边,节点30变成节点60的左边,原来节点30是根,现在节点60是根。为什么这样调整呢?这也是根据二叉树的性质来调整的,旋转前,节点30的右子树一定都大于它,节点60的左子树一定都小于节点60,这样调整也合理,旋转完成后,更新节点的平衡因子即可。
代码如下:

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if(subRL)
		subRL->_parent = parent;
		Node* ppNode = parent->_parent;
		subR->_left = parent;
		parent->_parent = subR;
		if (ppNode==nullptr)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
		}
		parent->_bf = subR->_bf = 0;//最后更新平衡因子
	}
  1. 新节点插入较高左子树的左侧—左左:右单旋
    C++AVL树_第3张图片
    通过上面的抽象图我们看到a/b/c是高度为h的AVL树,a插入节点后,导致高度变化为h+1,左边就高了需要往右边压,就会发生右单转。
    右单旋的旋转规则是:节点b变成节点60的左边,节点60变成节点30的右边,原来节点60是根,现在节点30是根,为什么这样调整呢?目的二我们要保证这是一颗搜索数,节点b比节点30大比节点60小,节点60又比节点30大,这样调整也合理,不会破坏这棵树,旋转完成后,更新节点的平衡因子即可。
    代码如下:
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		Node* ppNode = _parent;
		subL->_right = parent;
		parent->_parent = subL;
		if (_root==parent)
		{
			_root = subL;//sybL是根
			_root->_parent = nullptr;		
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
		subL->_bf = parent->_bf = 0;
	}
  1. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
    C++AVL树_第4张图片
    通过上面的抽象图我们看到a/d为高度h的AVL树,b/c为高度h-1的AVL树,假设节点b是新增的一个节点,单旋的时候我们的AVL树结构像是一条直线,比较好控制平衡,现在这棵树更像是一条折现,这种情况就需要进行双旋,也就是通过两次旋转来调整平衡。
    左右旋转的旋转规则是:第一次节点30为轴点进行一个左单旋,节点b变成30的右边,节点30变成节点60的左边,节点60变成子树的根,第二次节点90为轴点进行一个右单旋,节点c变成节点90的左边,节点90变成节点60的右边,节点60变成子树的根,这个根才是我们旋转两次后得到的这颗子树的根。

这种情况复杂一些,思路就是将双旋变成单旋后再旋转,即:先对节点30进行左单旋,然后再对节点90进行右单旋,旋转完成后再考虑平衡因子的更新。双旋的平衡因子更新比较麻烦,又要分三种情况,注意这里是旋转完后平衡因子的更新,旋转完后的图我就不一一画了,我把旋转之前的图一一画出来供大家参考!
第一种:旋转之前,如果是节点b插入,那么节点60的平衡因子就是-1。
C++AVL树_第5张图片

第二种:如果是节点c插入那么节点60的平衡因子就是1。
C++AVL树_第6张图片

第三种:节点60自己就是新增,它的平衡因子就是0。
C++AVL树_第7张图片

代码如下:

	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左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1) // subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) // subLR自己就是新增
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
  1. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
    C++AVL树_第8张图片

思路和左右双旋差不多,原理都是一样的,结合抽象图,新增节点后,也是分三种情况:
第一种:旋转之前,如果是节点b插入,那么节点60的平衡因子就是-1。
第二种:旋转之前,如果是节点c插入,那么节点60的平衡因子就是1。
第三种:节点60自己就是新增,它的平衡因子就是0。
代码如下:

  void RotateRL(Node* parent)//右左双旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(parent->_right);
		RotateL(parent);

		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 if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

AVL树的验证

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

  1. 验证其为二叉搜索树:
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树。
	void Inorder()
	{
		_Inorder(_root);
	}

	void _Inorder(Node* root)//中序的子函数
	{
		if (root == nullptr)
			return;

		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
  1. 验证其为平衡树:
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确。
	bool IsBalance()
	{
		return IsBalance(_root);
	}

	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) <=1
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}
  1. 验证用例
    像这种树型结构我们最好自己动手画AVL树的创建过程并验证代码是否有漏洞。
    常规场景1
    {16, 3, 7, 11, 9, 26, 18, 14, 15}
    特殊场景2
    {4, 2, 6, 1, 3, 5, 15, 7, 16, 14}
    我这里写了一个随机种子来验证,我们知道size_t最大可能开辟的数组尺寸是2^64,这里随机给100000个数:
    C++AVL树_第9张图片

AVL的整体实现

这是我AVLTree.h的代码:

#pragma once
#include 
#include 

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

	int _bf;  

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

template<class K, class V>
struct 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)
			{
				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)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}
	void RotateL(Node* parent)//左旋
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

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


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

			subR->_parent = ppNode;
		}

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

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

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

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

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

			subL->_parent = ppNode;
		}

		subL->_bf = parent->_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左子树新增
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		else if (bf == 1) // subLR右子树新增
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == 0) // subLR自己就是新增
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_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 == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	//中序
	void Inorder()
	{
		_Inorder(_root);
	}
	void _Inorder(Node* root)//中序的子函数
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int lh = Height(root->_left);
		int rh = Height(root->_right);
		return lh > rh ? lh + 1 : rh + 1;
	}

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

	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) <=1
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};

//void TestAVLTree()
//{
//	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//	int a[] = { 8,4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
//	AVLTree t;
//	for (auto e : a)
//	{
//		t.Insert(make_pair(e, e));
//	}
//
//	t.Inorder();
//
//	cout << t.IsBalance() << endl;
//}
void TestAVLTree()
{
	srand(time(0));
	const size_t N = 100000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}
	//t.Inorder();
	cout << t.IsBalance() << endl;
}

运行一下:
C++AVL树_第10张图片
bool返回值是1,说明我们的测试用例没有问题!

AVL树的删除

AVL树的插入有了,那么删除呢?有的伙伴可能会问,因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后要对平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

AVL树的性能

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

总结

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

  1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为subR
    当subR的平衡因子为1时,执行左单旋。
    当subR的平衡因子为-1时,执行右左双旋。
  2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
    当subL的平衡因子为-1是,执行右单旋。
    当subL的平衡因子为1时,执行左右双旋。
    旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
    这就是AVL树,下面我们将继续学习红黑树,记得三连哦,小佳会更新更多干货!!!

你可能感兴趣的:(c++,开发语言)