学习->C++篇十四:平衡二叉搜索树

目录

1.平衡二叉搜索树(AVL树)的定义

2.AVL树是如何平衡的?

左旋操作

右旋操作

左右双旋操作

右左双旋操作


1.平衡二叉搜索树(AVL树)的定义

        上篇提到当插入的数据有序或接近有序时,节点n个的树的高度接近n,查找效率会接近O(n),效率较低,为此在每个节点引入了平衡因子,表示右子树与左子树的高度差,为让树接近完全二叉树,从而让查找效率为O(logn),可以约束左右子树的高度差不超过1,为做到需要通过旋转操作来实现,为便于旋转操作,在每个节点再引入一个父节点的指针,便于旋转。

        于是可以定义树的节点如下:

template<class T>
struct AVLTreeNode
{
	AVLTreeNode<T>* _left;
	AVLTreeNode<T>* _right;
	AVLTreeNode<T>* _parent;
	T _data;
	int _bf;//balance factor = 右子树高度 - 左子树高度
	AVLTreeNode(const T& data = T())
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		,_bf(0)//新增节点的子树高度差为0
	{}
};

再定义树的结构如下:

template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}
private:
	Node* _root;
};

2.AVL树是如何平衡的?

        AVL树可通过控制 -1<=_bf<=1来控制左右子树高度差小于2,可从插入操作来理解平衡规则。首先按二叉搜索树插入的方式进行插入,如然后再更新对应的平衡因子,当平衡因子不满足规则时,通过旋转操作来调整平衡因子,直到节点都满足平衡规则。

可以实现insert框架如下:

bool insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_data == data)
				return false;
			parent = cur;
			if (cur->_data < data)
				cur = cur->_right;
			else
				cur = cur->_left;
		}

		cur = new Node(data);
		if (parent->_data < data)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;

		//从下往上调整平衡因子
		while (parent)
		{
			if (parent->_left == cur)
				--parent->_bf;
			else
				++parent->_bf;
			int bf = parent->_bf;
			if (bf == 0)//parent为根的树高度不变
				break;
			if (bf == 1 || bf == -1)//0 变成 -1或1,parent为根的树高度变高了,往上继续调整
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (bf == 2 || bf == -2)//平衡因子为2不满足规则,通过旋转调整
			{
				//...
			}
			else assert(false);//检查平衡因子
		}
		return true;
	}

当平衡因子为-2或2时,如果parent的平衡因子为-2,cur的平衡因子应当由-1或1,否则parent的平衡因子不会更新到2,于是可分成四种情况来考虑:

画出各种情况的抽象图如下:

左旋操作

情况(1):parent的平衡因子为2,cur的平衡因子为1

左旋:将cur的左子树作为parent的右子树,将parent作为cur的左子树。 

于是可以实现左旋操作代码:

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* grandparent = parent->_parent;

		parent->_right = subRL;
		if (subRL)subRL->_parent = parent;
		subR->_left = parent;
		parent->_parent = subR;

		if (grandparent)
		{
			if (grandparent->_left == parent)
				grandparent->_left = subR;
			else
				grandparent->_right = subR;
		}
		else
		{
			_root = subR;
		}
		subR->_parent = grandparent;
		
		subR->_bf = parent->_bf = 0;
	}

右旋操作

情况(2):parent的平衡因子为-2,cur的平衡因子为-1

 右旋:将cur的右子树作为parent的左子树,将parent作为cur的右子树。 

 

于是可以实现右旋操作代码(与左旋相似): 

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* grandparent = parent->_parent;

		parent->_left = subLR;
		if (subLR)subLR->_parent = parent;
		subL->_right = parent;
		parent->_parent = subL;

		if (grandparent)
		{
			if (grandparent->_left == parent)
				grandparent->_left = subL;
			else
				grandparent->_right = subL;
		}
		else
		{
			_root = subL;
		}
		subL->_parent = grandparent;

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

左右双旋操作

情况(3):parent的平衡因子为-2,cur的平衡因子为1

 此时插入subLR子树有三种情况(它们是类似的,假设往subLR的左子树插入):

 

 另外两种情况同理(两次旋转的操作相同):

 

可以复用之前旋转的代码,于是可以实现下面的代码:

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

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

右左双旋操作

情况(4):parent的平衡因子为2,cur的平衡因子为-1

这个情况与情况三相反,先将subR为根的树向右旋转,再将parent为根的树向左旋转即可。

代码类似上:

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

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

测试平衡树:

        检查每个节点的平衡因子是否正确(bf = height(node->right) - height(node->left) ),然后检查是否大于1或小于-1,即是否违反平衡树的规则。(出错时,层序遍历输出树,方便检查)

代码:

public:
	int height() { return _height(_root); }
	bool isAVLTree() { return _isAVLTree(_root); }
	vector<vector<int>> levelOrder() { return _levelOrder(_root); }
private:

	vector<vector<int>> _levelOrder(Node* root)
	{
		vector<vector<int>>ret(0);
		if (root == nullptr)
			return ret;
		queue<Node*> q;
		q.push(root);
		int level = 0;
		while (q.size())
		{
			int n = q.size();
			ret.push_back(vector<int>{});
			while (n--)
			{
				if (q.front()->_left)
					q.push(q.front()->_left);
				if (q.front()->_right)
					q.push(q.front()->_right);
				ret[level].push_back(q.front()->_data);
				q.pop();
			}
			++level;
		}
		for (auto &v : ret)
		{
			for (auto e : v)
				cout << e << " ";
			cout << endl;
		}
		return ret;
	}

	bool _isAVLTree(Node* root)
	{
		if (root == nullptr)
			return true;
		int bf = root->_bf;
		if (bf != _height(root->_right) - _height(root->_left))
		{
			cout << "balance factor isn't correct! " << endl;
			cout << "Node val = " << root->_data<<" node->bf = "<<root->_bf << endl;
			return false;
		}
		else if (abs(bf) > 1)
		{
			cout << "abs(bf) means imbalance! " << endl;
			cout << "Node val = " << root->_data << " node->bf = " << root->_bf<<endl;
			return false;
		}
		return _isAVLTree(root->_left) && _isAVLTree(root->_right);
	}

	int _height(Node* root)
	{
		if (root == nullptr)
			return 0;
		
		return std::max(_height(root->_left), _height(root->_right)) + 1;
	}

测试代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<string>

using std::cout;
using std::cin;
using std::endl;

#define N 5000
#include"avltree.h"
int main() 
{
	srand(time(nullptr));
	AVLTree<int> avlt;
	//while(avlt.IsBalance())
	for (int i = 0; i < N; i++)
	{
		avlt.insert(rand());
		if (avlt.isAVLTree() == false)
		{
			avlt.InOrder();
			break;
		}
		if (i % 500 == 0)
			cout <<"now avlTree height = " << avlt.height() << endl;
	}

	return 0;
}

输出结果:正常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值