C++进阶数据结构_AVL树

Posted LHlucky_2

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++进阶数据结构_AVL树相关的知识,希望对你有一定的参考价值。

1.AVL树的基本概念

1.1为什么需要

set/map底层用的都是二叉搜索树,但是普通的二叉搜索树是有缺陷的,如果往树中插入的元素是有序的或者是接近有序的,那么树就越接近链表的形式,那么查找和插入的效率就变成了O(N)。

因此map、set等关联式容器的底层结构是对二叉搜索树进行了平衡处理的,即采用平衡二叉树搜索树(AVL树)

1.2性质

当向二叉搜索树中插入新的节点后,如果能保证每个节点的左右子树高度只差绝对值不超过1,即保证了这棵树是AVL树,即有如下量大特点:

左右子树都是AVL树

左右子树的高度之差(平衡因子)的绝对值不超过1

如果一棵二叉搜索树的高度是平衡的,它就是AVL树。它的查找效率为logn

1.3适用场景

AVL是一颗绝对平衡的二叉搜索树,要求每个节点的左右子树的高度差的绝对值不超过1,这样可以保证查询时候的时间复杂度为logN。
如果要对其进行结构上的修改操作,由于需要维护树的平衡,就会需要对树进行旋转,性能会非常低下

因此:如果需要一种查询高效且有序的数据结构(且数据的个数为静态)可以考虑AVL树,如果一个结构经常修改,就不太适合

2.AVL树的实现

2.1平衡因子的调节

在这里插入图片描述

2.2几种旋转情况

2.2.1右单旋

在这里插入图片描述
实现代码:

void RotateR(Node *parent)
	{
		Node *subL = parent->_left;//此时p->bf=-2,左边肯定不为空
		Node *subR = subL->_right;
		Node *pparent = parent->_parent;//保存一根

		//将子树链接到parent的左侧
		parent->_left = subR;
		if (subR)
			subR->_parent = parent;

		//将parent连接到subL的右侧
		subL->_right = parent;
		parent->_parent = subL;

		//将subL与ppretn链接起来
		if (pparent == nullptr)//subL变成新的根
		{
			_root = subL;
			subL->_left = nullptr;
		}
		else//不为根
		{
			subL->_parent = pparent;
			if (parent == pparent->_left)//在上一级节点的左侧
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
		}

		//平衡因子的更新
		parent->_bf = 0;
		subL->_bf = 0;

	}

2.2.2左单旋

在这里插入图片描述

实现代码:

void RotateL(Node *parent)//左旋
	{
		Node *subR = parent->_right;//右变高,不可能为空
		Node *subL = subR->_left;
		Node *pprent = parent->_parent;
		
		//subL连接到parent上
		parent->_right = subL;
		if (subL)
			subL->_parent = parent;

		//parent连接到subR上面
		subR->_left = parent;
		parent->_parent = subR;

		//subR链接到pprent上
		if (pprent == nullptr)//根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else//不为根
		{
			subR->_parent = pprent;
			//判断链接在哪一侧
			if (pprent->_left == parent)
			{
				pprent->_left = subR;
			}
			else
			{
				pprent->_right = subR;
			}
		}

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

2.2.3左右单旋

在这里插入图片描述
代码实现:

void RotateLR(Node *parent)
	{
		Node *subL = parent->_left;
		Node *subLR = subL->_right;//此时不可能为空,因为右子树高

		int bf = subLR->_bf;//保存一份平衡因子

		RotateL(subL);//先左旋
		RotateR(parent);//再右旋

		//左旋、右旋会将平衡因子全部处理成0,因此要对平衡因子进行更改

		if (bf == 1)//在subLR的右侧插入
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if(bf==-1)//在subLR左侧插入
		{
			subLR->_bf = 0;
			subL->_bf = 1;
			parent->_bf = 1;
		}
	}

2.2.4右左单旋

在这里插入图片描述
代码实现:

void RotateRL(Node *parent)
	{
		Node *subR = parent->_right;
		Node *subRL = subR->_left;

		int bf = subRL->_bf;

		RotateR(subR);//先右旋
		RotateL(parent);//再左旋

		//平衡因子出来

		if (bf == 1)//在subRL右侧插入时
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if(bf==-1)//在左侧插入时
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 1;
		}
	}

3.整体代码即验证

3.1代码

#pragma once
using namespace std;
#include <iostream>
#include <map>
#include <string>
#include "assert.h"
#include <stdlib.h>
#include <vector>

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K, V>& kv=pair<K,V>())
	:_left(nullptr)
	,_right(nullptr)
	, _parent(nullptr)
	, _bf(0)
	, _kv(kv)
	{}

	AVLTreeNode<K, V>*_left;
	AVLTreeNode<K, V>*_right;
	AVLTreeNode<K, V>*_parent;
	pair<K, V> _kv;
	int _bf;//平衡因子
};

template <class K,class V>
class AVLTree
{
public:
	typedef struct AVLTreeNode<K,V> Node;
private:
	//旋转函数编写
	void RotateR(Node *parent)
	{
		Node *subL = parent->_left;//此时p->bf=-2,左边肯定不为空
		Node *subLR = subL->_right;
		Node *pparent = parent->_parent;//保存一份

		//将子树链接到parent的左侧
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//将parent连接到subL的右侧
		subL->_right = parent;
		parent->_parent = subL;

		//将subL与ppretn链接起来
		if (pparent == nullptr)//subL变成新的根
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else//不为根
		{
			subL->_parent = pparent;
			if (parent == pparent->_left)//在上一级节点的左侧
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
		}

		//平衡因子的更新
		parent->_bf = 0;
		subL->_bf = 0;

	}

	void RotateL(Node *parent)//左旋
	{
		Node *subR = parent->_right;//右变高,不可能为空
		Node *subRL = subR->_left;
		Node *pprent = parent->_parent;
		
		//subL连接到parent上
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		//parent连接到subR上面
		subR->_left = parent;
		parent->_parent = subR;

		//subR链接到pprent上
		if (pprent == nullptr)//根
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else//不为根
		{
			subR->_parent = pprent;
			//判断链接在哪一侧
			if (pprent->_left == parent)
			{
				pprent->_left = subR;
			}
			else
			{
				pprent->_right = subR;
			}
		}

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

	void RotateLR(Node *parent)
	{
		Node *subL = parent->_left;
		Node *subLR = subL->_right;//此时不可能为空,因为右子树高

		int bf = subLR->_bf;//保存一份平衡因子

		RotateL(subL);//先左旋
		RotateR(parent);//再右旋

		//左旋、右旋会将平衡因子全部处理成0,因此要对平衡因子进行更改

		if (bf == 1)//在subLR的右侧插入
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if(bf==-1)//在subLR左侧插入
		{
			subLR->_bf = 0;
			subL->_bf = 1;
			parent->_bf = 1;
		}
		else//bf==0,新增的
		{
			subLR->_bf = 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)//在subRL右侧插入时
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if(bf==-1)//在左侧插入时
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else//bf==0,新增的
		{
			subRL->_bf = parent->_bf = subR->_bf = 0;
		}
	}

public:

//插入函数
	pair<Node*, bool> Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return make_pair(_root,true);
		}

			//有根了,按照平衡二叉树的方法进行插入
			Node *parent = nullptr;
			Node *cur = _root;
			while (cur)
			{
				if (kv.first<cur->_kv.first)//K值比较,小于往左边走
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kv.first>cur->_kv.first)//往右走
				{
					parent = cur;
					cur = cur->_right;
				}
				else//相等,返回已有元素的指针
				{
					return make_pair(cur, false);
				}
			}


			//此时已经找到插入的位置了,判断插入在左边还是插入在右边
			cur = new Node(kv);
			if (parent->_kv.first > kv.first)//插在左边
			{
				parent->_left = cur;
				cur->_parent = parent;//回指
			}
			else//插在右边
			{
				parent->_right = cur;
				cur->_parent = parent;
			}

			//更新平衡因子
			Node *newnode = cur;//cur会发生改变

			while (parent)//不为空
			{
				if (parent->_left == cur)//cur在parent左侧
				{
					parent->_bf--;
				}
				else//右侧
				{
					parent->_bf++;
				}

				if (parent->_bf == 0)//当前树是平衡的
					break;
				else if (abs(parent->_bf) == 1)//继续往上面走
				{
					cur = parent;
					parent = parent->_parent;
				}
				else if (abs(parent->_bf) == 2)//需要进行旋转处理
				{
					if (parent->_bf == -2)//左边高
					{
						if (cur->_bf == -1)//是在当前节点的左侧插入了节点 ->右单旋
						{
							RotateR(parent);
						}
						else//cur->_bf=1 ->曲线影响
						{
							RotateLR(parent);
						}
					}
					else//右边高
					{
						if (cur->_bf == 1)//在当前节点的右侧插入了节点 ->  左单旋
						{
							RotateL(parent);
						}
						else//cur->_bf=-1 曲线影响
						{
							RotateRL(parent);
						}
					}
					break;//旋转过后当前的树就是平衡的了,立即退出
				}
				else//0 1 2 -> 不可能走到这一步,走到这里说明发生了逻辑错误
				{
					assert(false);
				}
			}
			return make_pair(newnode, true);//放到这里放回是因为需要更新平衡因子
	}


private:
	//遍历的时候 root为private外面无法拿到
	//因此需要封装一层
	void _Inorder(Node *root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << " " << root->_kv.second << endl;
		_Inorder(root->_right);
	}

public:
	//遍历
	void Inorder()
	{
		_Inorder(_root);
	}

	Node* Find(const K &k)
	{
		Node *cur = _root;
		while (cur)
		{
			if (cur->_kv.first > k)
				cur = cur->_left;
			else if (cur->_kv.first < k)
				cur = cur->_right;
			else
				return cur;
		}
		return false;
	}


	V &operator [] (const K &k)//string、int、vector等等都可以是V,是由默认的构造函数的 int()=0
	{
		//插入函数返回的是pair<node*,bool>
		return (((Insert(make_pair(k, V()))).first)->_kv).second;//返回的是引用,是可以修改的

	}	

	//验证是否是平衡树

private:
	int _Isbalance(Node* root)//采用后序遍历的方式验证
	{
		if (root == nullptr)
			return 0;

		int left = _Isbalance(root->_left);
		int right = _Isbalance(root->_right);
		cout << root->_bf << " "<<endl;

		if (abs(root->_bf)>1||left==-1||right==-1||abs(left - right) > 1)//不满足条件返回-1
			return -1;

		return (int)fmax(left, right) + 1;//满足条件返回当前高度
	}

public:

	bool Isbalance()
	{
		if (_Isbalance(_root) == -1)
			return false;
		else
			return true;
	}


private :
	Node *_root = nullptr;
};

3.2验证

#include "avl.hpp"

void test1(vector<int>&arr)
{
	AVLTree<int,int> t1;

	cout << "中序遍历输出节点----------------" << endl;
	for (auto&e : arr)
	{
		t1[以上是关于C++进阶数据结构_AVL树的主要内容,如果未能解决你的问题,请参考以下文章

C++进阶数据结构_AVL树

C++进阶AVL树

C++进阶AVL树

C++进阶第十八篇——AVL树(概念+平衡因子的调节+旋转+代码实现)

C++进阶数据结构_红黑树

C++进阶数据结构_红黑树