AVL - 自平衡二叉树 - 详解

1.AVL

说到AVL,我们就必须先要了解一下BST
Lantian的BST总结 在了解了有关BST的性质之后,我们现在就明白了 因为在我们的插入的节点有序的情况下,我们的BST会出现偏树的情况,这会导致我们的ASL(平均查找长度)大大增加从而降低我们的查找效率 因此,我们就需要一种BST的优化版本取克服这种输入造成的弊端(现在证明,在平均情况下,出现偏树的概率大致在45.6%),所以说,我们的优化是非常有必要的 因此我们需要对传统的BST进行一种平衡处理 在这里,历史上出现了大批的优秀的自平衡二叉查找树,相对有splay,treap,AVL,SBT,RBT,很多很多,这其中最经典也是最早发明的自平衡二叉树就是我们心在要说明的AVL,也是现阶段我们的数据结构课程中选取讲解的自平衡二叉树

PS:
Lantian的SBT讲解 本人之前曾经讲解过一种有中国大神陈启峰发明的自平衡二叉树SBT,目前来说,SBT相对于算法竞赛来说非常好实现,但是与优化后的AVL下昂比的话,我们的SBT(Size Balance Tree)因为是节点的数量域作为平衡因子,这里的话,我们牵扯到对于树的修改为虎的探讨的话,SBT自始至终都是要从底向上一直到根来进行维护的,但是AVL的平衡因子的性质决定了AVL不需要一直维护到根,我们只需要维护到当前的第一个
最小不平衡子树就可以了,但是毋庸置疑,SBT相对于AVL来说非常的好写,但是我在对AVL的平衡因子的优化之后,发现其实两者的AVL,SBT的维护效率相当,并不存在明显的难易之说

AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文《An algorithm for the organization of information》中发表了它。也是目前已知的最早的自平衡二叉树

2.AVL’s ADT

AVL的抽象数据类型基于BST 先给出BST的定义: 1.BST是一颗空树 2.BST不是空树并且左子树的节点的大小都小于根节点,右子树节点的大小都大于根节点(在没有进行扩展的AVL平衡二叉树中,我们默认不存在键值相同的节点)

AVL的ADT基于BST的ADT的定义如下:

ADT
{
    数据对象:节点
    数据关系:节点的指向关系,套用BST的定义
    数据操作:
        1.中序遍历
        2.插入
        3.删除
        4.旋转维护
        5.查找
}

主要的数据操作如上: 在这里我先来讲解一下对于AVL树的操作的时间复杂度:

1.中序遍历:

这里我们强调中序遍历的原因就是,对于BST/AVL来说,我们定义保证了我们的中序序列一定构成一个定义的有序序列,所以说我们的中序遍历可以获取我们的当前的AVL树中有序的序列值 时间复杂度:O(number_point)

2.插入:

插入节点,对于我们考虑的时候需要考虑两种情况 首先,如果插入之后我们不需要对树进行维护,那么时间复杂度同查找就是O(logn),这是我么的平衡二叉树的性质保证的 但是对于如果插入后节点我们需要对树进行维护的时候,我们除了O(logn)的查找操作之外,我们还需要O(c)的常数时间的旋转维护操作,乳沟我们之简简单单考虑时间复杂度的话,我们的市价复杂度都是O(logn)

3.删除:

首先,如果我们删除之后,树的平衡因子保证了树的平衡性,那么我们其实也只需要O(logn)的时间复杂度 但是一旦我们需要进行维护,的话,时间复杂度无非就是O(logn+C)没有什么本质的影响

4.查找:

对于AVL树的查找,因为平衡树已经保证了我们的树基本上额深度和O(logn)是同一个数量级上的,所以说我们的查找你的时间复杂度最坏的情况下也就是O(logn)

5.旋转维护:

对于AVL树的旋转维护,因为只是牵扯到指针的修改,所以在全局范围内来看的话,时间复杂度还是简单的O(c)在大输入的情况下,我们完全可以对市价耗费进行忽略(无限接近常数阶),我们之后会有对旋转操作详细的讲解

PS: 在接下来的AVL树的讲解过程中,我们可能会要一个小的要点: 我们需要记住,对于平衡二叉树AVL(这里好像仅限于AVL)的平衡维护操作实际上就是尽可能保证我们的树高不变

3.Point – ADT

我们的AVL树的节点是要非常的小心的,这里我们对接点有如下的ADT的定义:

template<typename T>
class point
{
	public:
		point()
		{
			left=right=NULL;
			height=0;
			freq=1;
		}
		point(T x)
		{
			dt=x;
			left=right=NULL;
			height=0;
			freq=1;
		}
		~point()   //方便测试的析构函数 
		{
			cout<<"节点"<<dt<<"被删除!"<<endl; 
		}
		inline void set_data(T x)
		{
			dt=x;
		} 
		inline T get_data()
		{
			return dt;
		}
		friend void set_height(point<T>* p,int ht)
		{
			p->height=ht;
		}
		friend int get_height(point<T>* p)
		{
			if(p == NULL) return -1; 
			else return p->height;
		}
		inline void set_freq()
		{
			freq++;
		}
		point* left;
		point* right;
	protected:
		T dt;    //数据 
		int height;   //树高 
		int freq;   //频度,扩展的AVL树的功能单元 
};

这是我的代码上的对于AVL的point的描述,这里我们需要注意到,对于AVL树的平衡因子的定义 众所周知,AVL树的书写是非常的困难的,困难的点就在于我们在平衡维护的时候,需要考虑的要点太多,不容易思考,原本的AVL树的定义的平衡因子的内容是左右子树的高度差,在这里,我将平衡因子该位当前节点的高度(距离叶子节点),这里我只需要添加set_height和get_height(定义这两个接口的必要性原因在与如果节点为空的话,我们必须要对其进行取高操作)两个几口就可以简单的取代之前的AVL的困难的树的平衡因子的定义了,非常的简便

对于平衡因子定义: 树高: 1.叶子节点的树高是0 2.空树的树高是-1 3.其余点的树高由以上的内容类推

4.Rotation – Maintain操作

对于一个平衡二叉树来说,我们我们在了解了BST之后,我们唯一需要攻破的就是这个Rotation操作了 在广泛的定义上,我们将AVL等平衡树的维护操作采用了四种分类的旋转来代替 参考SBT的资料来看看:
《AVL - 自平衡二叉树 - 详解》

我们先来看看旋转操作: 左旋右旋的代码如下(封装了对平衡因子的修改):

template<typename T>
void AVL<T>::left_rotation(point<T>*& p)
{
	point<T>* k=p->right;
	p->right=k->left;
	k->left=p;
	set_height(p,max(get_height(p->left),get_height(p->right))+1);
	set_height(k,max(get_height(k->left),get_height(k->right))+1);
	p=k;
}

template<typename T>
void AVL<T>::right_rotation(point<T>*& p)
{
	point<T>* k=p->left;
	p->left=k->right;
	k->right=p;
	set_height(p,max(get_height(p->left),get_height(p->right))+1);
	set_height(k,max(get_height(k->left),get_height(k->right))+1);
	p=k;
}

我们可以看到,对于AVL的核心旋转操作来说,无非就是对指针的修改而已,算法非常的简明高效

1.Case 1:左左

《AVL - 自平衡二叉树 - 详解》

单旋转是针对于左左和右右这两种情况的解决方案,这两种情况是对称的,只要解决了左左这种情况,右右就很好办了。图3是左左情况的解决方案,节点k2不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的左子树X子树,所以属于左左情况。

为使树恢复平衡,我们把k2(此处可能是作者笔误,应该为k1)变成这棵树的根节点,因为k2大于k1,把k2置于k1的右子树上,而原本在k1右子树的Y大于k1,小于k2,就把Y置于k2的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。

2.Case 2:左右

《AVL - 自平衡二叉树 - 详解》

对于左右的情况,我们对于树的左节点为根进行左旋操作,然后对跟进行右旋操作,最后得到根子树就会被自动的修改完成
对于右右和右左的情况和上面两种是镜像的,没有必要赘语了

5.模板类代码:

#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#include"cmath"
#define N 100005

using namespace std;

template<typename T>
class point
{
	public:
		point()
		{
			left=right=NULL;
			height=0;
			freq=1;
		}
		point(T x)
		{
			dt=x;
			left=right=NULL;
			height=0;
			freq=1;
		}
		~point()   //方便测试的析构函数 
		{
			cout<<"节点"<<dt<<"被删除!"<<endl; 
		}
		inline void set_data(T x)
		{
			dt=x;
		} 
		inline T get_data()
		{
			return dt;
		}
		friend void set_height(point<T>* p,int ht)
		{
			p->height=ht;
		}
		friend int get_height(point<T>* p)
		{
			if(p == NULL) return -1; 
			else return p->height;
		}
		inline void set_freq()
		{
			freq++;
		}
		point* left;
		point* right;
	protected:
		T dt;    //数据 
		int height;   //树高 
		int freq;   //频度,扩展的AVL树的功能单元 
};

template<typename T>
class AVL
{
	public:
		AVL()
		{
			root=NULL;
			number_point=0;
		}
		~AVL()
		{
			clear(root);
		}
		void left_rotation(point<T>*&);
		void right_rotation(point<T>*&); 
		void mid_order(point<T>*); 
		void clear(point<T>*);   
		point<T>* find(point<T>*,T);
		void insert(point<T>*&,T);     //支持针对子树的操作,灵活性更高 
		bool do_insert(T);
		bool delete_(point<T>*&,T);   
		void do_delete_(point<T>*&,point<T>*); 
		inline point<T>* get_father(point<T>* p)   //辅助函数,获取父亲 
		{
			point<T>* k=root;
			point<T>* help=root;
			while(k != p)
			{
				help=k;
				if(k==NULL) return NULL;
				else if(k->get_data() > p->get_data()) k=k->left;
				else if(k->get_data() < p->get_data()) k=k->right;
			} 
			return help;
		}
		void test_print(point<T>*);
		inline point<T>* return_root()
		{
			return root;
		}
		inline point<T>*& returnroot()
		{
			return root;
		}
	protected:
		point<T>* root;
		int number_point;    
};

template<typename T>
void AVL<T>::test_print(point<T>* p)   //层序 
{
	point<T>* queue[N];
	int head=1;
	int tail=2;
	if(p==NULL) return ;   //空树 
	queue[1]=p;
	while(head!=tail)
	{
		if(queue[head]->left!=NULL) queue[tail++]=queue[head]->left;
		if(queue[head]->right!=NULL) queue[tail++]=queue[head]->right;
		head++;
	}
	for(int i=1;i<tail;i++) cout<<queue[i]->get_data()<<' ';
	cout<<endl; 
}

template<typename T>
void AVL<T>::mid_order(point<T>* p)
{
	if(p==NULL) return ;
	else
	{
		AVL<T>::mid_order(p->left);
		cout<<p->get_data()<<' ';
		AVL<T>::mid_order(p->right);
	}
}

template<typename T>
void AVL<T>::clear(point<T>* p)
{
	if(p==NULL) return ;
	else
	{
		AVL<T>::clear(p->left);
		AVL<T>::clear(p->right);
		delete p;
	}
}

template<typename T>
void AVL<T>::left_rotation(point<T>*& p)
{
	point<T>* k=p->right;
	p->right=k->left;
	k->left=p;
	set_height(p,max(get_height(p->left),get_height(p->right))+1);
	set_height(k,max(get_height(k->left),get_height(k->right))+1);
	p=k;
}

template<typename T>
void AVL<T>::right_rotation(point<T>*& p)
{
	point<T>* k=p->left;
	p->left=k->right;
	k->right=p;
	set_height(p,max(get_height(p->left),get_height(p->right))+1);
	set_height(k,max(get_height(k->left),get_height(k->right))+1);
	p=k;
}

template<typename T>
point<T>* AVL<T>::find(point<T>* p,T x)
{
	point<T>* k=NULL;
	if(p==NULL) return NULL;
	else if(x > p->get_data()) 
	{
		if((k=find(p->right,x))==NULL) return NULL;
		else return k;
	}
	else if(x < p->get_data())
	{
		if((k=find(p->left,x))==NULL) return NULL;
		else return k;
	}
	else return p;
}

template<typename T>
bool AVL<T>::do_insert(T x)
{
	point<T>* k=NULL;
	k=find(root,x);
	AVL<T>::insert(root,x);
	if(k==NULL) 
	{
		number_point++;
		return true;
	}
	else return false;
}

template<typename T>
void AVL<T>::insert(point<T>*& p,T x)
{
	if(p==NULL)
	{
		p=new point<T>(x);
		return ;
	}
	else if(p->get_data() > x)
	{
		insert(p->left,x);
		if(get_height(p->left) - get_height(p->right) == 2)
		{
			if(x > p->left->get_data())
			{
				AVL<T>::left_rotation(p->left);
				AVL<T>::right_rotation(p); 
			}
			else
			{
				AVL<T>::right_rotation(p); 
			}
		}
	}
	else if(p->get_data() < x)
	{
		insert(p->right,x);
		if(get_height(p->left) - get_height(p->right) == -2)
		{
			if(x > p->right->get_data())
			{
				AVL<T>::left_rotation(p);
			}
			else
			{
				AVL<T>::right_rotation(p->right);
				AVL<T>::left_rotation(p);
			}
		}
	}
	else 
	{
		p->set_freq();
	}
	set_height(p,max(get_height(p->left),get_height(p->right))+1);
}

template<typename T> 
bool AVL<T>::delete_(point<T>*& p,T x)
{
	point<T>* k=find(p,x);
	if(k->get_data() == x)
	{
		number_point--; 
		AVL<T>::do_delete_(p,k);
		return true;
	}
	else return false;   //删除失败 
}

template<typename T>
void AVL<T>::do_delete_(point<T>*& p,point<T>* k)
{
	if(p->get_data() > k->get_data())
	{
	    AVL<T>::do_delete_(p->left,k);
	    if(get_height(p->left) - get_height(p->right) == -2)
	    {
	    	if(p->right->left!=NULL && get_height(p->right->left)>get_height(p->right->right) )
	        {
	            AVL<T>::right_rotation(p->right);
				AVL<T>::left_rotation(p); 	 	
			}
			else
			{
				AVL<T>::left_rotation(p);
			}
		}
	}
	else if(p->get_data() < k->get_data())
	{
		AVL<T>::do_delete_(p->right,k);
		if(get_height(p->left) - get_height(p->right) == 2)
		{
			if(p->left->right!=NULL && get_height(p->left->right)>get_height(p->left->left))
			{
				AVL<T>::left_rotation(p->left);
				AVL<T>::right_rotation(p); 
			}
			else
			{
				AVL<T>::right_rotation(p);
			}
		}
	}
	else
	{
		if(p->left != NULL && p->right != NULL)
		{
			point<T>* temp=p->right;
			while(temp->left!=NULL) temp=temp->left;
			p->set_data(temp->get_data());
			AVL<T>::do_delete_(p->right,temp);
			if(get_height(p->left) - get_height(p->right) == 2)   //选择删除右子树的替代节点 
			{
				if(p->left->right!=NULL && get_height(p->left->right)>get_height(p->left->left))
		    	{
	    			AVL<T>::left_rotation(p->left);
	    			AVL<T>::right_rotation(p); 
	    		}
	    		else
    			{
    				AVL<T>::right_rotation(p);
	    		}
			}
		}
		else
		{
			point<T>* w=p;
			if(p->left == NULL)
			{
				p=p->right;
				delete w;
			}
			else
			{
				p=p->left;
				delete w;
			}
		}
	}
	if(p!=NULL) set_height(p,max(get_height(p->left),get_height(p->right))+1);   //回溯维护高度域 
}

int main()  //测试入口 
{
	//整形测试
	/*
	AVL<int> test; 
	test.test_print(test.return_root());
	cout<<"节点个数"<<endl;
	int t;
	cin>>t;
	for(int i=1;i<=t;i++)
	{
		int y;
		cin>>y;
		bool judge=test.do_insert(y);
		if(judge==true) cout<<"节点插入成功"<<endl;
		else cout<<"尝试插入已经存在的同键节点,节点频度增加"<<endl;
		test.test_print(test.return_root());cout<<endl;
	}
	test.mid_order(test.return_root());
	cout<<endl;
	test.test_print(test.return_root());
	
	test.delete_(test.returnroot(),37);
	test.test_print(test.return_root());cout<<endl;
	test.delete_(test.returnroot(),53);
	test.test_print(test.return_root());cout<<endl;
	test.delete_(test.returnroot(),93);
	test.test_print(test.return_root());cout<<endl;*/
    //浮点型测试
	/*AVL<double> test;
	int t;
	cout<<"节点个数"<<endl;
	cin>>t;
	while(t--)
	{
		double x;
		cin>>x;
		bool judge=test.do_insert(x);
		if(judge == true) cout<<"节点插入成功"<<endl;
		else cout<<"节点插入失败"<<endl;
		test.test_print(test.returnroot());cout<<endl;
	}*/
	//对于其他的自定义类型,我们还需要自己对于相应的 > , < ,=运算符进行运算符重载就ok,实现一个比较大小的接口就ok 
	return 0;
}
    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/ltyqljhwcm/article/details/53305753
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞