AVL树的C++实现

引言

       学习了map/multimap/set/multiset等容器之后,我们知道这几个容器的底层实现都是按照二叉搜索树来实现的,但是二叉搜索树也有着自身的缺陷,比如我们如果往树中插入的元素有序或者接近有序的话,二叉搜索树就会退化成单支树,查找元素就相当于在顺序表中查找元素,时间复杂度会退化成O(N),因此map,set等关联式容器的底层结构都是对二叉树进行了平衡处理,即由此引出了平衡二叉树。

一、AVL树的概念

AVL树是于1962年由两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis发明的一种解决二叉搜索树退化成单支树的方法。该方法为:在我们向二叉搜索树中插入新结点后,如果我们能保证每个结点的左右子树高度之差的绝对值不超过1(不能为0,考虑偶数个结点),则可以降低树的高度,从而减少我们的平均搜索长度。

一颗AVL树要么是空树,要么有着以下的特点

  • 它的左右子树都是AVL树
  • 它的左右子树的高度之差(右子树的高度-左子树的高度)的绝对值不超过2

二、AVL树的简单实现

 AVL树的结点定义

首先我们要实现的是关联式容器,与序列式容器不同,其里面存储的是<key,value>的结构的键值对,这种存储在数据检索时比序列式容器的效率更高。

其次,我们构造一个三叉链,除了定义两个孩子结点,还需要一个指向父亲的结点。

接着,我们还需要一个平衡因子,方便我们监视和调整结点的子树的高度以满足平衡。

所以我们给出下列类模板

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;//三叉链
	int _bf;//balance factor平衡因子
	pair<K, V> _kv;//存储的键值对
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
		, _kv(kv)
	{}
};

AVL树的插入

对于AVL树的插入,我们考虑如下步骤

  1. 先将该结点按照二叉搜索树的规则插入到树中
  2. 插入完成后更新平衡因子
  3. 对插入结点的父亲结点不同的的平衡因子做出不同的处理
  4. 当父亲结点的平衡因子为0时
  5. 当父亲结点的平衡因子为-1或1时
  6. 当父亲结点的平衡因子为-2或者2时,这里需要考虑旋转

插入

从头结点开始,我们使用键值对中的first来比较大小,遇到大的向左走,反之向右走。

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

更新平衡因子 

这里不用考虑该结点的插入位置,每次插入都是该树的叶子结点,无论是哪个叶子结点首先影响的是该结点的父亲结点,当结点插入到右边时则父亲结点的平衡因子加一,否则减一。 

if (cur == parent->_right)
	parent->_bf++;
else
	parent->_bf--;

分析平衡因子的不同对树的影响

经过分析我们知道在我们插入一个结点时,其影响的只会是其祖先的平衡因子,其他结点并不会受到影响,且并不是该点的所有祖先都会被它影响,所以这里我们将父亲结点的平衡因子分为三种情况来讨论:

1、更新后父亲结点的平衡因子是0

在插入新结点更新后的父亲的平衡因子为0,说明在插入前父亲的平衡因子为1或者-1,即该结点只有一个左叶子结点或者一个右叶子结点。此时再插入一个恰好使其平衡因子为0,说明新结点插入到了没有叶子结点的一边,此时父亲结点的高度并没有变化,所以它并不会影响到之后的祖先。

if (parent->_bf == 0)
{
	break;
}

2、更新后父亲结点的平衡因子是-1/1 

在插入新结点更新后的父亲的平衡因子为-1或1,说明在插入前父亲的平衡因子为0,说明父亲结点原来没有叶子结点,此时新插入的结点会影响到父亲节点及之后祖先的高度,这里需要不断向上更新祖先结点的平衡因子,观察祖先结点的平衡因子是否有问题。

else if(parent->_bf==1||parent->_bf==-1)
{
	cur = parent;
	parent=parent->_parent;
}

3、更新后父亲结点的平衡因子为2/-2

如果出现平衡因子为2或-2的情况,说明该结点不平衡,需要做旋转处理。根据新结点插入的位置不同,我们将其分成四种情况来讨论,对于这四种情况分别使用的不同的方法来解决问题。

1)新结点插入到较高左子树的左侧

当在较高左子树的左侧插入新结点后,该点失去平衡,需要调整。此时我们需要将左子树高度减一,右子树的高度增加一, 由于我们本身就是以二叉搜索树建树,所以满足每个结点的左子树都比其小,右子树都比其大,所以我们考虑将y作为6结点的左子树,6作为4结点的右子树,如下图所示

我们对平衡因子为-2 的点进行右旋转处理如下

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 (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subL;
		else
			pparent->_right = subL;
		subL->_parent = pparent;
	}
	subL->_bf = parent->_bf = 0;
}

2)新结点插入到较高右子树的右侧 

该情况与上一个情况类似,只不过是进行左单旋处理

 

void RotateL( Node* parent)
{
	Node* pparent = parent->_parent;//记录parent的父亲结点
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	parent->_right = subRL;
	if(subRL)
	   subRL->_parent = parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (_root == parent)//分情况讨论,如果parent是头节点
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else//parent是非头节点
	{
		if (parent == pparent->_left)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
	parent->_bf = subR->_bf = 0;
}

3)新结点插入到较高左子树的右侧

这里我们采用先对30进行左单旋,接着再对90进行右单旋。但是我们在这里需要注意的是在b点和在c点插入新结点或者考虑h为0的情况都会导致平衡树的高度变化,三种情况下旋转之后的结构是差不多的,但是其结点的平衡因子会因为情况的不同而不同。

所以我们考虑通过控制不同的平衡因子来区分不同的情况。上图是第一种情况,第二和第三种情况如下

 

void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	int bf = subLR->_bf;
	RotateL(subL);
	RotateR(parent);
	if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
		subLR->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
		subLR->_bf = 0;
	}
}

4)新结点插入到较高右子树的左侧

与上一小节讨论类似,不过这里我们采用的是先右单旋再左单旋的方法,同样是分三种情况来讨论,这里过程我们省略直接上代码

void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);
	if (bf == -1)
	{
		parent->_bf=0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		parent->_bf = -1;
		subRL->_bf = 0;
	}
	else if (bf==0)
	{
		subR->_bf = 0;
		parent->_bf = 0;
		subRL->_bf = 0;
	}
}

小结

到此,我们的针对平衡树的所有情况的旋转已经完成了,我们可以看到当插入的结点和其 父亲结点及父亲结点的父亲结点在一条直线上时,我们只需要单旋,如果是条折线的话我们则需要双旋。

中序遍历打印

由于其是一颗二叉搜索树,当按照中序遍历打印时,刚好是按照从小到大的顺序排出来的。

void _Inorder(Node* root)
{
	if (root == nullptr)
		return;
	_Inorder(root->_left);
	cout << root->_kv.first<< ":" << root->_kv.second << " ";
	_Inorder(root->_right);

}
void Inorder()
{
	_Inorder(_root);
	cout << endl;
}

由于类中的成员变量_root是私有的,不能在类外面直接传参,所以我们使用一个函数来调用另一个函数的子函数来解决这个问题。

 判断是否为平衡二叉树

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 _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	return abs(leftHeight - rightHeight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);

}
bool IsBalance()
{
	return _IsBalance(_root);
}

完整代码展示

如下

#pragma once
#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;//三叉链
	int _bf;//balance factor平衡因子
	pair<K, V> _kv;//存储的键值对
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
		, _kv(kv)
	{}
};
template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}
	/*~AVLTree()
	{
		Destory(_root);
	}*/
	bool Insert(const pair<K,V>& kv)
	{
		//先按搜索树的规则插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (cur->_kv.first > kv.first)	
				cur = cur->_left;
			else if (cur->_kv.first <= kv.first)//multi版本的
				cur = cur->_right;
		}
		//找到了
		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->_right)//节点插入到左边则平衡因子加一,否则减一
				parent->_bf++;
			else
				parent->_bf--;
			//判断平衡因子经过插入结点 后是否受影响
			if (parent->_bf == 0)//插入节点影响的只是他的祖先,且不一定所有祖先都会被影响,
				//如果插入之后该插入节点的父亲结点的平衡因子为0,
				// 则说明插入节点后父亲节点的高度没有变化,则它就不会对之后的祖先产生影响
			{
				break;
			}
			else if(parent->_bf==1||parent->_bf==-1)//说明之前parent节点的因子为0.再插入一个cur后才会变成1或-1,这样继续往上更新
			{
				cur = parent;
				parent=parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)//说明该结点不平衡,需要做旋转处理
			{
				//旋转处理
				if (parent->_bf == 2)
				{
					if (cur->_bf == 1)
					{
						//左旋
						RotateL(parent);
					}
					else if (cur->_bf == -1)
					{
						//右左双旋
						RotateRL(parent);

					}
				}
				if (parent->_bf == -2)
				{
					if (cur->_bf == -1)
					{
						RotateR(parent);
					}
					else if(cur->_bf==1)
					{
						//左右双旋
						RotateLR(parent);
					}
				}
				break;
			}	
		}
		return true;
	}
	//旋转处理的左单旋
	void RotateL( Node* parent)
	{
		Node* pparent = parent->_parent;//记录parent的父亲结点
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		parent->_right = subRL;
		if(subRL)
		   subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)//分情况讨论,如果parent是头节点
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else//parent是非头节点
		{
			if (parent == pparent->_left)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
		parent->_bf = subR->_bf = 0;
	}

	//右旋转
	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 (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		subL->_bf = parent->_bf = 0;
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(parent);
		if (bf == -1)
		{
			parent->_bf=0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
			subRL->_bf = 0;
		}
		else if (bf==0)
		{
			subR->_bf = 0;
			parent->_bf = 0;
			subRL->_bf = 0;
		}
	}
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first<< ":" << root->_kv.second << " ";
		_Inorder(root->_right);

	}
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;
		RotateL(subL);
		RotateR(parent);
		if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
	}
	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 _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		return abs(leftHeight - rightHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);

	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	Node* _root;
};
void Test_AVLTree()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e,e));
	}
	t.Inorder();
	cout << t.IsBalance() << endl;
}

 

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AVL树是一种自平衡二叉搜索树,它的每个节点存储一个键值对,且每个节点的左子树和右子树的高度差不超过1。这种平衡特性使得AVL树在查找、插入和删除操作方面都有很好的性能表现。 下面是一个简单的AVL树C++实现: ```c++ #include <iostream> using namespace std; // AVL树节点 struct Node { int key; int height; Node *left; Node *right; Node(int k) : key(k), height(1), left(NULL), right(NULL) {} }; // 获取节点高度 int height(Node *node) { if (node == NULL) { return 0; } return node->height; } // 获取节点平衡因子 int balanceFactor(Node *node) { if (node == NULL) { return 0; } return height(node->left) - height(node->right); } // 更新节点高度 void updateHeight(Node *node) { node->height = max(height(node->left), height(node->right)) + 1; } // 右旋操作 Node* rightRotate(Node *node) { Node *leftChild = node->left; Node *rightChild = leftChild->right; leftChild->right = node; node->left = rightChild; updateHeight(node); updateHeight(leftChild); return leftChild; } // 左旋操作 Node* leftRotate(Node *node) { Node *rightChild = node->right; Node *leftChild = rightChild->left; rightChild->left = node; node->right = leftChild; updateHeight(node); updateHeight(rightChild); return rightChild; } // 插入节点 Node* insert(Node *node, int key) { if (node == NULL) { return new Node(key); } if (key < node->key) { node->left = insert(node->left, key); } else if (key > node->key) { node->right = insert(node->right, key); } else { return node; } updateHeight(node); int bf = balanceFactor(node); if (bf > 1) { if (balanceFactor(node->left) >= 0) { return rightRotate(node); } else { node->left = leftRotate(node->left); return rightRotate(node); } } else if (bf < -1) { if (balanceFactor(node->right) <= 0) { return leftRotate(node); } else { node->right = rightRotate(node->right); return leftRotate(node); } } return node; } // 中序遍历AVL树 void inOrder(Node *node) { if (node == NULL) { return; } inOrder(node->left); cout << node->key << " "; inOrder(node->right); } int main() { Node *root = NULL; root = insert(root, 10); root = insert(root, 20); root = insert(root, 30); root = insert(root, 40); root = insert(root, 50); root = insert(root, 25); inOrder(root); cout << endl; return 0; } ``` 在上面的实现中,我们使用了递归插入节点,并在插入节点后更新了节点的高度和平衡因子。当节点的平衡因子大于1或小于-1时,我们进行相应的旋转操作来保持树的平衡。最后,我们在main函数中插入一些节点,并进行中序遍历来检查树是否正确构建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值