C++ - AVL (二叉平衡搜索树) 解析

二叉平衡搜索树概念

上一篇博客中讲解了二叉搜索树: C++ - 二叉搜索树讲解-优快云博客
我们了解到, 二叉搜索树的最坏时间复杂度为 O(N). (插入的顺序是有序的)
所以在实际中, 直接使用二叉搜索树的情况很少.

那么为了解决二叉搜索树这种最坏情况, 就有大佬提出了 AVL 树.

AVL 树又称为 二叉平衡搜索树, 可以看到, 多了两个字 "平衡".
那么平衡是什么意思: 
之前的二叉搜索树在插入顺序是有序的情况下, 会形成一个链表的结构, 从而导致查找的时间复杂度变为 O(N).
那么如果我们在插入的时候, 顺便调整这棵树, 让它始终保持一个利于查询的形态, 那么问题就解决了.

二叉平衡搜索树特性

"平衡": 要保证任意一个节点的左右两边的子树的高度差不超过 1 ( |右树高度 - 左树高度| <= 1).

当我们规定了每个节点的左右子树高度差不超过 1 之后,

就能使得二叉树的搜索的时间复杂度稳定在 O(log n)了,

当我们插入节点破坏 "平衡之后", 就需要去进行 "旋转" 来调节树

二叉平衡搜索树基本框架

AVL 树的节点的定义: 
和之前的搜索二叉树比较, 多了两个值: 
1. 一个平衡因子 (_balancefactor )
2. 指向父节点的指针 (_parent)

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const std::pair<K, V>& kv)
		:_data(kv)
	{}

	~AVLTreeNode()
	{}

	AVLTreeNode<K, V>* _left = nullptr; // 指向左孩子
	AVLTreeNode<K, V>* _right = nullptr; // 指向右孩子
	AVLTreeNode<K, V>* _parent = nullptr; // 指向父节点
	std::pair<K, V> _data; // 存储数据
	int _balancefactor = 0; // 平衡因子
};

AVL 树定义

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
	{}

	~AVLTree()
	{}
private:
	Node* _root = nullptr; // 根节点
};

二叉平衡搜索树的插入

插入总结有三个步骤:

  1. 按照二叉树规则找到插入位置, 并插入数据
  2. 数据插入完毕后, 更新平衡因子
  3. 检查平衡因子, 平衡因子不正确, 采取相应的措施

更新平衡因子:

  1. 如果新插入节点在左边, 那么父节点 balance - 1
    新增节点在右边, 那么父节点 balance + 1
  2. 更新完成后, 如果父节点的 balance != 0
    那么说明数的高度发生了变化, 需要向上更新

可以看到, 当我们插入 0 这个节点后, 5 节点的左右子树高度差超过了 1,
所以接下来我们就需要去调整树的结构 "旋转".

1. 更新 balance

旋转过程比较复杂, 这里先给出更新 balance 的代码.

bool insert(const std::pair<K, V>& kv)
{
	if (_root == nullptr) // 如果树为空, 那么直接插入, 并且要更新根节点
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr; // 记录要插入位置的父节点
	Node* cur = _root; // 查找要插入的位置
	while (cur)
	{
		if (cur->_data.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_data.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}

	if (parent->_data.first > kv.first) // 判断要插入在 parent 的左边还是右边
	{
		cur = new Node(kv);
		parent->_left = cur;
		cur->_parent = parent;
	}
	else
	{
		cur = new Node(kv);
		parent->_right = cur;
		cur->_parent = parent;
	}

	while (parent) // 插入完成后, 需要更新可能会被影响的父节点的平衡因子
	{
		if (parent->_left == cur) // 如果插入的节点位于 parent 的左边, 那么 balance - 1
		{
			--parent->_balancefactor;
		}
		else // 如果插入的节点位于 parent 的左边, 那么 balance + 1
		{
			++parent->_balancefactor;
		}

     // 如果 balance 更新完成后为 0, 说明对于 parent 来说树的高度没有发生变化, 可以直接返回
		if (parent->_balancefactor == 0)
		{
			return true;
		}
		else if (parent->_balancefactor == 1 || parent->_balancefactor == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_balancefactor == 2 || parent->_balancefactor == -2)
		{
            // 此时树已经不平衡了需要进行旋转, 这里是旋转的代码
		}
		else
		{
			perror("_balancefactor error\n");
			assert(false);
		}
	}
	return true;
}

2. 旋转调整

旋转又存在着四种旋转方式:

  1. 左单旋
  2. 右单旋
  3. 左右双旋
  4. 右左双旋

首先来看简单的两种旋转方式: 左单旋和右单旋.

下面给出这两种双旋的代码, 代码中的变量和图中变量也是对应的, 方便理解

void RotateL(Node* parent) // 左旋
{
	Node* subr = parent->_right;
	Node* subrl = subr->_left; // 上面的图中, 是没有画这个节点的, 但是有可能会存在这个点的
	if (_root == parent) // 
	{
		_root = subr;
		subr->_parent = nullptr;
		subr->_left = parent;
		parent->_parent = subr;
		parent->_right = subrl;
		if (subrl != nullptr) // 判断这个点是否存在, 如果存在则也需要连接上
		{
			subrl->_parent = parent;
		}
	}
	else
	{
		Node* pparent = parent->_parent;
		if (pparent->_left == parent)
		{
			pparent->_left = subr;
		}
		else
		{
			pparent->_right = subr;
		}
		subr->_parent = pparent;
		subr->_left = parent;
		parent->_parent = subr;
		parent->_right = subrl;
		if (subrl != nullptr)
		{
			subrl->_parent = parent;
		}
	}
	parent->_balancefactor = subr->_balancefactor = 0;
}

void RotateR(Node* parent) // 右旋
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;
	if (parent == _root)
	{
		_root = subl;
		subl->_parent = nullptr;
		subl->_right = parent;
		parent->_parent = subl;
		parent->_left = sublr;
		if (sublr != nullptr)
		{
			sublr->_parent = parent;
		}
	}
	else
	{
		Node* pparent = parent->_parent;
		if (pparent->_left == parent)
		{
			pparent->_left = subl;
		}
		else
		{
			pparent->_right = subl;
		}
		subl->_parent = pparent;
		subl->_right = parent;
		parent->_left = sublr;
		parent->_parent = subl;
		if (sublr != nullptr)
		{
			sublr->_parent = parent;
		}
	}
	parent->_balancefactor = subl->_balancefactor = 0;
}

 左右双旋适用于一下所有情况:

左单旋

右单旋

接下来还有左右双旋和右左双旋: 
左右双旋: 先左旋, 再右旋
右左双旋: 先右旋, 再左旋

 

void RotateLR(Node* parent)
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;
	int bf = sublr->_balancefactor;
	RotateL(parent->_left);
	RotateR(parent);
	if (bf == 0)
	{
		parent->_balancefactor = subl->_balancefactor = 0;
	}
	else if (bf == 1)
	{
		subl->_balancefactor = -1;
	}
	else if (bf == -1)
	{
		parent->_balancefactor = 1;
	}
}

void RotateRL(Node* parent)
{
	Node* subr = parent->_right;
	Node* subrl = subr->_left;
	int bf = subrl->_balancefactor;
	RotateR(parent->_right);
	RotateL(parent);
	if (bf == 0)
	{
		parent->_balancefactor = subr->_balancefactor = 0;
	}
	else if (bf == 1)
	{
		parent->_balancefactor = -1;
		subrl = 0;
	}
	else if (bf == -1)
	{
		subr->_balancefactor = 1;
	}
}

左右双旋: 

右左双旋: 

 

 插入完整的代码放在这里

bool insert(const std::pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_data.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}

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

	while (parent)
	{
		if (parent->_left == cur)
		{
			--parent->_balancefactor;
		}
		else
		{
			++parent->_balancefactor;
		}

		if (parent->_balancefactor == 0)
		{
			return true;
		}
		else if (parent->_balancefactor == 1 || parent->_balancefactor == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_balancefactor == 2 || parent->_balancefactor == -2)
		{
            // 旋转代码
			if (parent->_balancefactor == 2 && parent->_right->_balancefactor == 1)
			{
				RotateL(parent);
			}
			else if (parent->_balancefactor == -2 && parent->_left->_balancefactor == -1)
			{
				RotateR(parent);
			}
			else if (parent->_balancefactor == 2 && parent->_right->_balancefactor == -1)
			{
				RotateRL(parent);
			}
			else if(parent->_balancefactor == -2 && parent->_left->_balancefactor == 1)
			{
				RotateLR(parent);
			}
			break;
		}
		else
		{
			perror("_balancefactor error\n");
			assert(false);
		}
	}
	return true;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值