详解set/map的底层结构——AVL树和红黑树

news2024/11/23 23:17:35

目录

前文

一,AVL树

1.1 什么是AVL树?

1.2 AVL树节点的定义

1.3 insert—插入(重点)

 1.4 旋转(重点)

1.4.1 右单旋

 1.4.2 左单旋

 1.4.3 左右双旋

 1.4.4 右左双旋

 1.5 IsBalanc(平衡判断)

1.6 中序遍历

1.7 测试

二,红黑树

2.1 什么是红黑树?

2.2 红黑树的性质

2.3 红黑树节点的定义

2.4 插入

2.5 插入调整

2.5.1 情况一

2.5.2 情况二

2.5.3 情况三

2.6 IsBalance平衡判断

2.7 中序遍历以及求高度

2.7.1 中序遍历

2.7.2 高度

2.8 测试

三,AVL树和红黑树源码

3.1 AVL树

3.2 红黑树

总结



前文

本文带领大家深入了解set/map的底层结构——AVL树和红黑树,如果老铁对set和map不太熟悉,推荐先学习一下set和map的基本介绍和基本用法。

一,AVL树

1.1 什么是AVL树?

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

AVL树的规则如下

1.它的左右子树都是AVL树

2.左右子树高度之差(这里我们简称为平衡因子)的绝对值不超过1.

 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在logN,搜索时间复杂度logN

1.2 AVL树节点的定义

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;

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

节点我们采用三叉链的形式和之前的搜索二叉树类似,但是多了一个平衡因子_bf

(平衡因子=右子树高度-左子树高度)

1.3 insert—插入(重点)

首先这里插入的基本逻辑和搜索二叉树类似

1.树为空,则直接new一个新节点给root

2.树不为空,通过二叉树查找的规则找到空位置,插入即可

 

但是这里要多出一个步骤,同时也是重中之重

那就是调整平衡因子

更新平衡因子的规则如下:

1.cur在父节点的右边,父节点的平衡因子++

2.cur在父节点的左边,父节点的平衡因子--

然后因为平衡因子的变化我们还需要向上更新平衡因子,更新的规则如下

a.  bf == -1 || bf == 1:说明该节点的平衡因子变成了1/-1,因此该节点的平衡因子原来一定是0,此时需要向上更新

b. bf == -2 || bf == 2:说明该节点的左右子树已经打破平衡,需要进行旋转调整

c. bf == 0:说明该节点经过插入后高度正好相等,无需在向上更新

 1.4 旋转(重点)

AVL树的精髓便是通过旋转保持左右子树的平衡,从而防止歪脖子树的高度,在上面的插入中,更新平衡因子后,情况b. bf == -2 || bf == 2需要进行旋转从而保持左右子树的平衡。首先先让我们了解一下旋转的原因以及目的

旋转的原因:平衡因子大于1或者小于-1时,左右子树不平衡

旋转的目的:降低树的高度,保持左右子树的平衡,同时保持搜索二叉树的性质

旋转主要分为以下四种情况

 接下来我们就逐个讲解以下各个旋转的发生条件以及旋转具体实现

1.4.1 右单旋

 如图所示,当新节点要插入较高左子树的左侧时就会导致parent的左右失衡,此时就需要进行右单旋调整

首先我们根据二叉搜索树的性质得知:parent>subL,subL<subLR<parent

旋转步骤如下:

1.parent成为subL的右

2.subLR成为parent的左

经过上述旋转既降低了树的高度,维持左右子树的平衡,又保持了二叉搜索树的性质

代码实现如下(有更详细的注释):

 1.4.2 左单旋

 如图所示,当新节点要插入较高右子树右侧时,也就是在c的位置插入新节点,就会导致parent的左右子树失衡,这时候就需要进行左单旋调整

首先我们根据搜索二叉树的性质的得知:parent<subR,parent<subRL<subR

旋转步骤:

1.parent成为subR的左树

2.subRL成为parent的右子树

经过上述旋转既降低了树的高度,维持左右子树的平衡,又保持了二叉搜索树的性质

代码如下(由于左右类似,这里我们不在进行详细注释):

 1.4.3 左右双旋

 如上图所示,当新节点要插入较高子树的右侧时,也就是在b或者c插入,就会导致parent的左右子树失衡,此时需要左右双旋

 具体步骤如下:

1.以subL为parent进行左单旋

2.以parent为parent进行右单旋

3.调整平衡因子

在调整平衡因子这一步需要注意:

a. 如果新节点插入后,subLR的平衡因子为-1,那么最后parent的平衡因子为1。

b. 如果新节点插入后,subLR的平衡因子为1,那么最后subL的平衡因子为-1.

c. 如果新节点插入后,subLR的平衡因子为0,那么无需进行特殊调整

代码如下:

 1.4.4 右左双旋

  如上图所示,当新节点要插入较高子树的左侧时,也就是在b或者c插入,就会导致parent的左右子树失衡,此时需要右左双旋

 具体步骤如下:

1.以subR为parent进行右单旋

2.以parent为parent进行左单旋

3.调整平衡因子

在调整平衡因子这一步需要注意:

a. 如果新节点插入后,subRL的平衡因子为-1,那么最后subR的平衡因子为1。

b. 如果新节点插入后,subRL的平衡因子为1,那么最后parent的平衡因子为-1.

c. 如果新节点插入后,subRL的平衡因子为0,那么无需进行特殊调整

代码如下:

 1.5 IsBalanc(平衡判断)

根据AVL树的性质,我们只需要判断左右子树的高度差是否大于1即可,这里我们走的是一个中序遍历

    //检查树的高度是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
    //树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}
	//检查树的高度是否平衡
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return abs(Rheight - Lheight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);

	}

1.6 中序遍历

//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

1.7 测试

大致框架完成后,我们来简单测试一下

void Test_AVLTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		/*	if (e == 14)
			{
			int x = 0;
			}*/

		t1.Insert(make_pair(e, e));
		cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

// 10:35继续
void Test_AVLTree2()
{
	srand(time(0));
	const size_t N = 500000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.TreeHeight() << endl;
}

 欧克,测试通过

二,红黑树

讲完AVL树,我们就要讲一下绝对经典的红黑树了

2.1 什么是红黑树?

红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。 红黑树是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。 红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。 

具体来说,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的

2.2 红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(重点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 (重点)
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

那么红黑树是如何根据上面的性质来保证,最长路径不超过最短路径的2倍

 如上图是一个简单的没有红色节点的红黑树,每条路径的黑色节点数量相同,假设我们在最左边路径中添加红色节点,但是因为规则三,所以红色节点不能连续添加,所以最多只能加三个,也就说,在保证是红黑树的条件下,最短路径最长只能达到原路径的二倍,因此就保证了在节点树为N的情况下,红黑树中的所有路径长度都处于[logN,2logN]的范围内,从而达到接近平衡

2.3 红黑树节点的定义

enum Colour
{
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

红黑树的节点和AVL树相比,少了一个平衡因子,多了一个枚举类型Colour来表示颜色,注意这里的新节点插入,默认插入红色,这样避免破坏规则四,方便调整

2.4 插入

首先这里插入的基本逻辑和搜索二叉树类似

1.树为空,则直接new一个新节点给root

2.树不为空,通过二叉树查找的规则找到空位置,插入即可

3.根据情况进行调整

 

这里插完之后需要进行调整,调整的情况如下:

1.如果插入节点的父节点是黑色,则没有破坏规则,拍拍屁股走人既可

2.如果插入节点的父节点是红色,则破坏了规则,需要进行变色或者旋转调整

2.5 插入调整

注意:cur为新插入节点,cur的父节点叫做parent,cur的祖父节点叫做grandfather,另外还需要标注出uncle

调整大概分为三种情况(下面的情况都是以cur插入到grandfather的左树为例子,右树反过来即可):

情况一:cur为红,parent为红,uncle为红

情况二:cur为红,parent为红,uncle为黑/uncle不存在,cur是parent的左孩子

情况三:cur为红,parent为红,uncle为黑/uncle不存在,cur是parent的右孩子

2.5.1 情况一

处理方式:进行变色即可,将p和u变黑,g变红,注意这里的树可能是子树,也可能是个完整的树,如果是个完整的树,再将g变黑,如果不是则继续向上调整,因为g变红可能会对上面造成影响

2.5.2 情况二

 处理方式:旋转+变色,先以g为parent节点进行右旋(如果是右树进行左旋),然后p变黑,g变红即可。

2.5.3 情况三

处理方式:双旋转+变色

1.cur属于g的左子树:则先以parent为根节点进行左旋,这样就转换成了情况二,再以g为根节点进行右旋,最后进行变色,cur变黑,g变红

2.cur属于g的右子树:则是先右旋再左旋,最后变色,多的不在赘述,细节和上面类似

调整的所有代码如下:

//调整

		while (parent && parent->_col == RED)
		{
			//cur在g的左子树上
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的左子树,右单旋+变色
					if (cur == parent->_left)
					{
						_RotateR(grandfather);//右单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的右子树,左单旋+右单旋+变色
					else
					{
						_RotateL(parent);//左单旋
						_RotateR(grandfather);//右单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
			//cur在g的右子树
			else
			{
				Node* uncle = grandfather->_left;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的右子树,左单旋+变色
					if (cur == parent->_right)
					{
						_RotateL(grandfather);//左单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的左子树,右单旋+左单旋+变色
					else
					{
						_RotateR(parent);//右单旋
						_RotateL(grandfather);//左单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
		}

2.6 IsBalance平衡判断

红黑树的平衡判断我们也是根据规则来:

1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(重点)
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 (重点)
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

 检查方法如下:一一对应

1.无需检查

2.检查根节点是否为黑色即可

3.规则三意思是不能有连续红色,检查每个红色节点的parent,如果parent也为红色,则违反规则

4.利用传值传参,记录该节点的路径上的黑色节点数目,然后和第一条路径的黑色节点数目作比较,不相等则违反规则

5.无需检查

 代码如下

//检查是否平衡/是否符合红黑树规则
	bool IsBalance()
	{
		int FPathNumber = -1;
		return _check(_root,0,FPathNumber);
		
	}
    //检查
	bool _check(Node* root,int BlackNumber,int& FPathNumber)
	{
		//检查规则二,检查根节点
		if (_root->_col == RED)
		{
			cout << "根节点不为黑,违反规则二" << endl;
			return false;
		}

		if (root == nullptr)
		{
			//检查路径黑色节点是否相等
			if (FPathNumber == -1)
			{
				FPathNumber = BlackNumber;
			}

			if (FPathNumber != BlackNumber)
			{
				cout << "路径的黑色节点数量不同,违反规则四" << endl;
				return false;
			}
			return true;
		}

2.7 中序遍历以及求高度

2.7.1 中序遍历

    //中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
    //中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

2.7.2 高度

    //树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
    //树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}

2.8 测试

测试代码如下

void Test_RBTree1()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
		//cout << e << "插入:" << t1.IsBalance() << endl;
	}

	t1.InOrder();
	cout << t1.IsBalance() << endl;
}

void Test_RBTree2()
{
	srand(time(0));
	const size_t N = 5000000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand() + i;
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
	cout << t.TreeHeight() << endl;
}

 测试完成无误

三,AVL树和红黑树源码

3.1 AVL树

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <time.h>
using namespace std;
//avl树的实现
//结点
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;

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

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		//找到空位置插入
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//大于往右边走
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			//小于往左边走
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			//相等,插入失败
			else
			{
				return false;
			}
		}
		//连接
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//连接完成,更新平衡因子
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			//因子为1或-1,继续向上调整
			if (parent->_bf == -1 || parent->_bf == 1)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			//因子为-2或者2,说明左右子树已经打破平衡,需要进行调整
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//进行旋转调整
				//左旋
				if (parent->_bf == 2 && cur->_bf == 1)
				{
					_RotateL(parent);
				}
				//右旋
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					_RotateR(parent);
				}
				//左右双旋
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					_RotateLR(parent);
				}
				//右左双旋
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					_RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			//因子为0,则左右子树插入后正好平衡,无需调整
			else if (parent->_bf == 0)
			{
				break;
			}
			//其他值,插入错误
			else
			{
				assert(false);
			}
		}

		return true;


	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//检查树的高度是否平衡
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
private:
	//树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}
	//检查树的高度是否平衡
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return abs(Rheight - Lheight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);

	}



	//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

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

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		//判断subLR不为空
		if (subLR)
			subLR->_parent = parent;

		//判断parent是否为根节点
		if (parent==_root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = ppnode;
			if (ppnode->_right == parent)
			{
				ppnode->_right = subL;
			}
			else
			{
				ppnode->_left = subL;
			}
		}
		parent->_bf = subL->_bf = 0;
	}

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

		Node* ppnode = parent->_parent;//记录parent的父节点后续根据ppnode调整subR的父节点

		parent->_right = subRL;
		if (subRL)//判断subRL是否不为空,为空则不能非法访问
			subRL->_parent = parent;

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

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

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

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

		//记录subLR的平衡因子后续需要根据这个平衡因子调整subL parent的平衡因子
		int bf = subLR->_bf;
		//左旋
		_RotateL(parent->_left);
		//右旋
		_RotateR(parent);

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

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

		int bf = subRL->_bf;

		//右旋
		_RotateR(parent->_right);
		//左旋
		_RotateL(parent);

		//调整平衡因子
		if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(bf);
		}
	}

private:
	Node* _root;
};

3.2 红黑树

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
#include <time.h>
using namespace std;

//节点

enum Colour
{
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:_root(nullptr)
	{}

	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

	//插入
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		//找到空位置插入
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//大于往右边走
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			//小于往左边走
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			//相等,插入失败
			else
			{
				return false;
			}
		}
		//连接
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//调整

		while (parent && parent->_col == RED)
		{
			//cur在g的左子树上
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的左子树,右单旋+变色
					if (cur == parent->_left)
					{
						_RotateR(grandfather);//右单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的右子树,左单旋+右单旋+变色
					else
					{
						_RotateL(parent);//左单旋
						_RotateR(grandfather);//右单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
			//cur在g的右子树
			else
			{
				Node* uncle = grandfather->_left;
				//情况一,cur为红,p为红,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					//更新颜色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					if (grandfather == _root)//如果是整树,需要将g的颜色变为黑
					{
						grandfather->_col = BLACK;
					}
					else//如果是子树则继续向上调整
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				//情况二,情况三可以归为一大类:cur为红,p为红,u为黑或者不存在
				else
				{
					//情况二,cur为p的右子树,左单旋+变色
					if (cur == parent->_right)
					{
						_RotateL(grandfather);//左单旋
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况三,cur为p的左子树,右单旋+左单旋+变色
					else
					{
						_RotateR(parent);//右单旋
						_RotateL(grandfather);//左单旋
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//当调整到这里时,无需继续向上调整,因为该子树的祖节点为黑色
				}
			}
		}
		return true;
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//检查是否平衡/是否符合红黑树规则
	bool IsBalance()
	{
		//检查根节点
		int FPathNumber = -1;
		return _check(_root,0,FPathNumber);	
	}

	//树的高度
	int TreeHeight()
	{
		return _TreeHeight(_root);
	}
private:
	//析构函数
	void _Destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	//树的高度
	int _TreeHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int Lheight = _TreeHeight(root->_left);
		int Rheight = _TreeHeight(root->_right);

		return Lheight > Rheight ? Lheight + 1 : Rheight + 1;
	}

	//检查
	bool _check(Node* root,int BlackNumber,int& FPathNumber)
	{
		//检查规则二,检查根节点
		if (_root->_col == RED)
		{
			cout << "根节点不为黑,违反规则二" << endl;
			return false;
		}

		if (root == nullptr)
		{
			//检查路径黑色节点是否相等
			if (FPathNumber == -1)
			{
				FPathNumber = BlackNumber;
			}

			if (FPathNumber != BlackNumber)
			{
				cout << "路径的黑色节点数量不同,违反规则四" << endl;
				return false;
			}
			return true;
		}

		//规则四:求每条路径黑色节点数量,如果root为黑色节点,BlackNumber++
		if (root->_col == BLACK)
		{
			BlackNumber++;
		}

		//判断规则三,是否有连续红色节点
		Node* parent = root->_parent;
		if (root->_col == RED && parent && parent->_col == RED)
		{
			cout << "出现连续红色节点,违反规则三" << endl;
			return false;
		}

		return _check(root->_left,BlackNumber,FPathNumber)
			&& _check(root->_right, BlackNumber, FPathNumber);
	}

	//中序遍历_递归
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}





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

		Node* ppnode = parent->_parent;//记录parent的父节点后续根据ppnode调整subR的父节点

		parent->_right = subRL;
		if (subRL)//判断subRL是否不为空,为空则不能非法访问
			subRL->_parent = parent;

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

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

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

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		parent->_left = subLR;
		//判断subLR不为空
		if (subLR)
			subLR->_parent = parent;

		//判断parent是否为根节点
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			subL->_parent = ppnode;
			if (ppnode->_right == parent)
			{
				ppnode->_right = subL;
			}
			else
			{
				ppnode->_left = subL;
			}
		}
	}
private:
	Node* _root;
};

总结

这篇文章主要是带领大家深入了解了一下map和set的底层数据结构AVL树以及红黑树

AVL树的精髓主要是平衡因子,以及通过旋转来降低树的高度,使树变得平衡。

而红黑树的精髓则是在大多数情况下可以通过变色来减少旋转的次数,借此提高效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非常提效的7款原型工具推荐

原型图工具允许在开发前进行测试和迭代过程&#xff0c;可以帮助节省大量的开发时间和成本。在本文中&#xff0c;我们盘点了7个易于使用的原型图工具&#xff0c;以提高您的生产力&#xff01; 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&…

自学黑客,一般人我劝你还是算了吧!

我为啥说自学黑客&#xff0c;一般人我还是劝你算了吧&#xff01;因为我就是那个不一般的人。 首先我谈下对黑客&网络安全的认知&#xff0c;其实最重要的是兴趣热爱&#xff0c;不同于网络安全工程师&#xff0c;他们大都是培训机构培训出来的&#xff0c;具备的基本都是…

【Python入门】Python的判断语句(if elif else语句)

前言 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff0c;喜爱音乐的一位博主。 &#x1f4d7;本文收录于Python零基础入门系列&#xff0c;本专栏主要内容为Python基础语法、判断、循环语句、函…

从0开始学习数据库

一个数据库最重要的部分是什么&#xff1f; 关系型数据库mysql有着四大特性&#xff0c;原子性&#xff0c;隔离性&#xff0c;一致性&#xff0c;持久性。 kv数据库有着原子性&#xff0c;持久性&#xff0c;弱一致性。 可见&#xff0c;不管数据库的存储引擎是什么&#xff0…

【计算机网络】第一章 计算机网络基础(期末急救包)

目录 前言 正文 考点 1.1 计算机网络组成 1.2 计算机网络的分类 ——4种 1.3计算机网络的性能指标 1.4 计算机网络标准化工作及相关组织 2.计算机网络体系结构与模型 结语 前言 期末将至&#xff0c;相信有的同学们还在为怎么过期末而发愁吧&#xff01;不用担心&#…

谈「效」风生 | 「自动化」聊起来简单,做起来难

#第4期&#xff1a;“自动化”聊起来简单&#xff0c;做起来难# 在上一期《如何找到现有研发体系的「内耗问题」》中&#xff0c;我们聊了评估现有研发体系&#xff0c;正确的找到“体系内耗问题”&#xff0c;是改变研发体系的第一步。本期我们继续聊下一个关键点就是研发体系…

多USB工业相机的使用

USB相机的使用 USB3.0引入了“SuperSpeed”(SS)传输速率。理论传输速度高达625 MByte/s, SuperSpeed传输可以在短时间内传输大量数据&#xff0c;适用于许多视觉应用。给出的带宽上限是一个理想化的理论值。对于实际应用&#xff0c;主机控制器&#xff08;Host Controller&am…

红黑树下岗,内核新数据结构上场:maple tree!

在外界看来&#xff0c;Linux 内核的内部似乎变化很少&#xff0c;尤其是像内存管理子系统&#xff08;memory-management subsystem&#xff09;这样的子系统。然而&#xff0c;开发人员时常需要更换内部接口来解决某些长期存在的问题。比如&#xff0c;其中一个问题就是用来保…

五个程序员必要的在线绘图工具

说到程序员&#xff0c;每个人的第一反应都是敲代码。事实上&#xff0c;画图也是程序员必备的技能之一。各种流程图、架构图、UML类图、线框图等多种多样。 1.即时设计 即时设计是一款免费的在线 UI 设计工具&#xff0c;无系统限制&#xff0c;浏览器打开即可使用&#xff…

算法修炼之练气篇——练气七层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

Recoil在React中完整实践方案

先让我吐槽一下&#xff0c;Recoil这个玩意文档是真的不友好&#xff0c;另外发现国内很少有人去用Recoil&#xff0c;然后好多文章都是照搬官网文档&#xff0c;我特喵的要是出了问题直接看官方不就行了。如果你碰巧看到这个文章了&#xff0c;就细心看完吧&#xff0c;绝对的…

从一文不值到数字黄金 诞生于极客圈的比特币,究竟经历了什么?

比特币作为技术性很强的神奇发明&#xff0c;从一文不值到数字黄金&#xff0c;在发展过程中不仅为金融范式转变奠定了基础&#xff0c;改变了人们感知和交易价值的方式&#xff0c;也为无数数字资产开辟了一条可追随的道路。 比特币之所以复杂&#xff0c;是因为技术属性、金融…

在线未注册域名批量查询-域名注册批量查询

域名批量注册查询 域名批量注册查询是一种工具&#xff0c;可以帮助用户批量查询并注册多个域名。这种工具通常被域名管理者、品牌专家、互联网营销人员等使用。 以下是域名批量注册查询工具的优点&#xff1a; 提高效率&#xff1a;与手动单独注册域名相比&#xff0c;域名批…

机器学习基础(粗学)【未完待续】

卷积神经网络粗学 卷积&#xff1a;用卷积求系统的存量&#xfeff;&#xfeff;&#xfeff; 卷积&#xff0c;就是把输出函数反转一下。。。。&#xff08;离谱&#xff09; 实际不是从物理意义上理解的函数翻转&#xff0c;而是应该从数学意义上&#xff0c;去理解卷积的…

六、IDEAJ同一个服务启动多台服务器的方法

目录 1、打开启动类配置窗口--->选择Edit Configurations进入配置窗口 2、从左侧Springboot应用选择需要启动的多台服务器&#xff08;服务只要启动一次就会在此窗口有显示&#xff09;--->勾选Allow parallel run菜单&#xff08;默认不勾选&#xff0c;则只能启动一台…

【JAVAEE】常见的锁策略

目录 1.常见的锁 1.乐观锁&悲观锁 2.轻量级锁&重量级锁 3.读写锁&普通互斥锁 4.自旋锁&挂起等待锁 5.可重入锁&不可重入锁 6.公平锁&非公平锁 2.CAS 1.什么是CAS 2.CAS的应用 1.实现原子类 2.实现自旋锁 3.synchronized用到的锁策略 1.s…

RobotFramework +appium实现Android自动化

环境准备 1、已安装python37版本&#xff08;SDK、JDK均已安装完成&#xff0c;且环境变量都配置好了&#xff09;。 2、已安装robotframework。 3、已安装安卓模拟器&#xff08;本文使用夜神模拟器&#xff09;。 4、安装appium&#xff08;下载地址&#xff1a;http://6…

立创梁山派学习笔记——GPIO输出控制

梁山派 前言开发板简介GD32F407ZGT6官方资源数据手册1.系统框图2. 引脚复用表3.命名规则4.其他 用户手册固件库与PACK包 开发环境搭建立创官方的资料包资料齐活&#xff0c;开发1.工程搭建2.使用寄存器点亮LEDGPIO数量LED的GPIO口GPIO的配置流程GPIO的寄存器1.端口控制寄存器&a…

假如你是一位测试主管,如何判断一名测试工程师是否优秀?

假如你是一位测试主管&#xff0c;去评价一名测试工程师是否优秀&#xff0c;那么你将如何去判断呢&#xff1f;你最看重的是哪方面的能力呢&#xff1f; 对于这个问题&#xff0c;是不能一概而论的&#xff0c;要分为两种情况&#xff0c;情况不同&#xff0c;答案一定是不同…

自媒体素材哪里找?这5个网站告诉你答案。

做自媒体最需要的就是各种视频、配乐、图片等素材了。想要图文好&#xff0c;那就必须要一个好封面图。想要视频好&#xff0c;那就必须要有好的素材和配乐。 那要如何找到这些素材&#xff1f;看这几个网站就够了。 1、菜鸟图库 https://www.sucai999.com/video.html?vNTY…