【c++】AVL树详解

AVL树是又叫平衡二叉搜索树,但是它不是完全平衡,只是近似平衡(高度平衡)。什么叫完全平衡?想象一下完全二叉树。

根据名字’二叉搜索树‘,我们可以知道它的一些性质:

1. 每个节点都有一个作为搜索依据的关键码(key),所有节点的关键码互不相同。

2. 左子树上所有节点的关键码(key)都小于根节点的关键码(key)。

3. 右子树上所有节点的关键码(key)都大于根节点的关键码(key)。

除此之外,它还具有一些它独特的性质来维持它的高度平衡。

1. 左子树和右子树的高度之差的绝对值不超过1

2. 树中的每个左子树和右子树都是AVL树

3. 每个节点都有一个平衡因子(balance factor–bf),任一节点的平衡因子是-1,0,1。(每个节点的平衡因子等于右子树的高度减去左子树

的高度)


AVL树有三个值,一个_key(关键码),一个_value(值,目前不要关心这个value是什么),一个_bf(平衡因子)。三个指针,左

孩子指针,右孩子指针,父亲指针。由此我们便得出了AVL树的结构:

template<typename K,typename V>
struct AVLTreeNode
{
	K _key;
	V _value;

	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf;

	AVLTreeNode(const K& key, const V& value)
		:_key(key)
		,_value(value)
		,_bf(0)
		, _left(NULL)
		, _right(NULL)
		, _parent(NULL)
	{}
};

搞懂AVL树的结构并不难,难点在于如何去维护这个结构,并用代码写出来。当我们对一棵AVL树插入一个结点的时候,我们很容易
就破坏了它高度平衡的结构(从前文我们可
以知道,我们可以拿平衡因子来判断一棵树是否平衡,当一个结点的平衡因子bf 等于-2
或者2时,这个树便不再平衡。)所以我们需
要做一些工作去调整插入新结点后的AVL
树。

对下图中出现的符号做一些备注:_bf – – – – 平衡因子
                                                     a,b,c,d – – – – 子树,若一个为空,都为空。若一个不为空,则都不为空,并且都平衡且满足AVL树。     
                                                     parent – – – – 祖先结点,因为我们不确定根结点在哪里,所以用parent统一概括祖先结点。






左单旋,对应插入情况如图所示,当在子树c上插入一个新结点。此时子树c 的高度变为h+1,子树b的高度为h。结点5的bf变为
1,结点3的bf变为2。此树不再平衡。

《【c++】AVL树详解》

什么叫左单旋?让结点3和结点5以及它们之间的连线为基准,进行逆时针旋转。具体操作为,将结点5的左子树b变为结点3的右子
树,将结点3以及其子树变为结点5的左子树。
完成一次左单旋。结果我们可以看到结点3的_bf与结点5的_bf都变为0,AVL树变为平衡。
《【c++】AVL树详解》



右单旋,对应的新结点插入位置如图所示,当在子树a的下面入一个结点时,结点3的_bf左子树高度为h+1,右子树高度为h,平衡
因子_bf变为-1,。结点5的左子树高度变为h+2,右子树高度为h,平衡因子_bf变为-2。AVL树不再平衡。

《【c++】AVL树详解》

什么叫右单旋?同上,以结点5和结点3以及它们之间的连线为基准,进行顺时针旋转。具体操作为,将结点5的左子树变为结点3的
右子树,将结点3以及其子树变为结点5的左子树,将parent指向结点3。完成一次右单旋,完成后我们可以看到结点3与结点5的平衡
因子_bf变为0,AVL树恢复平衡。
《【c++】AVL树详解》

左右双旋,当我们理解了左单旋与右单旋后,理解左右双旋就简单的多了。其插入情况如图所示(PS:插到子树b上与插到子树c
会导致结点4的平衡因子_bf不同,属于是不同的情况。这里以插到子树b下为例,另一种情况与其类似。),当在子树b下插入一个
结点时,结点4的平衡因子_bf变为-1,结点3的平衡因子_bf变为1,结点5的平衡因子_bf变为-2。AVL树不再平衡。
《【c++】AVL树详解》
什么么叫左右双旋?很简单啦!先左单旋,再右单旋!不过两次的对象不同。先对结点3和结点4进行左单旋。
《【c++】AVL树详解》

再对结点5与结点4进行右单旋。
《【c++】AVL树详解》

最后结点4的平衡因子_bf变为0,结点3的平衡因子_bf变为0,j结点5的平衡因子_bf变为1。AVL树恢复平衡。
右左单旋,其插入情况如图所示(PS:插到子树b上与插到子树c会导致结点4的平衡因子_bf不同,属于是不同的情况。这里以插
到子树c下为例,另一种情况与其类似。),当在子树b下插入一个结点时,结点3的平衡因子_bf变为1,结点5的平衡因子_bf变
为-1,结点4的平衡因子_bf变为2。AVL树不再平衡。
《【c++】AVL树详解》

什么叫右左双旋呢?哈哈,相信聪明的你已经知道了。先对结点5与结点3进行右单旋。

《【c++】AVL树详解》

再对结点3与结点4进行左单旋。
《【c++】AVL树详解》
旋转完成后结点5的平衡因子_bf变为0,结点4的平衡因子_bf变为-1,结点3的平衡因子_bf变为0。AVL树恢复平衡。
以上4中旋转就囊括所有的插入情况与调整情况了,掌握这些,写代码就简单了。






先看左单旋代码,将上面左单旋的图类比成下面这副。结点3类比为结点parent,结点5类比结点subR,子树b类比为subRL,
ppNode为祖先结点,即parent的parent。
《【c++】AVL树详解》

      

代码即为:

void RotateL(Node* parent)
{
	Node* subR = parent->_right;//subR为parent结点的左孩子。
	Node* subRL = subR->_left;//subRL为subR的左孩子
	parent->_right = subRL;// 将subRL变为parent的右子树

	if (subRL)//若sunRL不为空,则设置它的父亲指针,否则则不设置。
	{
		subRL->_parent = parent;
	}
	Node* ppNode = parent->_parent;//记录parent的父亲结点,即祖先结点

	//将parent变为subR的左子树,并设置父亲指针。
	subR->_left = parent;
	parent->_parent = subR;

	//设置ppNode,若不为空,则将其指向subR
	if (ppNode)
	{
		//判断parent是ppNode的左孩子还是右孩子,以方便设置subR的父亲结点。
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;
		subR->_parent = ppNode;
	}
	else//若其为空,则代表parent原来为根节点,则旋转后subR变为根节点。
	{
		subR->_parent = NULL;
		_root = subR;
	}
	//设置平衡因子,置0。
	subR->_bf = parent->_bf = 0;
}

右单旋代码,类比图如下,结点5类比为结点parent,结点3类比为结点subL,子树b类比为subLR。ppNode为祖先结点。
《【c++】AVL树详解》

代码即为:

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 (ppNode)
	{
		if (ppNode->_left == parent)
			ppNode->_left = subL;
		else
			ppNode->_right = subL;
		subL->_parent = ppNode;
	}
	else
	{
		subL->_parent = NULL;
		_root = subL;
	}
	subL->_bf = parent->_bf = 0;
}



左右单旋代码,类比图如下,类比规则与上相同。从上面两段代码中可以看到,我在最后对平衡因子_bf进行了调整。细心的同学可
以注意到,我在上面说过新结点插入子树b下与插入子树c下是两种不同的情况。这里得到了体现。插入的不同影响到了最后平衡因
子的调整的不同。


①插入子树b下

《【c++】AVL树详解》

②插入子树c下
《【c++】AVL树详解》

仔细观察上面两幅图,我们会看到,无论新结点插入到哪里,
1、结点subLR的左子树b都变成了结点subL的右子树;
2、结点subRL的右子树c都变成了结点parent的左子树;
3、平衡因子的计算公式为,_bf = 右子树高度-左子树高度;
通过这三点,我们可以概括一下了,
①旋转前,新结点插到子树b下,_bf(subLR) = Hc(h-1) – Hb(h-1+1) = -1。旋转后,_bf(subL) = Hb(h-1+1) – Ha(h) = 0,_bf(parent) = 
Hd(h) – Hc(h-1) = 1,_bf(sunLR) = 0。
②旋转前,新结点插到子树c下,_bf(subLR) = Hc(h-1+1) – Hb(h-1) = 1。旋转后,_bf(subL) = Hb(h-1) – Ha(h) = -1,_bf(parent) = 
Hd(h) – Hc(h-1+1) = 0,_bf(sunLR) = 0。
③为什么会出现第三种情况呢????不要忘了子树a、b、c、d都为空的情况。当其都为空时,ppNode为空,parent为根节点。此
时subLR就变成了插入的新结点。示意图如下。旋转前_bf(sunRL) = 0,旋转后,_bf(suBL) = 0,_bf(parent) = 0。
《【c++】AVL树详解》

以上三种情况概括下来就是:
《【c++】AVL树详解》





到这里,就可以写代码了,因为已经写出了左单旋与右单旋代码,所以在双旋时我们可以复用这些代码。

void RotateLR(Node* parent)
{
	Node* SubL = parent->_left;
	Node* SubLR = SubL->_right;
	int bf = SubLR->_bf;//记录旋转前subLR的平衡因子
	//左右双旋
	RotateL(parent->_left);//  复用代码,先左单旋
	RotateR(parent);//  复用代码,再右单旋

	//三种情况
	if (bf == 0)
	{
		parent->_bf = SubL->_bf = 0;
		SubLR->_bf = 0;
	}
	else if (bf == -1)
	{
		SubL->_bf = 0;
		parent->_bf = 1;
		SubLR->_bf = 0;
	}
	else
	{
		SubL->_bf = 1;
		parent->_bf = 0;
		SubLR->_bf = 0;
	}

}


右左双旋与左右双旋就一样了,同样的类比,同样的三种情况①子树b②子树c③子树a、b、c、d都为空。这里只给出插入子树b下的

类比图。

《【c++】AVL树详解》

三种情况平衡因子的总结:
《【c++】AVL树详解》



右左旋代码:

void RotateRL(Node* parent)
{
	Node* SubR = parent->_right;
	Node* SubRL = SubR->_left;
	int bf = SubRL->_bf;//记录旋转前subRL的平衡因子
	//
	RotateR(SubR);//复用代码,先右旋
	RotateL(parent);//复用代码,后左旋

	//调整平衡因子
	//三种情况
	if (bf == 0)
	{
		SubR->_bf = parent->_bf = 0;
	}
	else if (bf == 1)
	{
		SubR->_bf = 0;
		parent->_bf = -1;
	}
	else
	{
		SubR->_bf = 1;
		parent->_bf = 0;
	}
	SubRL->_bf = 0;
}

以上,我们就已经讲完了它的调整算法。
AVL树的核心算法就是当AVL树平衡被破坏时,如何去调整并让它恢复平衡。这也是整个AVL树知识的重点与难点,面试时极大可能
性被问到,如果你很完美的把它答了上来,恭
喜你,面试官已经给你加分了。所以赶紧搞定它吧 !


思考:当我们已经构建好了一棵AVL树,我们如何去检查它是否正确呢?(我给出代码,大家自己研究)


//递归检查AVL树是或否正确。
bool _IsAVLTree(Node *root, int &Height)
{
	if (root == NULL)
	{
		Height = 0;
		return true;
	}

	//检查左子树
	int leftHeight = 0;
	if (_IsAVLTree(root->_left, leftHeight) == false)
	{
		return false;
	}
	//检查右子树
	int rightHeight = 0;
	if (_IsAVLTree(root->_right, rightHeight) == false)
	{
		return false;
	}
	//计算高度
	Height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

	//检查当前结点
	return abs(leftHeight - rightHeight) < 2;
}





完成的AVL树代码:


头文件 AVLTree.h

#pragma once
#include<iostream>
using namespace std;

template<typename K,typename V>
struct AVLTreeNode
{
	K _key;
	V _value;

	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	int _bf;

	AVLTreeNode(const K& key, const V& value)
		:_key(key)
		,_value(value)
		,_bf(0)
		, _left(NULL)
		, _right(NULL)
		, _parent(NULL)
	{}
};

template<typename K, typename V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(NULL)
	{}
	bool Insert(const K& k, const V& v)
	{
		if (_root == NULL)
		{
			_root = new Node(k, v);
			return true;
		}
		Node* cur = _root;
		Node* parent = NULL;
		while (cur)
		{
			if (k > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (k < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}


		cur = new Node(k, v);
		if (k > parent->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//调整平衡因子
		while (parent)
		{
			//如果cur左右不为空且平衡因子为0,则parent的平衡因子不用改变。
			if (cur->_bf == 0&&cur->_left!= NULL&&cur->_right!= NULL)
			{
				break;
			}

			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			if (parent->_bf == 2 || parent->_bf == -2)
			{
				break;
			}
			cur = parent;
			parent = cur->_parent;
		}

		if (parent == NULL)
		{
			return true;
		}
		//旋转
		if (parent->_bf == 2)
		{
			if (cur->_bf == 1)
			{
				RotateL(parent);
			}
			else if (cur->_bf == -1)
			{
				RotateRL(parent);
			}
		}
		else
		{
			if (cur->_bf == -1)
			{
				RotateR(parent);
			}
			else if (cur->_bf == 1)
			{
				RotateLR(parent);
			}
		}
	}

	//判断一棵树是否是平衡二叉树
	bool IsAVLTree()
	{
		int Height = 0;
		return _IsAVLTree(_root,Height);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
protected:
	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 (ppNode)
		{
			if (ppNode->_left == parent)
				ppNode->_left = subL;
			else
				ppNode->_right = subL;
			subL->_parent = ppNode;
		}
		else
		{
			subL->_parent = NULL;
			_root = subL;
		}
		subL->_bf = parent->_bf = 0;
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;//subR为parent结点的左孩子。
		Node* subRL = subR->_left;//subRL为subR的左孩子
		parent->_right = subRL;// 将subRL变为parent的右子树

		if (subRL)//若sunRL不为空,则设置它的父亲指针,否则则不设置。
		{
			subRL->_parent = parent;
		}

		Node* ppNode = parent->_parent;//记录parent的父亲结点,即祖先结点

		//将parent变为subR的左子树,并设置父亲指针。
		subR->_left = parent;
		parent->_parent = subR;

		//设置ppNode,若不为空,则将其指向subR
		if (ppNode)
		{
			//判断parent是ppNode的左孩子还是右孩子,以方便设置subR的父亲结点。
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
			subR->_parent = ppNode;
		}
		else//若其为空,则代表parent原来为根节点,则旋转后subR变为根节点。
		{
			subR->_parent = NULL;
			_root = subR;
		}
		//设置平衡因子,置0。
		subR->_bf = parent->_bf = 0;
	}
	void RotateRL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		int bf = SubRL->_bf;//记录旋转前subRL的平衡因子
		//
		RotateR(SubR);//
		RotateL(parent);

		//调整平衡因子
		//三种情况
		if (bf == 0)
		{
			SubR->_bf = parent->_bf = 0;
		}
		else if (bf == 1)
		{
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else
		{
			SubR->_bf = 1;
			parent->_bf = 0;
		}
		SubRL->_bf = 0;
	}
	void RotateLR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;//记录旋转前subLR的平衡因子
		//左右双旋
		RotateL(parent->_left);//  先左单旋
		RotateR(parent);//  再右单旋
		
		//三种情况
		if (bf == 0)
		{
			parent->_bf = SubL->_bf = 0;
			SubLR->_bf = 0;
		}
		else if (bf == -1)
		{
			SubL->_bf = 0;
			parent->_bf = 1;
			SubLR->_bf = 0;
		}
		else
		{
			SubL->_bf = 1;
			parent->_bf = 0;
			SubLR->_bf = 0;
		}
		
	}
	void _InOrder(Node* root)
	{
		if (root == NULL)
		{
			return;
		}
		_InOrder(root->_left);

		cout << root->_key << " ";

		_InOrder(root->_right);
	}
	int _Height(Node* root)
	{
		if (root == NULL)
		{
			return 0;
		}
		int left = _Height(root->_left) + 1;
		int right = _Height(root->_right) + 1;
		
		return left > right ? left : right;
	}
	bool _IsAVLTree(Node *root, int &Height)
	{
		if (root == NULL)
		{
			Height = 0;
			return true;
		}

		int leftHeight = 0;
		if (_IsAVLTree(root->_left, leftHeight) == false)
		{
			return false;
		}
		int rightHeight = 0;
	    if (_IsAVLTree(root->_right, rightHeight) == false)
		{
			return false;
		}

		Height = leftHeight > rightHeight ? leftHeight+1 : rightHeight+1;

		return abs(leftHeight - rightHeight) < 2;
	}
protected:
	Node* _root;
};

void Test()
{
	AVLTree<int, int> tree;
	int a[9] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	//int a[3] = { 3, 14, 2 };
	for (int i = 0; i < 9; i++)
	{
		tree.Insert(a[i], i);
	}
	tree.InOrder();
	cout << "IsAVLTree?" << tree.IsAVLTree() << endl;

	int b[10] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> tree1;
	for (int i = 0; i < 10; i++)
	{
		tree1.Insert(b[i], i);
	}
	tree1.InOrder();
	cout <<"IsAVLTree?"<< tree1.IsAVLTree() << endl;


}




test.cpp

#include"AVLTree.h"

int main()
{
	Test();
	system("pause");
	return 0;
}
    原文作者:AVL树
    原文地址: https://blog.csdn.net/llzk_/article/details/52967146
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞