[C++随笔录] AVL树

AVL树

  • 引言
  • AVL树的模拟实现
    • AVL树的底层结构
    • insert的实现
      • 实现思路
      • 旋转逻辑
      • insert的完整代码
    • insert的验证
  • 源码

引言

前面 二叉搜索树的时间复杂度那里提过一嘴 AVL树 和 红黑树. 因为二叉搜索树的时间复杂度是 O(高度次), 最坏情况下 -- 退化到单支链, 是 O(N); AVL 和 红黑树 可以避免这种 极端情况, 时间复杂度是 O(log N)

️AVL树是如何做到避免 二叉搜索树的极端情况的呢?

  • 利用了 三叉链 && 平衡因子
    三叉链 和 平衡因子是互相成就的,.

至于它们到底是什么, 有什么妙用, 下面会有详细的解释

AVL树的模拟实现

AVL树的底层结构

AVL树具有以下的特点:

  1. 左右子树都是AVL树
  2. 每棵子树的 高度差(平衡因子) 的绝对值不超过 1 (-1, 0 1)
  • 一般的平衡因子是 : 右子树的高度 - 左子树的高度
    [C++随笔录] AVL树_第1张图片

️为啥不让每棵子树的 平衡因子的绝对值为 0 ?


  • [C++随笔录] AVL树_第2张图片

AVL树的底层结构:
[C++随笔录] AVL树_第3张图片

  1. AVLTreeNode类
template<class K, class V>
struct AVLTreeNode
{
public:
	AVLTreeNode(const pair<K,V>& kv)
		:_kv(kv)
	{}

public:
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left = nullptr;
	AVLTreeNode<K, V>* _right = nullptr;
	AVLTreeNode<K, V>* _parent = nullptr;
	int _bf = 0;
};
  1. AVLTree类
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;

public:
	AVLTree()
		:_root(nullptr)
	{}
private:
	// 根节点
	Node* _root = nullptr;
	// 记录旋转次数
	int RotateCount = 0;

};

insert的实现

实现思路

二叉树的插入逻辑 + 更新平衡因子

bool Insert(const pair<K, V>& kv)
{
	//二叉搜索树的插入逻辑
	
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = _root;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	// 链接
	cur = new Node(kv);
	if (cur->_kv.first > parent->_kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	parent = cur->_parent;

	// 更新平衡因子
	// ... ...
}

那重点就是 如何更新平衡因子 :

首先, 先明确; 新插入的节点不影响自己的平衡因子, 只会影响父亲节点到 root这一段的节点的平衡因子
[C++随笔录] AVL树_第4张图片

其次, 要讨论插入节点的位置

  1. 新插在左 — — parent的平衡因子减减
  2. 新插在右 — — parent的平衡因子加加

最后, 也要讨论插入后的parent的平衡因子

  1. 更新后的parent的平衡因子为 1 或 -1 — — 继续往上更新
  2. 更新后的parent的平衡因子为 0 — — 停止更新
  3. 更新后的parent的平衡因子为 2 或 -2 — — 需要旋转 — — 旋转后停止更新

[C++随笔录] AVL树_第5张图片

这里有几个问题:
️为什么更新后的parent的平衡因子等于 1 或 -1, 要继续往上更新?

  • 更新后的parent等于 0, 说明新插入的节点并不会影响parent的高度差
    如果更新后的parent等于 1 或 -1, 说明新插入的节点影响到了parent的高度差, 有可能也会影响到parent上面的高度差 ⇒ 所以, 我们要继续向上更新, 直至 parent的平衡因子为 0 或 更新到了root

️ 为什么更新后的parent等于空, 也要停止更新呢?

  • 首先, 只有root 的父亲节点才是 空
    其次,我们最差的更新情况是 更新到root节点

️ 为什么旋转后就停止更新了呢?

  • 结合后面的 旋转逻辑 来进行讲解

insert的主体结构

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = _root;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	// 链接
	cur = new Node(kv);
	if (cur->_kv.first > parent->_kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	parent = cur->_parent;

	// 更新平衡因子
	while (parent) // 最差更新到root节点
	{
		// 1. 先更新一下parent
		// 新插在右
		if (parent->_right == cur)
		{
			parent->_bf++;
		}
		else // 新插在左
		{
			parent->_bf--;
		}

		// 2. 检查更新后的parent是否影响平衡
		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)
		{
			// 旋转的逻辑
			// ...
			// ...
			
			// 旋转后停止更新平衡因子
			break;
		}
	}

	return true;
}

旋转逻辑

原本的树形结构符合AVL树的特点, 如果插入一个新节点 造成不平衡了, 即parent的平衡因子 等于 2 或 -2了, 这时候就要进行旋转. 根据 插入节点的位置, 一共有四种情况:

  1. 新节点插入到较高左子树的左侧 — — 左左 — — 对parent进行右旋
    [C++随笔录] AVL树_第6张图片

核心操作: 让cur的右节点充当parent的左节点, 然后让parent整体充当cur的右节点

  • 原理 :
    左边偏高 — — 想办法让左边的高度降下来 — — 把cur的左右高度差降低
    每次旋转, 也要维持 搜索树的特性 — — 中序遍历是有序的 — — cur的右节点 (b) 充当parent的左节点是合理的, parent充当 cur的右也是合理的
    ⇒ 这样以来: cur的左右子树的高度是相等的, 都为 h+1;
void RotateL(Node* parent)
{
	// 每次旋转都++
	++RotateCount;

	// 提前保存grandfather节点, 保证后面的链接是正确的
	Node* cur = parent->_right;
	Node* grandfather = parent->_parent;
	Node* curleft = cur->_left;

	// 旋转核心
	parent->_right = curleft;
	cur->_left = parent;

	// 更新父亲
	// 1. parent && curleft
	if (curleft)
	{
		curleft->_parent = parent;
	}
	parent->_parent = cur;

	// 2.更新cur
	// cur要充当起parent的责任, 向上进行连接
	if (grandfather == nullptr)
	{
		cur->_parent = nullptr;
		_root = cur;
	}
	else
	{
		// 判读cur应该位于grandfather节点的哪一侧
		// 1. 向下进行链接
		if (grandfather->_left == parent)
		{
			grandfather->_left = cur;
		}
		else
		{
			grandfather->_right = cur;
		}
		
		// 2. 向上进行链接
		cur->_parent = grandfather;
	}

	// 更新平衡因子
	cur->_bf = parent->_bf = 0;
}
  1. 新节点插入到较高右子树的右侧 — — 右右
    [C++随笔录] AVL树_第7张图片
void RotateR(Node* parent)
{
	++RotateCount;

	Node* cur = parent->_left;
	Node* grandfather = parent->_parent;
	Node* curright = cur->_right;

	// 旋转核心
	parent->_left = curright;
	cur->_right = parent;

	// 更新链接关系
	// 1. parent && curright
	if (curright)
	{
		curright->_parent = parent;
	}
	parent->_parent = cur;

	// 2.更新cur
	if (grandfather == nullptr)
	{
		cur->_parent = nullptr;
		_root = cur;
	}
	else
	{
		if (grandfather->_left == parent)
		{
			grandfather->_left = cur;
		}
		else
		{
			grandfather->_right = cur;
		}

		cur->_parent = grandfather;
	}

	cur->_bf = parent->_bf = 0;
}
  1. 新节点插入到较高左子树的右侧 — — 左右 — — 先对cur左旋, 再对parent右旋
    [C++随笔录] AVL树_第8张图片
  • 其实 左右双旋的本质 是:
    把curright的左子树(b)充当cur的右子树,
    把curright的右子树(c)充当parent的左子树,
    然后curright充当根节点, cur 和 parent分别充当左右子树

⇒ 那么 更新平衡因子, 也是要看curright的左右子树(b 和 c的高度情况) :

  1. h = 0 ⇒ curright 为新增
    [C++随笔录] AVL树_第9张图片
cur->bf = 0;
curright->_bf = 0;
parent->_bf = 0;
  1. h > 0 ⇒ 新增在 b 或 c
    (1)新增在 b
    [C++随笔录] AVL树_第10张图片
cur->bf = 0;
curright->_bf = 0;
parent->_bf = 1;

(2)新增在 c
[C++随笔录] AVL树_第11张图片

cur->bf = -1;
curright->_bf = 0;
parent->_bf = 0;

左右双旋的完整代码

void RotateLR(Node* parent)
{
	// 提前保存一份, 后面的左右旋转中会发生变化的
	Node* cur = parent->_left;
	Node* curright = cur->_right;

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

	// 更新平衡因子
	if (curright->_bf == 0)
	{
		cur->_bf = 0;
		parent->_bf = 0;
		curright->_bf = 0;
	}
	else if (curright->_bf == 1)
	{
		cur->_bf = -1;
		parent->_bf = 0;
		curright->_bf = 0;
	}
	else if (curright->_bf == -1)
	{
		cur->_bf = 0;
		parent->_bf = 1;
		curright->_bf = 0;
	}
}
  1. 新节点插入到较高右子树的左侧 — — 右左 — — 先对cur右旋, 再对parent左旋
    [C++随笔录] AVL树_第12张图片
  • 其实, 右左双旋的本质是:
    把curleft的左子树(b) 充当 parent的右子树
    把curleft的右子树(c) 充当cur的左子树
    让curleft来根节点, 让parent 和 cur分别充当curleft的左右子树

⇒ 那么 更新平衡因子, 也是要看curleft的左右子树(b 和 c的高度情况) :

  1. h = 0 ⇒ curleft为新增
    [C++随笔录] AVL树_第13张图片
cur->bf = 0;
curleft->_bf = 0;
parent->_bf = 1;
  1. h > 0 ⇒ 新增在b / c
    (1) 新增在 b
    [C++随笔录] AVL树_第14张图片
cur->bf = 1;
curleft->_bf = 0;
parent->_bf = 0;

(2) 新增在 c
[C++随笔录] AVL树_第15张图片

cur->bf = 0;
curleft->_bf = 0;
parent->_bf = -1;

右左双旋的完整代码

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

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

	// 更新平衡因子
	if (curleft->_bf == 0)
	{
		cur->_bf = 0;
		parent->_bf = 0;
		curleft->_bf = 0;
	}
	else if (curleft->_bf == 1)
	{
		cur->_bf = 0;
		parent->_bf = -1;
		curleft->_bf = 0;
	}
	else if (curleft->_bf == -1)
	{
		cur->_bf = 1;
		parent->_bf = 0;
		curleft->_bf = 0;
	}
}

insert的完整代码

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}

	Node* parent = _root;
	Node* cur = _root;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	// 链接
	cur = new Node(kv);
	if (cur->_kv.first > parent->_kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	parent = cur->_parent;

	// 更新平衡因子
	while (parent)
	{
		// 1. 先更新一下parent
		// 新插在右
		if (parent->_right == cur)
		{
			parent->_bf++;
		}
		else // 新插在左
		{
			parent->_bf--;
		}

		// 2. 检查更新后的parent是否影响平衡
		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)
			{
				RotateRL(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == -1)
			{
				RotateR(parent);
			}
			else if (parent->_bf == -2 && cur->_bf == 1)
			{
				RotateLR(parent);
			}
			else
			{
				assert("平衡因子更新错误!");
			}
			
			// 旋转结束, 就停止更新平衡因子
			break;
		}
	}

	return true;
}

insert的验证

  1. 检查每棵子树的高度差的绝对值小于 1
  2. 检查平衡因子是否等于左右子树的高度差 ( 我们算的平衡因子有可能不对)

检查程序

int Height()
{
	return Height(_root);
}

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

	int left = Height(root->_left);
	int right = Height(root->_right);

	return left > right ? left + 1 : right + 1;
}

bool Isbalance()
{
	return Isbalance(_root);
}

bool Isbalance(Node* root)
{
	if (root == nullptr)
		return true;

	int lheight = Height(root->_left);
	int rheight = Height(root->_right);
	
	if (root->_bf != rheight - lheight || abs(rheight - lheight) > 1)
	{
		cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
		return false;
	}
	
	// 继续检查下一个支树
	return Isbalance(root->_left) && Isbalance(root->_right);
}

GetRotateCount

int GetRoateCount()
{
	return RotateCount;
}

验证代码:

void avl_test()
{
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	// srand((unsigned int)time(nullptr));

	for (size_t i = 0; i < N; i++)
	{
		// int ret = rand();
		// v.push_back(ret);

		v.push_back(i);
	}

	muyu::AVLTree<int, int> avl;
	for (auto e : v)
	{
		avl.Insert(make_pair(e, e));
	}
	
	cout << "AVL树是否达标-> " << avl.Isbalance() << endl;
	cout << "AVL树的高度-> " << avl.Height() << endl;
	cout << "AVL树旋转的次数-> " << avl.GetRoateCount() << endl;

}

int main()
{
	avl_test();
	
	return 0;
}

运行结果:

AVL树是否达标-> 1
AVL树的高度-> 17
AVL树旋转的次数-> 99983

源码

#pragma once

#include
#include

using namespace std;

namespace muyu
{
	template<class K, class V>
	struct AVLTreeNode
	{
	public:
		AVLTreeNode(const pair<K,V>& kv)
			:_kv(kv)
		{}

	public:
		pair<K, V> _kv;
		AVLTreeNode<K, V>* _left = nullptr;
		AVLTreeNode<K, V>* _right = nullptr;
		AVLTreeNode<K, V>* _parent = nullptr;
		int _bf = 0;
	};

	template<class K, class V>
	class AVLTree
	{
		typedef AVLTreeNode<K, V> Node;

	public:
		AVLTree()
			:_root(nullptr)
		{}

		void RotateL(Node* parent)
		{
			// 每次旋转都++
			++RotateCount;

			// 提前保存grandfather节点, 保证后面的链接是正确的
			Node* cur = parent->_right;
			Node* grandfather = parent->_parent;
			Node* curleft = cur->_left;

			// 旋转核心
			parent->_right = curleft;
			cur->_left = parent;

			// 更新父亲
			// 1. parent && curleft
			if (curleft)
			{
				curleft->_parent = parent;
			}
			parent->_parent = cur;

			// 2.更新cur
			// cur要充当起parent的责任, 向上进行连接
			if (grandfather == nullptr)
			{
				cur->_parent = nullptr;
				_root = cur;
			}
			else
			{
				// 判读cur应该位于grandfather节点的哪一侧
				// 1. 向下进行链接
				if (grandfather->_left == parent)
				{
					grandfather->_left = cur;
				}
				else
				{
					grandfather->_right = cur;
				}
				
				// 2. 向上进行链接
				cur->_parent = grandfather;
			}

			// 更新平衡因子
			cur->_bf = parent->_bf = 0;
		}

		void RotateR(Node* parent)
		{
			++RotateCount;

			Node* cur = parent->_left;
			Node* grandfather = parent->_parent;
			Node* curright = cur->_right;

			// 旋转核心
			parent->_left = curright;
			cur->_right = parent;

			// 更新链接关系
			// 1. parent && curright
			if (curright)
			{
				curright->_parent = parent;
			}
			parent->_parent = cur;

			// 2.更新cur
			if (grandfather == nullptr)
			{
				cur->_parent = nullptr;
				_root = cur;
			}
			else
			{
				if (grandfather->_left == parent)
				{
					grandfather->_left = cur;
				}
				else
				{
					grandfather->_right = cur;
				}

				cur->_parent = grandfather;
			}

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

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

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

			// 更新平衡因子
			if (curleft->_bf == 0)
			{
				cur->_bf = 0;
				parent->_bf = 0;
				curleft->_bf = 0;
			}
			else if (curleft->_bf == 1)
			{
				cur->_bf = 0;
				parent->_bf = -1;
				curleft->_bf = 0;
			}
			else if (curleft->_bf == -1)
			{
				cur->_bf = 1;
				parent->_bf = 0;
				curleft->_bf = 0;
			}
		}

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

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

			// 更新平衡因子
			if (curright->_bf == 0)
			{
				cur->_bf = 0;
				parent->_bf = 0;
				curright->_bf = 0;
			}
			else if (curright->_bf == 1)
			{
				cur->_bf = -1;
				parent->_bf = 0;
				curright->_bf = 0;
			}
			else if (curright->_bf == -1)
			{
				cur->_bf = 0;
				parent->_bf = 1;
				curright->_bf = 0;
			}
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				return true;
			}

			Node* parent = _root;
			Node* cur = _root;
			while (cur)
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			// 链接
			cur = new Node(kv);
			if (cur->_kv.first > parent->_kv.first)
			{
				parent->_right = cur;
				cur->_parent = parent;
			}
			else
			{
				parent->_left = cur;
				cur->_parent = parent;
			}
			parent = cur->_parent;

			// 更新平衡因子
			while (parent)
			{
				// 1. 先更新一下parent
				// 新插在右
				if (parent->_right == cur)
				{
					parent->_bf++;
				}
				else // 新插在左
				{
					parent->_bf--;
				}

				// 2. 检查更新后的parent是否影响平衡
				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)
					{
						RotateRL(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == -1)
					{
						RotateR(parent);
					}
					else if (parent->_bf == -2 && cur->_bf == 1)
					{
						RotateLR(parent);
					}
					else
					{
						assert("平衡因子更新错误!");
					}

					break;
				}
			}

			return true;
		}

		int Height()
		{
			return Height(_root);
		}

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

			int left = Height(root->_left);
			int right = Height(root->_right);

			return left > right ? left + 1 : right + 1;
		}

		bool Isbalance()
		{
			return Isbalance(_root);
		}

		bool Isbalance(Node* root)
		{
			if (root == nullptr)
				return true;

			int lheight = Height(root->_left);
			int rheight = Height(root->_right);

			if (root->_bf != rheight - lheight || abs(rheight - lheight) > 1)
			{
				cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
				return false;
			}

			return Isbalance(root->_left) && Isbalance(root->_right);
		}

		int GetRoateCount()
		{
			return RotateCount;
		}

	private:
		Node* _root = nullptr;
		int RotateCount = 0;

	};
}

富家不用买良田,书中自有千钟粟。
安居不用架高堂,书中自有黄金屋。
出门无车毋须恨,书中有马多如簇。
娶妻无媒毋须恨,书中有女颜如玉。
男儿欲遂平生志,勤向窗前读六经。
— — 赵恒《劝学诗》

你可能感兴趣的:(C++,c++,算法)