【C++】模拟实现AVL树

一、前言

        本文将会构建一个更通俗易懂的AVL树,保证小白也能看懂,通过画图厘清AVL树的插入实现过程,代码环境为VS2019。

二、AVL树的概念

        对于普通的二叉搜索树而言,虽然可以缩短查找的效率,但如果很多甚至全部的数据有序,那么这个二叉搜索树将会退化为单支树,查找效率大大降低(变成O(n))。在此基础上,有两位俄罗斯的数学家G.M.Adelson-Velskii 和 E.M.Landis 设计了这样一种算法:当在二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度差绝对值不超过1,那么就可以降低树的高度,降低树的搜索次数,这就是AVL树。

三、AVL树节点的定义

        本文中模拟的AVL树中存储的数据固定为pair类型。和普通二叉搜索树不一样,AVL树中多定义了一个int类型的变量_ef,这个变量是AVL树每个节点的平衡因子,当这个节点的左树高度高于右树高度n个节点时,平衡因子大小为-n,平衡因子和保存的父节点一样,都是为后续对树的调整做准备。

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V>& data)
		:_ef(0)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_data(data)
	{}

	int _ef;
	pair<K, V> _data;

	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
};

四、AVL树的插入

1.找到并插入节点

        第一步的插入和普通的二叉搜索树一样,本文在此不过多赘述,代码如下:

// 插入
bool Insert(const pair<K, V>& data)
{
	// 排除极端情况
	if (_root == nullptr)
	{
		_root = new Node(data);
		return true;
	}

	// 找到最终插入节点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data.first == data.first)
			break;
		else if (cur->_data.first > data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
	}

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

	// 重新确认平衡因子


	return true;
}

2.平衡AVL树

判断是否需要执行平衡操作

        首先明白一个原理:每个节点都有平衡因子,初始为0,当插入的节点为左节点时,父节点平衡因子-1;插入为右节点时,父节点平衡因子+1。

        执行了插入操作之后,父节点此时存在三种情况,平衡因子分为是:①为0、②正负1、③正负2

①这说明在插入新节点之前,父节点的平衡因子为正负1的一种,插入之后为0,爷爷节点及以上节点都不会受到影响。这时是一个完整的AVL树,直接结束判断即可;

②说明插入之前父节点平衡因子为0,及没有左右子树,在父节点更新平衡因子后一定会影响到父节点,此时需要迭代当前节点和父节点,向上继续调整祖宗节点平衡因子,直到情况③或情况①为止;

③这种情况说明插入新节点后父节点平衡因子已经超出,需要进行重新调整操作(注:此处指的父节点也可能是迭代后的父节点)。

// 重新确认平衡因子
while (parent)
{
	if (parent->_left == cur)
		parent->_ef--;
	else if (parent->_right == cur)
		parent->_ef++;

	// 更新完成
	if (parent->_ef == 0)
		break;
	// 并没有更新完
	else if (abs(parent->_ef) == 1)
	{
		cur = parent;
		parent = cur->_parent;
	}
	// 失衡,进行调整
	else if (abs(parent->_ef) == 2)
	{

	}
	else
	{
		assert(false);
	}
}

平衡平衡因子

        此时的树一定是不平衡的,如果要树保持平衡的话,可以对树进行旋转操作,旋转分为左单转,右单旋,左双旋,右双旋。

①新节点插入在较高左子树的左侧 —— 右单旋

        此处使用了最为简单的例子,实际情况中插入新节点后需要进行旋转的很可能不是新插入节点,有可能是迭代后的子树。但无论是什么,都适用于以上旋转,只要保持平衡因子绝对值不过2就能实现。 具体代码段如下:

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

	parent->_left = cur->_right;

	// 细节处理:若存在cur的右子树,将cur右子树的父亲指向parent
	if (curright)
		curright->_parent = parent;

	// 细节处理:祖父节点子树指向和被指向的更新
	Node* pparent = parent->_parent;
	cur->_right = parent;
	parent->_parent = cur;

	if (pparent == nullptr)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = cur;
		}
		else
		{
			pparent->_right = cur;
		}
		cur->_parent = pparent;
	}
	// 细节处理:更新平衡因子
	cur->_ef = parent->_ef = 0;
}
②新节点插入在较高右子树的右侧 —— 左单旋

        这种情况和右单旋基本没有区别,照葫芦画瓢,代码段如下:

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

	parent->_right = cur->_left;

	if (curleft)
		curleft->_parent = parent;

	Node* pparent = parent->_parent;
	cur->_left = parent;
	parent->_parent = cur;

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

	cur->_ef = parent->_ef = 0;
}
 ③新节点插入在较高左子树的右侧 —— 先左单旋,再右单旋(右双旋)

        这种情况,需要先对cur节点进行左单旋,再对parent节点右单旋——这很简单,直接复用已经写过的左右单旋函数即可。但实际上并没有结束,重要的是对更新后节点平衡因子的更新,此处只列举出了其中一种情况,其他两种情况将在代码中实现,感兴趣的各位可以自行去画图验证~

// 右双旋
void RotateLR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;
	// 左旋cur,再右旋parent
	RotateL(cur);
	RotateR(parent);

	// 确定平衡因子
	if (curright->_ef == 0)
	{
		parent->_ef = cur->_ef = curright->_ef = 0;
	}
	else if (curright->_ef == -1)
	{
		curright->_ef = cur->_ef = 0;
		parent->_ef = 1;
	}
	else if (curright->_ef == 1)
	{
		curright->_ef = parent->_ef = 0;
		cur->_ef = 1;
	}
	else
	{
		assert(false);
	}
}
  ④新节点插入在较高右子树的左侧 —— 先右单旋,再左单旋(左双旋)

        这一段和右双旋原理相同,关键在于旋转后平衡因子的重置,这点需要自己画图验证,代码实现如下:

// 左双旋
void RotateRL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;
	// 右旋cur,再左旋parent
	RotateR(cur);
	RotateL(parent);

	// 确定平衡因子
	if (curleft->_ef == 0)
	{
		parent->_ef = cur->_ef = curleft->_ef = 0;
	}
	else if (curleft->_ef == -1)
	{
		parent->_ef = curleft->_ef = 0;
		cur->_ef = 1;
	}
	else if (curleft->_ef == 1)
	{
		curleft->_ef = cur->_ef = 0;
		parent->_ef = -1;
	}
	else
	{
		assert(false);
	}
}

        解决掉所有插入后需要平衡的情况后,一颗能保证绝对平衡的二叉搜索树就此诞生啦!希望这篇文章能对您的学习有所收获。

五、源码分享

#pragma once

#include <iostream>
#include <assert.h>
using namespace std;

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V>& data)
		:_ef(0)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_data(data)
	{}

	int _ef;
	pair<K, V> _data;

	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 插入
	bool Insert(const pair<K, V>& data)
	{
		// 排除极端情况
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}
		
		// 找到最终插入节点
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data.first == data.first)
				break;
			else if (cur->_data.first > data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_data.first < data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
		}

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

		// 重新确认平衡因子
		while (parent)
		{
			if (parent->_left == cur)
				parent->_ef--;
			else if (parent->_right == cur)
				parent->_ef++;

			// 更新完成
			if (parent->_ef == 0)
				break;
			// 并没有更新完
			else if (abs(parent->_ef) == 1)
			{
				cur = parent;
				parent = cur->_parent;
			}
			// 失衡,进行调整
			else if (abs(parent->_ef) == 2)
			{
				if (parent->_ef == 2 && cur->_ef == 1)
					RotateL(parent);
				else if (parent->_ef == -2 && cur->_ef == -1)
					RotateR(parent);
				else if (parent->_ef == 2 && cur->_ef == -1)
					RotateRL(parent);
				else if (parent->_ef == -2 && cur->_ef == 1)
					RotateLR(parent);
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

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

		parent->_right = cur->_left;
		if (curleft)
			curleft->_parent = parent;

		Node* pparent = parent->_parent;
		cur->_left = parent;
		parent->_parent = cur;

		if (pparent == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = cur;
			}
			else
			{
				pparent->_right = cur;
			}
			cur->_parent = pparent;
		}
		cur->_ef = parent->_ef = 0;
	}
	
	// 右单旋
	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		
		parent->_left = cur->_right;
		if (curright)
			curright->_parent = parent;

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

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

	// 左双旋
	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		// 右旋cur,再左旋parent
		RotateR(cur);
		RotateL(parent);

		// 确定平衡因子
		if (curleft->_ef == 0)
		{
			parent->_ef = cur->_ef = curleft->_ef = 0;
		}
		else if (curleft->_ef == -1)
		{
			parent->_ef = curleft->_ef = 0;
			cur->_ef = 1;
		}
		else if (curleft->_ef == 1)
		{
			curleft->_ef = cur->_ef = 0;
			parent->_ef = -1;
		}
		else
		{
			assert(false);
		}
	}
	
	// 右双旋
	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		// 左旋cur,再右旋parent
		RotateL(cur);
		RotateR(parent);

		// 确定平衡因子
		if (curright->_ef == 0)
		{
			parent->_ef = cur->_ef = curright->_ef = 0;
		}
		else if (curright->_ef == -1)
		{
			curright->_ef = cur->_ef = 0;
			parent->_ef = 1;
		}
		else if (curright->_ef == 1)
		{
			curright->_ef = parent->_ef = 0;
			cur->_ef = 1;
		}
		else
		{
			assert(false);
		}
	}
private:
	Node* _root = nullptr;
};

猜你喜欢

转载自blog.csdn.net/qq_74641564/article/details/132984121