搜索结构之平衡树

AVL树是一种高度平衡的二叉搜索树,由Adelson-Velskii和Landis于1962年提出。其特点是左右子树高度差不超过1,保证了查找效率为O(logN)。平衡因子是判断树平衡的关键,取值为-1, 0, 1。插入和删除节点可能导致不平衡,此时通过左旋、右旋以及左右/右左双旋进行调整。本文详细介绍了四种旋转操作,并给出了插入节点的伪代码实现。" 81503296,7938924,Android应用上下文(Context)创建详解,"['Android开发', '应用程序上下文', 'Context']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AVL tree:称为高度平衡的二叉搜索树(左右子树的高度差不超过1),是1962年有俄罗斯的数学家G.M.Adel'son-Vel'skii和E.M.Landis提出来的。它能保持二叉树的高度平衡,尽量降低二叉树的高度,减少树的平均搜索长度。

AVL树的性质:  1. 左子树和右子树的高度之差的绝对值不超过1
                         2. 树中的每个左子树和右子树都是AVL树
                        3. 每个节点都有一个平衡因子(balance factor--bf),任一节点的平衡因子是-1,0,1。(每个节点的平衡因子等于右子树                                  高度减去左子树的高度)

AVL树是在二叉搜索树(每个节点的左子树小于它的值,右子树大于它的值)的基础上建立出来的,我们都知道二叉搜索树增删查的时间复杂度为O(N)(最坏情况:每个节点只有左子树或者右子树),例如:

这种极端的情况下树是高度不平衡的。在二叉搜索树的基础上,我们如果使左右子树的高度差不超过1,即达到高度平衡的状态,这时候增删查的时间复杂度就达到了O(logN)(注:在这里我所提到的所有logN是以2为底数的),那么这样一颗高效的树结构我们应该怎么去实现呢?我们究竟怎样控制它才能使它在插入删除的时候,继续保持高度平衡的状态呢?这是构建AVL树的关键点。

为了让它保持高度平衡的特性,我们引入了平衡因子,每个节点的平衡因子只可能有三个取值-1(表示右子树高度-左子树高度=-1,即左子树的的高度比右子树的高度高1),0(左子树和右子树的高度相同),1(右子树比左子树高1);每次插入/删除一个节点,我们都需要向上更新一下新增节点/删除节点的祖先的平衡因子,一旦发现某个祖先的平衡因子变为2或者-2,我们就需要进行旋转来使这棵树保持AVL树的特性。

那么,问题又来了,我们需要怎样去旋转呢?

我总结了下面几种情况,我们先来考虑一下几种比较简单情况下的旋转(以向树中插入一个节点为例子):

  1.黄色方框表示我们要插入的元素有可能放的位置

可以看出如果我们要插入的元素的值小于10,那么这棵树依然是一颗平衡树,不需要进行旋转;假设现在我们想插入30这个元素,这个元素应该放在c这个位置处,这时候我们向上更新平衡因子,10的平衡因子达到了2,这时候我们需要进行旋转了。旋转过程如下:

这样是不是就成功了,这种旋转我们称为左单旋

2.

此时,我们我们如果插入的元素比30大那没有问题,如果我们想插入一个元素10呢?30的平衡因子就会变为-2,这时候树又不平衡了,我们需要进行旋转方式如下:

这种旋转方式我们称之为右单旋

3.

我想插入50,现在我们要插入的值比10大但是比20小,是不是应该放在b处,这个时候我们的树变成了这样:

这个时候需要进行旋转了。

先进行右单旋:                                   再左单旋:

                           

这种旋转方式我们称为右左双旋。

4.

现在我们想要插入元素25,即在b这个位置进行插入

需要进行旋转,过程如下:

先进行左单旋:              再进行右单旋:

                             

这种旋转我们称为左右双旋。

将这几种旋转我们再来统一的捋一捋:

1.左单旋,将g作为10的右子树,将以10为父节点的这颗子树作为20的左子树完成旋转,△的节点表示可能存在亦可能不存在

2.右单旋,把节点f作为30的左子树,然后将以30这个节点为父节点的子树作为20的右子树完成旋转

3.右左双旋,先以节点30进行右单旋,再以节点10进行左单旋

4.左右双旋,先以节点10进行左单旋,再以节点30进行右单旋

我们现在已经知道了旋转的过程,那么怎样来实现这颗AVL树呢?

以插入一个节点为例,步骤如下:

                                  1.先构建二叉搜索树

                                   2.插入一个节点,更新平衡因子,判断是否平衡

                                              a)右边增加一个节点,父亲的平衡因子+1

                                              b)左边增加一0个节点,父亲的平衡因子-1

                                      如果父亲的平衡因子等于,则不需要向上更新,说明树的整体高度没变

                                      如果父亲的平衡因子变为1或者-1,则需要向上进行更新,因为此时树的变了

                                      如果父亲的平衡因子变为-2或者2,则需要根据具体情况进行相应旋转

过程出来了,代码怎么实现呢?为了向上更新平衡因子,很显然我们需要一个三叉链,即每个节点有三个指针:_left,_right,_parent,

下面我们来完成每种情况伪代码的编写。

对于左单旋的情况:

            Node* ppNode=parent->_parent;
			Node* subR=parent->_right;
			Node* subRL=subR->_left;
		    parent->_right=subRL;//此处有大坑,如果subR不存在,需要将parent->_right置空
			if(subRL)
			{
				subRL->_parent=parent;
			}
			subR->_left=parent;
			parent->_parent=subR;
			if(_root==parent)
			{
				_root=subR;
				subR->_parent=NULL;
			}
			else
			{
				if(ppNode->_left==parent)
				{
					ppNode->_left=subR;
				}
				else
				{
					ppNode->_right=subR;
				}
				subR->_parent=ppNode;
			}
			subR->_bf=0;
			parent->_bf=0;

对于右单旋的情况:

Node* subL=parent->_left;
			Node* subLR=subL->_right;
			Node* PPNode=parent->_parent;
			parent->_left=subLR;
			if(subLR)
			{
			   subLR->_parent=parent;
			}
			subL->_right=parent;
			parent->_parent=subL;
			if(_root==parent)
			{
				_root=subL;
				subL->_parent=NULL;
			}
			else
			{
				if(PPNode->_left==parent)
				{
					PPNode->_left=subL;
				}
				else
				{
					PPNode->_right=subL;
				}
				subL->_parent=PPNode;
			}
			subL->_bf=0;
			parent->_bf=0;

对于右左双旋就是右单旋和左单旋的结合,左右双旋就是左单旋和右单旋的结合,这里不在进行分析。

完整的代码如下:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class K,class V>
struct AVLTreeNode{
	K _k;
	V _v;
	AVLTreeNode<K,V>* _left;
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K,V>* _parent;
	int _bf;
	AVLTreeNode(const K& key,const V& val)
		:_k(key)
		,_v(val)
		,_left(NULL)
		,_right(NULL)
		,_parent(NULL)
		,_bf(0)
	{}

};

template<class K,class V>
class MyAVL{
	typedef AVLTreeNode<K,V> Node;
public:
	MyAVL()
		:_root(NULL)
	{}
	void _InOrder(Node* root)
	{
		if(root==NULL)
		{
			return;
		}
		_InOrder(root->_left);
		cout<<root->_k<<" ";
		_InOrder(root->_right);
	}
	void InOrder()
	{
		return _InOrder(_root);
	}
	bool Insert(const K& key,const V& val)
	{
		if(_root==NULL)
		{
			_root=new Node(key,val);
			return true;
		}
		Node* cur=_root;
		Node* parent=NULL;
		while(cur!=NULL)
		{
			if(key<cur->_k)
			{
				parent=cur;
				cur=cur->_left;
			}
			else if(key>cur->_k)
			{
				parent=cur;
			    cur=cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur=new Node(key,val);
		if(parent->_k>key)
		{
			parent->_left=cur;
			cur->_parent=parent;
		}
		else
		{
			parent->_right=cur;
			cur->_parent=parent;
		}
		while(parent)
		{
			//如果插入节点为右节点,则父亲的平衡因子+1
			if(cur==parent->_right)
			{
				parent->_bf++;
			}
			else//为左节点,则父亲的平衡因子-1
			{
				parent->_bf--;
			}
			//如果父节点的平衡因子变为0,说明原来是-1或者1,树的高度没变,不用向上更新平衡因子
			if(parent->_bf==0)
			{
				break;
			}
			//如果父亲的平衡因子变为-1或者1,说明原来的平衡因子为0,树的高度变了,则需要向上更新
			//平衡因子
			else if(parent->_bf==-1||parent->_bf==1)
			{
				cur=parent;
				parent=cur->_parent;
			}
			else//父亲平衡因子变为-2或者2,子树不再是AVL树,需要进行旋转来平衡
			{
               if(parent->_bf==2)
			   {
				   if(cur->_bf==1)
				   {
					   RotateL(parent);
				   }
				   else
				   {
					   RotateRL(parent);
				   }
			   }
			   else
			   {
				   if(cur->_bf==-1)
				   {
					   RotateR(parent);
				   }
				   else
				   {
					   RotateLR(parent);
				   }
			   }
			
			   break;

			}

		}
		return true;
	}

		bool IsBlanceTree()
		{
			int height=0;
			return _IsBlanceTree(_root,height);
		}
		bool _IsBlanceTree(Node* root,int& height)
		{
			if(root==NULL)
			{
				height=0;
				return true;
			}
			int leftheight=0;
			if(_IsBlanceTree(root->_left,leftheight)==false)
			{
				return false;
			}

			int rightheight=0;
			if(_IsBlanceTree(root->_right,rightheight)==false)
			{
				return false;
			}
			 if(rightheight-leftheight!=root->_bf)
			   {
				   cout<<"平衡因子异常"<<root->_k<<endl;
				    return false;
			   }
			height=leftheight>rightheight?leftheight+1:rightheight+1;
			return abs(leftheight-rightheight)<2;
			
		}
		void RotateR(Node* parent)
		{
			Node* subL=parent->_left;
			Node* subLR=subL->_right;
			Node* PPNode=parent->_parent;
			parent->_left=subLR;
			if(subLR)
			{
			   subLR->_parent=parent;
			}
			subL->_right=parent;
			parent->_parent=subL;
			if(_root==parent)
			{
				_root=subL;
				subL->_parent=NULL;
			}
			else
			{
				if(PPNode->_left==parent)
				{
					PPNode->_left=subL;
				}
				else
				{
					PPNode->_right=subL;
				}
				subL->_parent=PPNode;
			}
			subL->_bf=0;
			parent->_bf=0;
		}

		void RotateL(Node* parent)
		{
			Node* ppNode=parent->_parent;
			Node* subR=parent->_right;
			Node* subRL=subR->_left;
		    parent->_right=subRL;//此处有大坑,如果subR不存在,需要将parent->_right置空
			if(subRL)
			{
				subRL->_parent=parent;
			}
			subR->_left=parent;
			parent->_parent=subR;
			if(_root==parent)
			{
				_root=subR;
				subR->_parent=NULL;
			}
			else
			{
				if(ppNode->_left==parent)
				{
					ppNode->_left=subR;
				}
				else
				{
					ppNode->_right=subR;
				}
				subR->_parent=ppNode;
			}
			subR->_bf=0;
			parent->_bf=0;

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

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

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

			 else if(bf==-1)
			 {
				 parent->_bf=1;
				 subL->_bf=0;
				 subLR->_bf=0;
			 }

			 else
			 {
				 assert(false);
			 }
		 }




private:
	Node* _root;
};

void TestAVLTree()
{
	MyAVL<int,int> t;
	//int arr[]={4,2,6,1,3,5,15,7,16};
	int arr[]={16,3,7,11,9,26,18,14,15};
	for(int i=0; i<sizeof(arr)/sizeof(arr[0]); ++i)
	{
		t.Insert(arr[i],i);
	    cout<<arr[i]<<" bf:"<<t.IsBlanceTree()<<endl;
	}
	t.InOrder();
	
}

运行结果如下:

有没有发现代码中包含了一个常见的笔试题:判断一颗二叉搜索树是否平衡?

思路很简单,同时递归左右子树并且记录左右子树的高度,一旦发现高度之差的绝对值大于1则说明这棵树不是平衡树。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值