【数据结构】平衡树之AVL树

        虽然二叉搜索树可以大大提升查找的效率,但如果数据本身有序或接近有序,二叉搜索树将退化为单支树,此时借助它查找元素就相当于在顺序表中搜索元素,效率反而大大降低(详情可见【数据结构】二叉搜索树)。        

        为了解决二叉搜索树退化为单支树影响效率的问题,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均的搜索长度,提高效率

        基于这种方法改进而来的二叉搜索树,被命名为AVL树(由两位数学家的名字而来)。

        AVL树是二叉平衡搜索树的一种,因其结构本质为“通过子树的高度差来控制平衡”,也被称为高度二叉平衡搜索树

       本篇博客将通过对AVL树主要性质的梳理和主要功能的模拟实现,帮助读者更加全面地理解AVL树。

目录

一、AVL的性质

二、AVL树的模拟实现

1 - 树的构建

2 - 插入及旋转

2.1.左单旋 

2.2.右单旋 

2.3.右左双旋

2.4.左右双旋

3 - 完整代码

补、一些迷思

1. AVL树的平衡为什么不是高度相等?

2. 增删查改的时间复杂度:高度次 - O(logN)

3. AVL树的性能问题 

4.数据的查找方案一般有哪些? 


一、AVL的性质

        控制一个节点的左右子树高度之差,是使AVL树平衡的一种重要手段。为了方便描述左右子树高度之差,我们将其称为平衡因子。在一棵平衡的AVL树中,任意节点的平衡因子值应为-101

       平衡因子是判断一棵树高度平衡的重要条件。如果一棵二叉搜索树满足了高度平衡的条件,那么它就是AVL树。

        对于任意一棵AVL树,它都具有如下性质:

  1. 任意树节点的左右子树都是AVL树;
  2. 任意树节点的平衡因子取值范围为[-1,1]。

        如果它有n个结点,其高度可保持在O(logN),搜索查找的时间复杂度为O(lognN)。

二、AVL树的模拟实现

1 - 树的构建

//此处树节点被定义为三叉链结构,每个节点中都有平衡因子
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的构造,对树节点做初始化
	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};

//AVL树
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
    //...
private:
	Node* _root = nullptr;
};

2 - 插入及旋转

        AVL树可以看作是引入了平衡因子的二叉搜索树。那么,在二叉搜索树的基础上,AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点:利用插入的值创建一个新的树节点。树为空,就直接将新节点赋给根节点的指针;树不为空,就按二叉搜索树的性质查找到合适的插入位置,在合适位置插入新节点。
  2. 控制树的平衡:利用旋转。
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;
		}
		
         /   

        //控制树的平衡


		//每插入一个节点,重置其_parent,为调整平衡铺垫
		cur->_parent = parent;

		//然后,控制平衡,维持AVL树的形态

		//插入时,
		//树中新增的节点会影响其祖先(可能是双亲的双亲,也可能是曾曾曾双亲等)的平衡因子			
		//插入在祖先的左子树中,以祖先为根的树的高度发生变化,祖先的平衡因子需-1;
		//插入在祖先的右子树中,同理,平衡因子需+1。
        //插入后,
		//若祖先节点的平衡因子因插入而变为0,则意味着矮的(空缺的)那个子树位置被填上了
		//且以祖先为根的树的高度并没有变化,此时无需再沿返回到整棵树的根的路径向上更新其他祖先,调整到此结束
		//若平衡因子变为1或-1,则说明以祖先为根的树的高度发生变化,此时需要继续往上调整祖先的祖先的平衡因子	
		//若平衡因子变为2或-2,则说明以祖先为根的树的高度发生变化,且该树不平衡了,
		//此时先对以祖先为根的树进行旋转处理,再调整平衡因子,整体平衡后,调整就结束了
		//若倒霉到必须一直更新到根节点,调整也结束了

        //例如:
        //假设cur为待插入位置的节点,parent为cur的双亲节点
        //cur插入后,parent的平衡因子一定需要调整。
        //在插入之前,parent的平衡因子分为三种情况:-1,0, 1,;插入后,又分以下两种情况:
        //   1. 若cur插入到了parent的左侧,只需给parent的平衡因子-1即可
        //   2. 若cur插入到了parent的右侧,只需给parent的平衡因子+1即可
        //经过以上操作,此时parent的平衡因子可能有三种情况:0,+1/-1, +2/-2
        //  1. 如果parent的平衡因子为0,说明插入之前parent的平衡因子为+1/-1,插入后被调整成0,满足AVL树的平衡条件,无需再调整;
        //  2. 如果parent的平衡因子为+1/-1,说明插入前parent的平衡因子为0,插入后被更新成+1/-1,以parent为根的树的高度增加,需要继续向上(向祖先和曾祖先等)更新;
        //  3. 如果parent的平衡因子为+2/-2,则以parent为根的树不满足平衡条件,需要对其进行旋转处理,再调整平衡因子


		while (parent)
		{    
            //插入时,调整parent的平衡因子
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else //if (cur == prant->_right)
			{
				parent->_bf++;
			}

            //根据parent的平衡因子作进一步调整
			if (parent->_bf == 0)    //为0则无需调整
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)    //为+1/-1,则需继续向上调整
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)    //为+2/-2,则需做旋转处理
            //ps:此处继续用else if,是因为无法保证出现_bf为3或-3等bug情况发生,所以用其来防bug影响
			{
				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)
				{
					RotateRL(parent);    //双亲右边高,孩子左边高 -> 右左双旋
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);    //双亲左边高,孩子右边高 -> 左右双旋
				}

				break;
			}
			else//走到这步,一定是出bug了
			{
				assert(false);
			}
		}

		return true;
	}

         如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

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

        1、parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为pSubR

  • 当pSubR的平衡因子为1时,执行左单旋
  • 当pSubR的平衡因子为-1时,执行右左双旋

        2、parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为pSubL

  • 当pSubL的平衡因子为-1时,执行右单旋
  • 当pSubL的平衡因子为1时,执行左右双旋

        旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新

2.1.左单旋 

        新节点插入较高右子树的右侧(右右) => 左单旋。

	void RotateL(Node* parent)
	{        
		Node* cur = parent->_right;    //cur是parent的右孩子
		Node* curleft = cur->_left;    //curleft是cur的左孩子

        //将parent及其左子树整体旋转下来,并将parent与cur、curleft与parent、cur与ppnode一一正确链接

	
		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;    //curleft可能为空,若为空则无需将curleft->_parent与parent链接
		}
		cur->_left = parent;

		//需要旋转的树,可能是一个局部的子树
        //cur可能需要跟parent的双亲节点ppnode链接
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

		if (parent == _root /*ppnode==nullptr*/)    //旋转点在根,cur无需跟parent的双亲节点链接
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else		            //旋转点不在根,cur需要跟parent的双亲节点链接
		{
			if (ppnode->_left == parent)    //parent是ppnode的左孩子,就让cur代替其成为左孩子
			{
				ppnode->_left = cur;
			}
			else                            //parent是ppnode的右孩子,就让cur代替其成为右孩子
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;        //将cur的双亲节点置为ppnode
		}


		//最终,更改平衡因子
		parent->_bf = cur->_bf = 0;

	}


	//使用情景:无数多种。一般插入节点的时候,使树不平衡则需要旋转
	//h(子树的高度)==0 => 空树,无需旋转
	//h==1 => 有一层子树,插入可能破坏平衡,此时需旋转
	//h==2 => 插入前树的可能形状有3*3*1=9种;插入位置的可能有四种(左/右孩子的左/右边);h==2合计的组合情况有:9*4=36种
	//......
	//以此类推,使用情景有无数多种
	

2.2.右单旋 

        类似左单旋。

        新节点插入较高左子树的左侧(左左) => 右单旋。

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;    //cur是parent的左孩子
		Node* curright = cur->_right; //curright是cur的右孩子

         //将parent及其右子树整体旋转下来,并将parent与cur、curleft与parent、cur与ppnode一一正确链接

        //链接curright与parent
		parent->_left = curright;
		if (curright)
		{
			curright->_parent = parent;
		}

        //链接cur与parent、cur与parent的双亲ppnode
		Node* ppnode = parent->_parent;
		cur->_right = parent;
		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

        //调整平衡因子
		parent->_bf = cur->_bf = 0;
	}


	//使用情景:无数多种。一般插入节点的时候,使树不平衡则需要旋转
	//h(子树的高度)==0 => 空树,无需旋转
	//h==1 => 有一层子树,插入可能破坏平衡,此时需旋转
	//h==2 => 插入前树的可能形状有3*3*1=9种;插入位置的可能有四种(左/右孩子的左/右边);h==2合计的组合情况有:9*4=36种
	//......
	//以此类推,使用情景有无数多种

2.3.右左双旋

        新节点插入较高右子树的左侧(右左) => 先右单旋再左单旋。

    //右左双旋
	//第一次旋转为预处理,第二次旋转为真正的平衡调整
	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;

        //先右单旋,再左单旋
		RotateR(parent->_right);
		RotateL(parent);

        //然后,还需调整平衡因子
        //...
    }


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

        //先右单旋,再左单旋
		RotateR(parent->_right);
		RotateL(parent);

        //调整平衡因子
		if (bf == 0)        //上图中值为60的节点左右两边没有插入
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)   //插入在60的右边
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)  //插入在60的左边
		{
			cur->_bf = 1;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

2.4.左右双旋

        类似于右左双旋。

        新节点插入较高左子树的右侧(左右) => 先左单旋再右单旋。


	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;
    
        //先左单旋再右单旋
		RotateL(parent->_left);
		RotateLR(parent);

        //调整平衡因子
		if (bf == 0)        //上图中值为60的节点左右两边没有插入
		{
			cur->_bf = 0;
			curright->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)  //插入在60的左边
		{
			cur->_bf = 0;
			curright->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)  //插入在60的右边
		{
			cur->_bf = -1;
			curright->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

3 - 完整代码

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

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>
class 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;
		}
		else
		{
			parent->_left = cur;
		}
		
		cur->_parent = parent;

		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else //if (cur == prant->_right)
			{
				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)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

	
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		parent->_right = curleft;
		if (curleft)
		{
			curleft->_parent = parent;
		}

		cur->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

		parent->_bf = cur->_bf = 0;

	}

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

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

		Node* ppnode = parent->_parent;
		cur->_right = parent;

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

			cur->_parent = ppnode;
		}

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

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

		RotateR(parent->_right);
		RotateL(parent);

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

	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		RotateL(parent->_left);
		RotateLR(parent);

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

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHight = Height(root->_left);
		int rightHeight = Height(root->_right);
		return leftHight > rightHeight ? leftHight + 1 : rightHeight + 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 << "_bf error" << root->_kv.first<<"->"<<root->_bf << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& IsBalance(root->_left)
			&& IsBalance(root->_right);
	}


	/	/	/
	

	//删除(了解即可)
	//step1.按搜索树的规则查找节点删除
	//step2.更新平衡因子
	//step3.出现异常需旋转


private:
	Node* _root = nullptr;
};

补、一些迷思

1. AVL树的平衡为什么不是高度相等?

        保证平衡因子为0(即左右子树高度相等),在节点少的时候容易做到,但节点多的时候很难做到相等。故退而求其次,将“平衡”定义为高度差的绝对值不超过1(即平衡因子为1或-1)。

2. 增删查改的时间复杂度:高度次 - O(logN)

解释:
满二叉树的节点个数:2^h-1 = N(最后一层全满不缺节点)
AVL树的节点个数:2^h-x = N(最后一层缺x-1个节点)
=> x的范围:[1,2^(h-1)-1]( [最后一层不缺节点,最后一层只剩一个节点] )

2^h-1 = N => 2^(h-1) = (N+1)/2 
2^h-x = N =>x最大时 2^h-N/2 = N =>2^h =N*3/2 =>约等于logN

3. AVL树的性能问题 

        AVL树是一棵绝对平衡的二叉搜索树,其每个节点的左右子树高度差绝对值都不超过1,这样可以保证查找时高效的时间复杂度(即O(logN))。

        但是,当涉及一些结构上的修改操作,AVL树的性能却十分低下。

        例如,在AVL树中插入数据时,要维护其绝对平衡,旋转调整不可避免,旋转的次数较多。更糟糕的是,在删除时, 有可能需要让旋转操作一直持续到根的位置才结束。

        因此,如果需要一种查找高效且有序的数据结构,且数据的上限是静态的(即数据个数不会改变),则AVL树是一个优选;但如果数据经常涉及修改,则不推荐选用AVL树。

 

4.数据的查找方案一般有哪些? 

  1. 暴力搜索(效率较低)
  2. 二分搜索(问题:原序列需有序,且序列涉及插入删除时非常麻烦,维护成本很高)
  3. 二叉搜索树(问题:极端场景下会退化为类似链表的结构)
  4. 二叉平衡搜索树(为了解决上面的问题,应运而生。但不能做到完全平衡)
  5. 多叉平衡搜索树(能做到完全平衡,属于B树系列)
  6. 哈希表
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值