AVL树

1. AVL树的概念

二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,导致查找元素相当于在顺序表中搜索元素,效率低下。

为了解决这个问题,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis发明了一种方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度,提高效率。

空树和具有以下性质的二叉搜索树是AVL树。

  1. 左右子树也是AVL树。
  2. 左右子树高度之差的绝对值不超过1。

比如,左边的树是AVL树,右边的树不是。
在这里插入图片描述

2. 结点的定义

由于会频繁计算左右子树的高度,对于结点的定义,多加一个成员_bf表示左右子树高度差。称为平衡因子。之前的二叉搜索树,存储的是key结构,这里存储的是<key,value>的键值对的结构。插入操作会引发旋转,需要找到父节点,所以增加一个父指针,指向父结点。

结构如下

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

	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; //  右子树高度 - 左子树高度
};

3. 插入

类里面只有一个指针指向根节点。

typedef AVLTreeNode<K, V> Node;
Node* _root = nullptr;

AVL树是二叉搜索树,插入结点步骤:
1.先找插入位置,插入并链接。
2.更新平衡因子。如果出现不符合AVL树性质的情况,需要进行旋转调整。

在这里插入图片描述

总结:

  1. 更新后平衡因子为0,说明更新前为-1或1,插入的结点在较低的那一侧,不用继续向上更新。
  2. 更新后平衡因子为-1/1,说明更新前为0,需要继续向上调整
  3. 更新后平衡因子为-2/2,不用继续向上调整,但是破坏了AVL树的规则,需要调整。

部分代码如下。

bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr) //空树
	{
		_root = new Node(kv);
		return true;
	}
	Node* cur = _root;
	Node* parent = _root;
	while (cur)           //找插入的位置
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	//new结点并链接
	cur = new Node(kv);
	if (kv.first > parent->_kv.first)
		parent->_right = cur;
	else
		parent->_left = cur;
	cur->_parent = parent;


	while (parent)
	{
		//更新平衡因子
		if (cur == parent->_left)
			parent->_bf--;
		else
			parent->_bf++;

	
		if (parent->_bf == 0)     //直接跳出
			break;
		else if (parent->_bf == -1 || parent->_bf == 1) //向上调整
		{
			cur = parent;
			parent = cur->_parent;
		}
		else if (parent->_bf == -2 || parent->_bf == 2)
		{
			//不符合AVL树标准,旋转调整
			//....
		}
		else
			assert(false);

	}

	return true;
}

旋转的逻辑。不同高度的AVL树和插入的情况可以有多种情况的组合,但是可以抽象为下面的样子。
情况一:

在这里插入图片描述

上图为左子树高于右子树的情况,进行右单旋。如果右子树高于左子树,同理需要进行左单旋。旋转逻辑类似。旋转时,需要考虑60结点的情况,它可能是根结点,可能是某节点的子树。

void RotateLeft(Node* parent) //左单旋
{
	Node* Pparent = parent->_parent;
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	subR->_left = parent;

	parent->_parent = subR;
	subR->_parent = Pparent;

	if (subRL)
		subRL->_parent = parent;

	if (Pparent == nullptr) //说明parent结点为原来的根
		_root = subR;
	else
	{
		if (Pparent->_left == parent)
			Pparent->_left = subR;
		else
			Pparent->_right = subR;
	}
	parent->_bf = subR->_bf = 0;
}

void RotateRight(Node* parent) //右单旋
{
	Node* Pparent = parent->_parent; 
	Node* subL = parent->_left; 
	Node* subLR = subL->_right;

	parent->_left = subLR;
	subL->_right = parent;

	parent->_parent = subL;
	subL->_parent = Pparent;

	if (subLR)
		subLR->_parent = parent;

	if (Pparent == nullptr) //调整点为根结点
		_root = subL;
	else
	{
		if (Pparent->_left == parent)
			Pparent->_left = subL;
		else
			Pparent->_right = subL;
	}

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

情况二:
在这里插入图片描述
在这里插入图片描述
总结:

  1. 新节点插入较高左子树的左侧—左左:右单旋
  2. 新节点插入较高右子树的右侧—右右:左单旋
  3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
  4. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋

前面两者为一边高,后两者为一高一低。(对于整棵树和插入结点所在的子树而言)

双旋最后的注意点为平衡因子的更新。

void RotateRightLeft(Node* parent) //右左双旋
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateRight(parent->_right);
	RotateLeft(parent);

	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else
		assert(false);
}

void RotateLeftRight(Node* parent) //左右双旋
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RotateLeft(parent->_left);
	RotateRight(parent);

	if (bf == 0)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subL->_bf = 0;
		subLR->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
		subLR->_bf = 0;
		parent->_bf = 0;
	}
	else
		assert(false);
}

4. 判断平衡二叉树

先计算左右子树的高度差,判断是否为1/-1/0。由于结点内的成员添加了_bf,所以还需要判断二者是否相等,最后递归左右子树。

//计算树的高度
int _Height(Node* root)
{
	if (root == nullptr)
		return 0;

	int left = _Height(root->_left);
	int right = _Height(root->_right);

	return left > right ? left + 1 : right + 1;
}

bool _IsBalanceTree(Node* root)
{
	if (root == nullptr)
		return true;

	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	int diff = rightHeight - leftHeight;

	if (abs(diff) > 1 || root->_bf != diff)
		return false;

	return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

测试程序
在这里插入图片描述

5. 其他接口

find的实现比较容易,比较key即可。

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

构造,拷贝构造,赋值和析构函数。

拷贝构造,先拷贝根结点,在拷贝左子树,在拷贝右子树,需要注意父节点指针的指向。

AVLTree(const AVLTree& a)
{
	_root = Copy(a._root);
}


Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* newNode = new Node(root->_kv);
	newNode->_left = Copy(root->_left); //拷贝左子树
	if (newNode->_left) //链接父亲
		newNode->_left->_parent = newNode;

	newNode->_right = Copy(root->_right); //拷贝右子树

	if (newNode->_right) 链接父亲
		newNode->_right->_parent = newNode;

	return newNode;
}

因为我们显示写了拷贝构造函数,编译器就不会生成默认构造了,也就不能空初始化了,可以使用default关键字强制编译器生成默认构造。

AVLTree() = default;

赋值,交换两个对象的指针即可。

Node* operator=(AVLTree a)
{
	swap(_root, a._root);
	return _root;
}

析构函数,后序遍历平衡搜索树,进行是否资源

~AVLTree()
{
	Destroy(_root);
	_root = nullptr;
}


void Destroy(Node* root)
{
	if (root == nullptr)
		return;

	Destroy(root->_left);
	Destroy(root->_right);

	delete root;
	root = nullptr;
}

6. 完整代码

AVLTree.h

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

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

	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf; //  右子树高度 - 左子树高度
};

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

	AVLTree() = default;

	AVLTree(const AVLTree& a)
	{
		_root = Copy(a._root);
	}

	Node* operator=(AVLTree a)
	{
		swap(_root, a._root);
		return _root;
	}


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

		//new结点并链接
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
			parent->_right = cur;
		else
			parent->_left = cur;
		cur->_parent = parent;


		while (parent)
		{
			//链接父节点
			if (cur == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;

		
			if (parent->_bf == 0)  
				break;
			else if (parent->_bf == -1 || parent->_bf == 1)
			{
				cur = parent;
				parent = cur->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//不符合AVL树标准,旋转调整
				if (parent->_bf == 2 && cur->_bf == 1)           //左旋
					RotateLeft(parent);
				else if (parent->_bf == -2 && cur->_bf == -1)  //右旋
					RotateRight(parent);
				else if (parent->_bf == 2 && cur->_bf == -1)   //右左双旋
					RotateRightLeft(parent);
				else if (parent->_bf == -2 && cur->_bf == 1)   //左右双旋
					RotateLeftRight(parent);

				break;
			}
			else
				assert(false);

		}

		return true;
	}

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

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

	bool IsBalanceTree()
	{
		return _IsBalanceTree(_root);
	}

	int Height()
	{
		return _Height(_root);
	}

	~AVLTree()
	{
		Destroy(_root);
		_root = nullptr;
	}

private:

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newNode = new Node(root->_kv);
		newNode->_left = Copy(root->_left); //拷贝左子树
		if (newNode->_left) //链接父亲
			newNode->_left->_parent = newNode;

		newNode->_right = Copy(root->_right); //拷贝右子树

		if (newNode->_right) 链接父亲
			newNode->_right->_parent = newNode;

		return newNode;
	}

	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_left);
		Destroy(root->_right);

		delete root;
		root = nullptr;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int left = _Height(root->_left);
		int right = _Height(root->_right);

		return left > right ? left + 1 : right + 1;
	}

	bool _IsBalanceTree(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;

		if (abs(diff) > 1 || root->_bf != diff)
			return false;

		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}

	void RotateLeft(Node* parent) //左单旋
	{
		Node* Pparent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		subR->_left = parent;

		parent->_parent = subR;
		subR->_parent = Pparent;

		if (subRL)
			subRL->_parent = parent;

		if (Pparent == nullptr) //说明parent结点为原来的根
			_root = subR;
		else
		{
			if (Pparent->_left == parent)
				Pparent->_left = subR;
			else
				Pparent->_right = subR;
		}
		parent->_bf = subR->_bf = 0;
	}

	void RotateRight(Node* parent) //右单旋
	{
		Node* Pparent = parent->_parent; 
		Node* subL = parent->_left; 
		Node* subLR = subL->_right;

		parent->_left = subLR;
		subL->_right = parent;

		parent->_parent = subL;
		subL->_parent = Pparent;

		if (subLR)
			subLR->_parent = parent;

		if (Pparent == nullptr) //调整点为根结点
			_root = subL;
		else
		{
			if (Pparent->_left == parent)
				Pparent->_left = subL;
			else
				Pparent->_right = subL;
		}

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

	void RotateRightLeft(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateRight(parent->_right);
		RotateLeft(parent);

		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else
			assert(false);
	}

	void RotateLeftRight(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateLeft(parent->_left);
		RotateRight(parent);

		if (bf == 0)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subL->_bf = 0;
			subLR->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else
			assert(false);
	}

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

private:
	Node* _root = nullptr;
};

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include"AVLTree.h"

void testAVLTree()
{
	AVLTree<int, int> avl;
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto val : b)
	{
		avl.Insert({ val,val });
	}
	avl.Inorder();

	AVLTree<int, int> a(avl);
	a.Insert({8,8});
	a.Inorder();

	AVLTree<int, int> c;
	c = a;
	c.Inorder();

	cout << "树的高度" << avl.Height() << endl;
}

void testAVLTree1()
{
	AVLTree<int, int> avl;
	int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto& e : b)
	{
		avl.Insert({ e,e });
		
	}
	avl.Inorder();

	if (avl.IsBalanceTree()) cout << "是平衡搜索树" << endl;
	else cout << "不是平衡搜索树" << endl;
}

int main()
{
	testAVLTree();
	//testAVLTree1();
	return 0;
}
  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值