【C++技能树】手撕AVL树 --插入与旋转详解

在这里插入图片描述
Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!

文章目录

  • 0.平衡搜索二叉树概念
    • 0.1 平衡因子
  • 1.插入
    • 1.1 普通插入操作
    • 1.2更新平衡因子
  • 2.旋转
    • 2.1 左单旋
    • 2.2 右单旋
    • 2.3 右左双旋
    • 2.4 左右双旋
  • 3. 旋转判定
  • 4. 验证是否为AVL树
  • 5.完整源码(AVL插入旋转)

0.平衡搜索二叉树概念

为什么在会了搜索二叉树之后,还需要学习平衡搜索二叉树呢?在搜索二叉树部分,并没有要求树保持平衡,仅需遵循左小右大即可.

若往树中插入有序或者接近有序的值,会出现下面这种情况,退化为单支树.

【C++技能树】手撕AVL树 --插入与旋转详解_第1张图片

这样的搜索树就完全没有效率可言,他的时间复杂度为o(N).所以我们需要一棵正常(平衡)的树来解决这个问题.

否则当map和set用这样的树岂不是效率非常的低,所以这就是平衡二叉搜索树的意义.

将子树的左右平衡高度差维持在(-1/0/1)之间,若超过了这个范围,则对树进行高度调整,从而减少搜索长度.

上面的那棵树可以变化成这个样子,这样搜索的效率就变成了o(logN),大大提高了效率.

【C++技能树】手撕AVL树 --插入与旋转详解_第2张图片

这同样是一棵满二叉树.其节点个数为 2 h − 1 2^h-1 2h1,可以将其看作一颗特殊的平衡二叉搜索树.

所以平衡二叉搜索树的节点个数的公式为: 2 h − X 2^h-X 2hX,X的范围介于[1, 2 ( h − 1 ) − 1 2^{(h-1)}-1 2(h1)1]之间.

这里可以这样理解.二叉树的最大深度差为1,此时的最后一层最差的情况为只有一个节点,那么缺少的节点数为前面所有层的节点.

忽略掉常数,可以看到h可以近似等于logN.

0.1 平衡因子

上文提到,二叉搜索树需要计算其平衡因子来稳定树的平衡度.那么平衡因子怎么算呢?

左子树的最大高度为负,右子树的最大高度为正.两值相加,体现在根上的即为这棵树的平衡因子

【C++技能树】手撕AVL树 --插入与旋转详解_第3张图片

因为其对树的结构调整,需要大量访问parent,也就是子树的根,所以在定义这棵树的时候.我们直接将其parent存起来,方便后续查找.

template<typename K,typename V>
struct TreeNode
{	
	pair<K, V>_val;
	TreeNode<K,V>* _left;
	TreeNode<K,V>* _right;
	TreeNode<K,V>* _parent;
	int _bf;

	TreeNode(const pair<K,V> val)
		:_val(val),
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_bf(0)
	{}
};

1.插入

我们先写一个最简单的二叉树插入操作,然后在上面加上平衡因子的更新与旋转即可.

1.1 普通插入操作

bool Insert(const pair<K, V> kv)
	{
		
		Node* parent = _root;
		Node* cur = _root;
		if (_root == nullptr)
		{
			_root = new Node(kv);
		}
		while (cur)
		{
			if (kv.first > cur->_val.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_val.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else return false;;
		}
		cur = new Node(kv);
		if (parent->_val.first > kv.first)
		{
			parent->_left = cur;
		}
		else
			parent->_right = cur;
		cur->_parent = parent;
	}

1.2更新平衡因子

当我们插入一个新的节点的时候,**新增在左则平衡因子减减,新增在右则平衡因子加加.**此时有以下这几种情况:

  1. 当更新完平衡因子,parent的平衡因子为0时,说明并没有引起这棵子树的高度变化.则不需要继续向上更新平衡因子

    【C++技能树】手撕AVL树 --插入与旋转详解_第4张图片

  2. 当更新完平衡因子,parent的平衡因子为-1/1时,说明引起了这棵树的高度变化,需要继续更新其祖先的平衡因子

    【C++技能树】手撕AVL树 --插入与旋转详解_第5张图片

  3. 当更新完平衡因子,parent的平衡因子为-2/2时,说明这棵树需要进行旋转来维持平衡

    【C++技能树】手撕AVL树 --插入与旋转详解_第6张图片

​ 关于旋转我们稍后再说,我们在代码里加上更新平衡因子的这几步操作.

while (parent)
{
    if (parent->_left == cur)
    {
        parent->_bf--;
    }
    else 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)
    {
        //旋转
    }
}

2.旋转

旋转根据实际的情况,可以分为四种情况:左单旋,右单旋,左右单旋,右左单旋

旋转的目的是:在不破坏AVL的前提下,降低这棵树的高度,使其重新成为AVL树

2.1 左单旋

当parent节点的平衡因子为2时,需要进行左单旋来降低整棵树的高度.

【C++技能树】手撕AVL树 --插入与旋转详解_第7张图片

这是最简单的情况,即让parent->right=cur->left cur->left=parent

【C++技能树】手撕AVL树 --插入与旋转详解_第8张图片

当需要旋转的是一颗子树的时候,核心操作不变,但需要更新下parent 与 pparent的关系,将pparent指向cur

【C++技能树】手撕AVL树 --插入与旋转详解_第9张图片

所以左单旋的代码也可以写出来了

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

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

2.2 右单旋

【C++技能树】手撕AVL树 --插入与旋转详解_第10张图片

【C++技能树】手撕AVL树 --插入与旋转详解_第11张图片

其旋转核心为:**parent->left=cur->right cur->right=parent **

所以右旋转的代码为:

void RotateR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curleft = cur->_left;
    if (curleft)
    {
        curleft->_parent = parent;
    }
    parent->_left = curleft;
    if (parent == _root)
    {
        cur->_left = parent;
    }
    else
    {
        Node* pparent = parent->_parent;
        if (pparent->_left = parent)
        {
            pparent->_left = cur;
        }
        else
            pparent->_right = cur;
        cur->_parent = pparent;

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

}

2.3 右左双旋

其抽象模型为:

【C++技能树】手撕AVL树 --插入与旋转详解_第12张图片

这个模型实在有点抽象,我们分以下几种情况来讨论.

当h0时,此时60为插入的节点(60->bf0)

【C++技能树】手撕AVL树 --插入与旋转详解_第13张图片

旋转完成后,parent->bf cur->bf均为0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curleft->bf==-1)

【C++技能树】手撕AVL树 --插入与旋转详解_第14张图片

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

【C++技能树】手撕AVL树 --插入与旋转详解_第15张图片

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curleft->bf==-1)

    【C++技能树】手撕AVL树 --插入与旋转详解_第16张图片

旋转完成后,parent->bf=0 cur->bf=1 curleft->bf=0

  1. 在右边插入(curleft->bf==1)

【C++技能树】手撕AVL树 --插入与旋转详解_第17张图片

旋转完成后,parent->bf=-1 cur->bf=0 curleft->bf=0

可以看出,引发右左双旋的原因为:cur->bf==-1,parent->bf==2

所以 其代码可以复用之前的旋转,但之前的旋转改变了平衡因子,我们需要依照上面的规律再特殊处理一下

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

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

2.4 左右双旋

其抽象模型为:

【C++技能树】手撕AVL树 --插入与旋转详解_第18张图片

这个模型实在有点抽象,我们分以下几种情况来讨论.

h0 此时60为插入的节点(curright->bf0)

【C++技能树】手撕AVL树 --插入与旋转详解_第19张图片

旋转完成后,parent->bf=cur->bf=curright->bf=0

当h==1时,此时可以分为在60的左边插入或者在60的右边插入

  1. 在左边插入(curright->bf==-1)

【C++技能树】手撕AVL树 --插入与旋转详解_第20张图片

旋转完成后,parent->bf=1 cur->bf=0 curleft->bf=0

  1. 在右边插入(curright->bf==1)

【C++技能树】手撕AVL树 --插入与旋转详解_第21张图片

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

当h==2时,此时也可以分为在左边插入以及在右边插入

  1. 在左边插入(curright->bf==-1)

    【C++技能树】手撕AVL树 --插入与旋转详解_第22张图片

旋转完成后,parent->bf=1 cur->bf=0 curright->bf=0

  1. 在右边插入(60->bf==1)

【C++技能树】手撕AVL树 --插入与旋转详解_第23张图片

旋转完成后,parent->bf=0 cur->bf=-1 curright->bf=0

可以看出,引发左右双旋的原因为:cur->bf1,parent->bf-2

void RotaLR(Node* parent)
{
    Node* cur = parent->_left;
    Node* curright = cur->right;
    int bf = curright->_bf;
    RotateL(cur);
    RotateR(parent);
    if (bf == 0)
    {
        parent->_bf = 0;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    if (bf == -1)
    {
        parent->_bf = 1;
        cur->_bf = 0;
        curright->_bf = 0;
    }
    if (bf == 1)
    {
        parent->_bf = 0;
        cur->_bf = -1;
        curright->_bf = 0;
    }
}

3. 旋转判定

旋转的判定为:同号单旋,异号双旋(将cur旋转为parent同号)

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)
    {
        RotaRL(parent);
    }
    else if (parent->_bf == -2 && cur->_bf == 1)
    {
        RotaLR(parent);
    }
    break;
}

4. 验证是否为AVL树

bool IsAVLTree()
{
    return IsAVLTree(_root);
}
bool IsAVLTree(Node* root)
{
    if (root == nullptr)return true;
    int leftheight = Height(root->_left);
    int rightheight= Height(root->_right);
    if (rightheight - leftheight != root->_bf)
    {
        cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;
        return false;
    }
    return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
}
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;
}

5.完整源码(AVL插入旋转)

#pragma once
#include
#include
using namespace std;
template<typename K,typename V>
struct TreeNode
{	
	pair<K, V>_val;
	TreeNode<K,V>* _left;
	TreeNode<K,V>* _right;
	TreeNode<K,V>* _parent;
	int _bf;

	TreeNode(const pair<K,V> val)
		:_val(val),
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_bf(0)
	{}
};
template<typename K,typename V>
class AVLBSTree {
	typedef TreeNode<K,V> Node;
public:
	bool Insert(const pair<K, V> kv)
	{
		
		Node* parent = _root;
		Node* cur = _root;
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		while (cur)
		{
			if (kv.first > cur->_val.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_val.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else return false;;
		}
		cur = new Node(kv);
		if (parent->_val.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
			
		}
		cur->_parent = parent;
		while (parent)
		{
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else 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)
				{
					RotaRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotaLR(parent);
				}
				break;
			}
			else assert(false);
		}     
		return true;


	}
	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		if (curleft)
			curleft->_parent = parent;
		parent->_right = curleft;
		cur->_left = parent;
		
		Node* pparent = parent->_parent;
		parent->_parent = cur;
		if (_root == parent)
		{
			cur->_parent = nullptr;
			_root = cur;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else pparent->_right = cur;
			cur->_parent = pparent;
		}
		parent->_bf =cur->_bf= 0;
	}
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		if (curright)
		{
			curright->_parent = parent;
		}
		parent->_left = curright;
		cur->_right = parent;
		if (parent == _root)
		{
			cur->_left = parent;
		}
		else
		{
			Node* pparent = parent->_parent;
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
				pparent->_right = cur;
			cur->_parent = pparent;

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

	}

	void RotaRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;
		RotateR(parent->_right);
		RotateL(parent);
		
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
		if (bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
			curleft->_bf = 0;
		}
		if (bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
	}
	void RotaLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;
		RotateL(cur);
		RotateR(parent);
		if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
	}
	bool IsAVLTree()
	{
		return IsAVLTree(_root);
	}
	bool IsAVLTree(Node* root)
	{
		if (root == nullptr)return true;
		int leftheight = Height(root->_left);
		int rightheight= Height(root->_right);
		if (rightheight - leftheight != root->_bf)
		{
			cout << "异常" << rightheight<< " " << leftheight<< " "<<root->_bf<<endl;
			return false;
		}
		return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
	}
	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;
	}
private:
	Node* _root = nullptr;
};

【C++技能树】手撕AVL树 --插入与旋转详解_第24张图片
{
if (root == nullptr)return true;
int leftheight = Height(root->_left);
int rightheight= Height(root->_right);
if (rightheight - leftheight != root->_bf)
{
cout << “异常” << rightheight<< " " << leftheight<< " "<_bf< return false;
}
return abs(rightheight - leftheight) < 2 && IsAVLTree(root->_left) && IsAVLTree(root->_right);
}
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;
}
private:
Node* _root = nullptr;
};


[外链图片转存中...(img-4uSXVW1J-1694524567600)]

你可能感兴趣的:(C++技能树,数据结构,c++,开发语言,算法,数据结构)