AVL树实现

AVL树概念

  • AVL树是最先发明的⾃平衡⼆叉查搜索树,AVL是⼀颗空树,或者具备下列性质的⼆叉搜索树:==它的左右⼦树都是AVL树,且左右⼦树的⾼度差的绝对值不超过1。==AVL树是⼀颗⾼度平衡搜索⼆叉树,通过控制⾼度差去控制平衡。
  • AVL树实现这⾥我们引⼊⼀个==平衡因⼦(balance factor)==的概念,每个结点都有⼀个平衡因⼦,任何结点的平衡因⼦等于右⼦树的⾼度减去左⼦树的⾼度,也就是说任何结点的平衡因⼦等于0/1/-1
  • AVL树整体结点数量和分布和完全⼆叉树类似,⾼度可以控制在 ,那么增删查改的效率也可以控制在O(logN),相⽐⼆叉搜索树有了本质的提升。

AVL树实现

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;	//平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)	//新增的节点平衡因子为0,右子树高度 - 左子树高度
	{ }
};

AVL树插入一个值

大致过程

  • 插⼊⼀个值,按⼆叉搜索树规则进⾏插⼊。

  • 新增结点以后,只会影响祖先结点的⾼度,也就是可能会影响部分祖先结点的平衡因⼦,所以更新从新增结点->根结点路径上的平衡因⼦,实际中最坏情况下要更新到根,有些情况更新到中间就可以停⽌了,具体情况我们下⾯再详细分析。

  • 更新平衡因⼦过程中没有出现问题,则插⼊结束

  • 更新平衡因⼦过程中出现不平衡,对不平衡⼦树旋转,旋转后本质调平衡的同时,本质降低了⼦树 的⾼度,不会再影响上⼀层,所以插⼊结束

    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
      	{
      		parent->_bf++;	
      	}
    
      	if (parent->_bf == 0)
      	{
      		break;
      	}
      	else if (parent->_bf == 1 || parent->_bf == -1)
      	{
      		//继续向上更新
      		cur = parent;
      		parent = cur->_parent;
      	}
      	else if (parent->_bf == 2 || parent->_bf == -2)
      	{
      		//不平衡了,旋转处理
      		if (parent->_bf == -2 && cur->_bf == -1)
      		{
      			RotateR(parent);	//右单旋
      		}
      		else if (parent->_bf == 2 && cur->_bf == 1)
      		{
      			RotateL(parent);	//左单旋
      		}
      		else if (parent->_bf == -2 && cur->_bf == 1)
      		{
      			RotateLR(parent);
      		}
      		else if (parent->_bf == 2 && cur->_bf == -1)
      		{
      			RotateRL(parent);
      		}
      		break;
      	}
      	else
      	{
      		assert(false);
      	}
      }
      return true;

    }

平衡因子更新

更新原则

  • 平衡因⼦=右⼦树⾼度-左⼦树⾼度
  • 只有⼦树⾼度变化才会影响当前结点平衡因⼦。
  • 插⼊结点,会增加⾼度,所以==新增结点在parent的右⼦树,parent的平衡因⼦++,新增结点在 parent的左⼦树,parent平衡因⼦-- ==
  • parent所在⼦树的⾼度是否变化决定了是否会继续往上更新

更新停止条件:

  • 更新后parent的平衡因⼦等于0(更新中parent的平衡因⼦变化为-1->0或者1->0),说明更新前parent⼦树⼀边⾼⼀边低,新增的结点插⼊在低的那边,插⼊后parent所在的⼦树⾼度不变,不会影响parent的⽗亲结点的平衡因⼦,更新结束。

  • 更新后parent的平衡因⼦等于1或-1,更新前更新中parent的平衡因⼦变化为0->1或者0->-1,说明更新前parent⼦树两边⼀样⾼,新增的插⼊结点后,parent所在的⼦树⼀边⾼⼀边低,parent所在的⼦树符合平衡要求,但是⾼度增加了1,会影响parent的⽗亲结点的平衡因⼦,所以要继续向上更新。

  • 更新后parent的平衡因⼦等于2或-2,更新前更新中parent的平衡因⼦变化为1->2或者-1->-2,说明更新前parent⼦树⼀边⾼⼀边低,新增的插⼊结点在⾼的那边,parent所在的⼦树⾼的那边更⾼了,破坏了平衡,==parent所在的⼦树不符合平衡要求,需要旋转处理,旋转的⽬标有两个:1、把 parent⼦树旋转平衡。2、降低parent⼦树的⾼度,恢复到插⼊结点以前的⾼度。所以旋转后也不需要继续往上更新,插⼊结束。 ==

  • 不断更新,更新到根,根的平衡因⼦是1或-1也停⽌了

旋转

旋转的原则

  • 保持搜索树的规则
  • 让旋转的树从不满⾜变平衡,其次降低旋转树的⾼度旋转总共分为四种,左单旋/右单旋/左右双旋/右左双旋。

右单旋

  • 本图展⽰的是10为根的树,有a/b/c抽象为三棵⾼度为h的⼦树(h>=0),a/b/c均符合AVL树的要求。10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根。这⾥a/b/c是⾼度为h的⼦树, 是⼀种概括抽象表⽰,他代表了所有右单旋的场景,实际右单旋形态有很多种。

  • 在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h+1,不断向上更新平衡因⼦,导致10的平 衡因⼦从-1变成-2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树左边太⾼了,需要 往右边旋转,控制两棵树的平衡。

  • 旋转核⼼步骤,因为5<b⼦树的值<10,将b变成10的左⼦树,10变成5的右⼦树,5变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h+2,符合旋转原则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。

    复制代码
      void RotateR(Node* parent)	//右单旋
      {
      	Node* subL = parent->_left;
      	Node* subLR = subL->_right;
      	Node* pParent = parent->_parent;
    
      	parent->_left = subLR;
      	if (subLR)
      	{
      		subLR->_parent = parent;
      	}
    
      	subL->_right = parent;
      	parent->_parent = subL;
    
      	if (parent == _root)
      	{
      		_root = subL;
      		subL->_parent = nullptr;
      	}
      	else
      	{
      		if (pParent->_left == parent)
      		{
      			pParent->_left = subL;
      		}
      		else
      		{
      			pParent->_right = subL;
      		}
      		subL->_parent = pParent;
      	}
    
      	parent->_bf = subL->_bf = 0;
      }

左单旋

  • 本图展⽰的是10为根的树,有a/b/c抽象为三棵⾼度为h的⼦树(h>=0),a/b/c均符合AVL树的要 求。10可能是整棵树的根,也可能是⼀个整棵树中局部的⼦树的根。这⾥a/b/c是⾼度为h的⼦树, 是⼀种概括抽象表⽰,他代表了所有右单旋的场景,实际右单旋形态有很多种,具体跟上⾯左旋类似。
  • 在a⼦树中插⼊⼀个新结点,导致a⼦树的⾼度从h变成h+1,不断向上更新平衡因⼦,导致10的平 衡因⼦从1变成2,10为根的树左右⾼度差超过1,违反平衡规则。10为根的树右边太⾼了,需要往 左边旋转,控制两棵树的平衡。
  • 旋转核⼼步骤,因为10<b⼦树的值<15,将b变成10的右⼦树,10变成15的左⼦树,15变成这棵树新的根,符合搜索树的规则,控制了平衡,同时这棵的⾼度恢复到了插⼊之前的h+2,符合旋转原则。如果插⼊之前10整棵树的⼀个局部⼦树,旋转后不会再影响上⼀层,插⼊结束了。

左右双旋

  • 下⾯我们将 a/b/c ⼦树抽象为⾼度 h 的AVL ⼦树进⾏分析,另外我们需要把b⼦树的细节进⼀步展开为8和左⼦树⾼度为 h-1 的 e 和 f ⼦树,因为 我们要对b的⽗亲5为旋转点进⾏左单旋,左单旋需要动 b 树中的左⼦树。b ⼦树中新增结点的位置 不同,平衡因⼦更新的细节也不同,通过观察8的平衡因⼦不同,这⾥我们要分三个场景讨论。

  • 先左旋再右旋,最后按情况更新平衡因子

  • 场景1:h>=1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1并为h并不断更新8->5->10平衡因⼦, 引发旋转,其中8的平衡因⼦为-1,旋转后8和5平衡因⼦为0,10平衡因⼦为1。

  • 场景2:h>=1时,新 增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新8->5->10平衡因⼦,引 发旋转,其中8的平衡因⼦为1,旋转后8和10平衡因⼦为0,5平衡因⼦为-1。

  • 场景3:h==0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新5->10平衡因⼦,引发旋 转,其中8的平衡因⼦为0,旋转后8和10和5平衡因⼦均为0。

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

    }

右左双旋

  • 与左右双旋类似,要分三个场景讨论

  • 场景1:h>=1时,新增结点插⼊在e⼦树,e⼦树⾼度从h-1变为h并不断更新12->15->10平衡因 ⼦,引发旋转,其中12的平衡因⼦为-1,旋转后10和12平衡因⼦为0,15平衡因⼦为1。

  • 场景2:h>=1时,新增结点插⼊在f⼦树,f⼦树⾼度从h-1变为h并不断更新12->15->10平衡因⼦, 引发旋转,其中12的平衡因⼦为1,旋转后15和12平衡因⼦为0,10平衡因⼦为-1。

  • 场景3:h==0时,a/b/c都是空树,b⾃⼰就是⼀个新增结点,不断更新15->10平衡因⼦,引发旋转,其中12的平衡因⼦为0,旋转后10和12和15平衡因⼦均为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)
    {
    subR->_bf = 0;
    subRL->_bf = 0;
    parent->_bf = 0;
    }
    else 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
    {
    assert(false);
    }
    }

AVL树查找

复制代码
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < key)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > key)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}

	return nullptr;
}

AVL树平衡检测

  • 使用左子树高度差来检验平衡因子是否有问题即可

    复制代码
      int _Height(Node* root)
      {
      	if (root == nullptr)
      		return 0;
      	int leftHeight = _Height(root->_left);
      	int rightHeight = _Height(root->_right);
      	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
      }
    
      bool _IsBalanceTree(Node* root)
      {
      	// 空树也是AVL树
      	if (nullptr == root)
      		return true;
      	// 计算pRoot结点的平衡因子:即pRoot左右子树的高度差
      	int leftHeight = _Height(root->_left);
      	int rightHeight = _Height(root->_right);
      	int diff = rightHeight - leftHeight;
    
      	// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
      	// pRoot平衡因子的绝对值超过1,则一定不是AVL树
      	if (abs(diff) >= 2)
      	{
      		cout << root->_kv.first << "高度差异常" << endl;
      		return false;
      	}
    
      	if (root->_bf != diff)
      	{
      		cout << root->_kv.first << "平衡因子异常" << endl;
      		return false;
      	}
    
      	// pRoot的左和右如果都是AVL树,则该树一定是AVL树
      	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
      }
相关推荐
大只鹅1 小时前
Java集合框架-Collection
java·开发语言
小冷coding1 小时前
【Java】基于Java的线上贷款分发业务技术栈设计方案
java·开发语言
星火开发设计1 小时前
循环结构进阶:while 与 do-while 循环的适用场景
java·开发语言·数据结构·学习·知识·循环
苏宸啊1 小时前
C++模版template(泛型编程)初阶
c++
郝学胜-神的一滴2 小时前
Qt自定义TabWidget:实现左侧标签与水平文本布局
开发语言·c++·qt·程序人生
计算机程序设计小李同学2 小时前
基于JavaServer Pages(JSP)技术开发的食谱分享平台
java·开发语言
划水的code搬运工小李2 小时前
VOFA修改数据解析GPCHC
开发语言·qt
cjp5602 小时前
019.C#管道服务,两软件间用json数据交互
开发语言·c#·json
我是一只小青蛙8882 小时前
C++模板进阶技巧全解析
java·开发语言