C++:set和map(模拟实现)

news2024/11/24 18:36:16

目录

关联式容器

键值对

树形结构的关联式容器

set的介绍

set的使用

map的介绍

map的使用

multiset的介绍

multimap的介绍

底层结构

AVL树的概念

AVL树节点的定义

AVL树的旋转

左单旋

右单旋

先右单旋再左单旋

先左单旋再右单旋

模拟实现AVL树

红黑树

红黑树的概念

红黑树的插入操作

情况一: cur为红,p为红,g为黑,u存在且为红

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

红黑树的模拟实现

封装map和set


关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面 存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别? 关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key,value>结构的键值对,在数据检索时比序列式容器效率更高。

键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代 表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应 该单词,在词典中就可以找到与其对应的中文含义。

template <class T1, class T2>
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    pair(): first(T1()), second(T2())
    {}
    pair(const T1& a, const T2& b): first(a), second(b)
    {}
}

树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结 构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使 用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一 个容器。

set的介绍

1. set是按照一定次序存储元素的容器

2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。 set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。

3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行 排序。

4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对 子集进行直接迭代。

5. set在底层是用二叉搜索树(红黑树)实现的。

注意:

1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放 value,但在底层实际存放的是由构成的键值对。

2. set中插入元素时,只需要插入value即可,不需要构造键值对。

3. set中的元素不可以重复(因此可以使用set进行去重)。

4. 使用set的迭代器遍历set中的元素,可以得到有序序列

5. set中的元素默认按照小于来比较

6. set中查找某个元素,时间复杂度为:$log_2 n$

7. set中的元素不允许修改(为什么?)

8. set中的底层使用二叉搜索树(红黑树)来实现。

set的使用

set的使用见:https://cplusplus.com/

map的介绍

1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元 素。

2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair value_type;

3. 在内部,map中的元素总是按照键值key进行比较排序的。

4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序 对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

map的使用

map的使用见:https://cplusplus.com/

multiset的介绍

1. multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。

2. 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是组成 的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器 中进行修改(因为元素总是const的),但可以从容器中插入或删除。

3. 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则 进行排序。

4. multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭 代器遍历时会得到一个有序序列。

5. multiset底层结构为二叉搜索树(红黑树)。

注意:

1. multiset中再底层中存储的是的键值对

2. mtltiset的插入接口中只需要插入即可

3. 与set的区别是,multiset中的元素可以重复,set是中value是唯一的

4. 使用迭代器对multiset中的元素进行遍历,可以得到有序的序列

5. multiset中的元素不能修改

6. 在multiset中找某个元素,时间复杂度为$O(log_2 N)$

7. multiset的作用:可以对元素进行排序

multimap的介绍

1. Multimaps是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对,其中多个键值对之间的key是可以重复的。

2. 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内 容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起, value_type是组合key和value的键值对: typedef pair value_type;

3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对 key进行排序的。

4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代 器直接遍历multimap中的元素可以得到关于key有序的序列。

5. multimap在底层用二叉搜索树(红黑树)来实现。

注意:multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以 重复的。

底层结构

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个 共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中 插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此 map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查 找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

它的左右子树都是AVL树

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

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

AVL树节点的定义

template<class T>
struct AVLTreeNode
{
 AVLTreeNode(const T& data)
     : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
 , _data(data), _bf(0)
 {}
 AVLTreeNode<T>* _pLeft;   // 该节点的左孩子
 AVLTreeNode<T>* _pRight;  // 该节点的右孩子
 AVLTreeNode<T>* _pParent; // 该节点的双亲
 T _data;
 int _bf;                  // 该节点的平衡因子
};

AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构, 使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

左单旋

值得注意的是,这里的h可以为0,也就是说a/b/c可以为空。 

//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* pparent = parent->_parent;
		Node* subRL = subR->_left;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
		}
		else
		{
			_root = subR;
		}
		if (subRL)
		{
			subRL->_parent = parent;
		}
		parent->_right = subRL;
		parent->_parent = subR;
		subR->_parent = pparent;
		subR->_left = parent;
		parent->_bf = subR->_bf = 0;
	}

右单旋

//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* pparent = parent->_parent;
		Node* subLR = subL->_right;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
		}
		else
		{
			_root = subL;
		}
		subL->_parent = pparent;

		if (subLR)
		{
			subLR->_parent = parent;
		}
		parent->_left = subLR;
		parent->_parent = subL;
		subL->_right = parent;
		parent->_bf = subL->_bf = 0;
	}

先右单旋再左单旋

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

		RotateR(subR);
		RotateL(parent);

		if (-1 == bf)
		{
			subR->_bf = 1;
		}
		else if (1 == bf)
		{
			parent->_bf = -1;
		}
	}

先左单旋再右单旋

 

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

		RotateL(subL);
		RotateR(parent);

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

模拟实现AVL树

AVL树的平衡因子是向上更新的, 是否继续更新依据:子树的高度是否变化

1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1

说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新

 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,

 parent所在子树高度变了,继续往上更新

 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则

旋转的目的:

1、让这颗子树左右高度不超过1

2、旋转过程中继续保持他是搜索树

3、更新调整孩子节点的平衡因子

4、让这颗子树的高度跟插入前保持一致

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


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

	AVLTreeNode<K, V>* _parent;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;

	pair<K, V> _kv;
	int _bf;//balance  factor
};



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

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

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

		while (parent)
		{
			if (parent->_left == cur)
			{
				--(parent->_bf);
			}
			else
			{
				++(parent->_bf);
			}

            // 是否继续更新依据:子树的高度是否变化
			// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
			// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
			// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
			// parent所在子树高度变了,继续往上更新
			// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
			// 就地处理--旋转

			// 旋转:
			// 1、让这颗子树左右高度不超过1
			// 2、旋转过程中继续保持他是搜索树
			// 3、更新调整孩子节点的平衡因子
			// 4、让这颗子树的高度跟插入前保持一致

			if (0 == parent->_bf)//当前树的高度并未增加
			{
				return true;
			}
			else if(1 == parent->_bf || -1 == parent->_bf)//当前树的高度+1
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (2 == parent->_bf || -2 == parent->_bf)//左右树高度差为2,需要旋转
			{
				if (parent->_bf == 2 && parent->_right->_bf == 1)//左单旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && parent->_left->_bf == -1)//右单旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && parent->_right->_bf == -1)//右左双旋
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && parent->_left->_bf == 1)//左右双旋
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}

	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* pparent = parent->_parent;
		Node* subRL = subR->_left;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
		}
		else
		{
			_root = subR;
		}
		if (subRL)
		{
			subRL->_parent = parent;
		}
		parent->_right = subRL;
		parent->_parent = subR;
		subR->_parent = pparent;
		subR->_left = parent;
		parent->_bf = subR->_bf = 0;
	}



	//右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* pparent = parent->_parent;
		Node* subLR = subL->_right;
		if (pparent)
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
		}
		else
		{
			_root = subL;
		}
		subL->_parent = pparent;

		if (subLR)
		{
			subLR->_parent = parent;
		}
		parent->_left = subLR;
		parent->_parent = subL;
		subL->_right = parent;
		parent->_bf = subL->_bf = 0;
	}


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

		RotateR(subR);
		RotateL(parent);

		if (-1 == bf)
		{
			subR->_bf = 1;
		}
		else if (1 == bf)
		{
			parent->_bf = -1;
		}
	}

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

		RotateL(subL);
		RotateR(parent);

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

	void Inorder()
	{
		_Inorder(_root);
	}

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

		int lh = Height(root->_left);
		int rh = Height(root->_right);

		return lh > rh ? lh + 1 : rh + 1;
	}

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

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

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

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

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


private:
	void _Inorder(Node* root)
	{
		if (nullptr == root)
			return;

		_Inorder(root->_left);
		cout << root->_kv.first << " : " << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	Node* _root;
};

测试代码:

#include"AVLTree.h"


//void TestAVLTree()
//{
//	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//	//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> t;
//	for (auto e : a)
//	{
//		t.insert(make_pair(e, e));
//	}
//
//	t.Inorder();
//
//	cout << t.IsBalance() << endl;
//}


void TestAVLTree()
{
	srand(time(0));
	const size_t N = 1000;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	t.Inorder();

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


int main()
{
	TestAVLTree();
	return 0;
}

红黑树

红黑树的概念

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

 红黑树的性质:

1. 每个结点不是红色就是黑色

2. 根节点是黑色的 

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点 

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

注意:第三、四条性质是在插入过程中需要严格遵守的。

红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1. 按照二叉搜索的树规则插入新节点

2. 检测新节点插入后,红黑树的性质是否造到破坏

新节点的默认颜色是红色!

新节点的默认颜色是红色!!

新节点的默认颜色是红色!!!

因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整:

 如图,在abcde中任意一个位置插入,其父节点的颜色为黑,没有违反任何规则,不需要进行调整,插入完成。

但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红

有四种插入位置,但是处理 方法都一样,仅以第一种位置进行讲解。

 如图所示,只要将g节点改为红色,将p和u节点改为黑色就可以。完成修改之后分为下列几种情况:

1. g节点的父节点为黑色,完成调整。

2. g节点的父节点为红色,向上进行再一次调整。

3. g节点为根节点,将g节点颜色改为黑色。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

左单旋:

右单旋:

 左右双旋:

右左双旋:

红黑树的模拟实现

#pragma once
#include<iostream>
using namespace std;

enum colour
{
	RED,
	BLACK,
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& p)
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(RED)
		,_kv(p)
	{}

	RBTreeNode<K, V>* _parent;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	colour _col;
	pair<K, V> _kv;
};

template<class K, class V>
class RBTree
{
public:
	typedef RBTreeNode<K, V>  Node;

	RBTree()
		:_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)
		{
			parent = cur;
			if (kv.first == cur->_kv.first)
			{
				return false;
			}
			else if (kv.first < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else
			{
				cur = cur->_right;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}

		while (parent && RED == parent->_col)
		{
			Node* grand = parent->_parent;
			if (parent == grand->_left)
			{
				Node* uncle = grand->_right;
				if (uncle && RED == uncle->_col)//叔叔为红色节点的情况
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grand->_col = RED;
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)//右单旋
					{
						RotateR(grand);
						parent->_col = BLACK;
						grand->_col = RED;
					}
					else//左右双旋
					{
						RotateL(parent);
						RotateR(grand);
						cur->_col = BLACK;
						grand->_col = RED;
					}
					break;
				}
			}
			else if (parent == grand->_right)
			{
				Node* uncle = grand->_left;
				if (uncle && RED == uncle->_col)//叔叔为红色节点的情况
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grand->_col = RED;
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//左单旋
					{
						RotateL(grand);
						parent->_col = BLACK;
						grand->_col = RED;
					}
					else//右左双旋
					{
						RotateR(parent);
						RotateL(grand);
						cur->_col = BLACK;
						grand->_col = RED;
					}
					break;
				}
			}

			//最后强制将根节点颜色置为黑色
			_root->_col = BLACK;
		}
		return true;

	}



	void Inorder()
	{
		_Inorder(_root);
	}

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

		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	bool Check(Node* root, int blackNum, const int ref)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (blackNum != ref)
			{
				cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
				return false;
			}

			return true;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "违反规则:出现连续红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
		{
			++blackNum;
		}

		return Check(root->_left, blackNum, ref)
			&& Check(root->_right, blackNum, ref);
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col != BLACK)
		{
			return false;
		}

		int ref = 0;
		Node* left = _root;
		while (left)
		{
			if (left->_col == BLACK)
			{
				++ref;
			}

			left = left->_left;
		}

		return Check(_root, 0, ref);
	}


private:
	void RotateR(Node* pn)
	{
		Node* ppn = pn->_parent;
		Node* subL = pn->_left;
		Node* subLR = subL->_right;
		if (ppn)
		{
			if (pn == ppn->_left)
			{
				ppn->_left = subL;
			}
			else
			{
				ppn->_right = subL;
			}
		}
		else
		{
			_root = subL;
		}
		subL->_parent = ppn;
		if (subLR)
		{
			subLR->_parent = pn;
		}
		pn->_left = subLR;

		pn->_parent = subL;
		subL->_right = pn;
	}

	void RotateL(Node* pn)
	{
		Node* ppn = pn->_parent;
		Node* subR = pn->_right;
		Node* subRL = subR->_left;
		if (ppn)
		{
			if (pn == ppn->_left)
			{
				ppn->_left = subR;
			}
			else
			{
				ppn->_right = subR;
			}
		}
		else
		{
			_root = subR;
		}
		subR->_parent = ppn;

		if (subRL)
		{
			subRL->_parent = pn;
		}
		pn->_right = subRL;

		subR->_left = pn;
		pn->_parent = subR;

	}



	Node* _root;
};

封装map和set

在我们将红黑树封装为map和set之前,我们先要了解这个封装是怎么实现的。也就是如何安排模板参数,实现代码最大限度的复用 。

注意:从源码中可以看到——创建的节点的内容的类型是由map和set的第二个模板参数决定的。

那么map和set的第一个模板参数的作用是什么呢?第一个模板参数类型是从第二个模板参数类型中能提取出来的数据类型(比如从pair<int , string>中提取出int)。

那么为什么要把第一个类型的数据提取出来呢?是为了比较大小。封装set时当然可以直接比较节点中内容的大小,但如果封装的是map,并不能直接直接比较pair结构,因为我们想要比较的是pair.first,这时候就需要将第一个参数提取出来了。

接下来是迭代器,我们要怎么实现迭代器的迭代(++)呢?可以分为三种情况:

1. 如果右子树不为空,下一个就是右子树的最左节点。

2. 如果右子树为空,去找祖先节点,向上溯源的过程中找到孩子是祖先的左孩子的节点就是下一个节点,如果没有就说明走到了尽头。

 

接下来将红黑树封装为map和set:

RBTree.h

#pragma once
#include<iostream>
using namespace std;

enum colour
{
	RED,
	BLACK,
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& t)
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(RED)
		,_value(t)
	{}

	RBTreeNode<T>* _parent;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	colour _col;
	T _value;
};

template<class V>
class _RBTree_iterator
{
public:
	typedef RBTreeNode<V>  Node;
	_RBTree_iterator(Node* np)
		:_n(np)
	{}

	V& operator*()
	{
		return _n->_value;
	}
	V* operator->()
	{
		return &(_n->_value);
	}

	_RBTree_iterator<V>& operator++()
	{
		if (_n->_right)
		{
			_n = _n->_right;
			while (_n->_left)
			{
				_n = _n->_left;
			}
		}
		else
		{
			Node* parent = _n->_parent;
			while (parent && _n != parent->_left)
			{
				_n = parent;
				parent = parent->_parent;
			}
			_n = parent;
		}
		return *this;
	}

	bool operator!=(const _RBTree_iterator<V>& b)
	{
		return _n != b._n;
	}

private:
	Node* _n;
};

template<class K, class V, class KeyOfV>
class RBTree
{
public:
	typedef RBTreeNode<V>  Node;
	typedef  _RBTree_iterator<V>  iterator;

	iterator begin()
	{
		Node* cur = _root;
		while (cur->_left)
		{
			cur = cur->_left;
		}
		return cur;
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	RBTree()
		:_root(nullptr)
	{}

	bool insert(const V& v)
	{
		if (_root == nullptr)
		{
			_root = new Node(v);
			_root->_col = BLACK;
			return true;
		}
		KeyOfV ext;
		//插入新节点
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (ext(v) == ext(cur->_value))
			{
				return false;
			}
			else if (ext(v) < ext(cur->_value))
			{
				cur = cur->_left;
			}
			else
			{
				cur = cur->_right;
			}
		}
		cur = new Node(v);
		cur->_parent = parent;
		if (ext(v) < ext(parent->_value))
		{
			parent->_left = cur;
		}
		else if (ext(v) > ext(parent->_value))
		{
			parent->_right = cur;
		}

		while (parent && RED == parent->_col)
		{
			Node* grand = parent->_parent;
			if (parent == grand->_left)
			{
				Node* uncle = grand->_right;
				if (uncle && RED == uncle->_col)//叔叔为红色节点的情况
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grand->_col = RED;
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_left)//右单旋
					{
						RotateR(grand);
						parent->_col = BLACK;
						grand->_col = RED;
					}
					else//左右双旋
					{
						RotateL(parent);
						RotateR(grand);
						cur->_col = BLACK;
						grand->_col = RED;
					}
					break;
				}
			}
			else if (parent == grand->_right)
			{
				Node* uncle = grand->_left;
				if (uncle && RED == uncle->_col)//叔叔为红色节点的情况
				{
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grand->_col = RED;
					cur = grand;
					parent = cur->_parent;
				}
				else
				{
					if (cur == parent->_right)//左单旋
					{
						RotateL(grand);
						parent->_col = BLACK;
						grand->_col = RED;
					}
					else//右左双旋
					{
						RotateR(parent);
						RotateL(grand);
						cur->_col = BLACK;
						grand->_col = RED;
					}
					break;
				}
			}

			//最后强制将根节点颜色置为黑色
			_root->_col = BLACK;
		}
		return true;

	}


private:
	void RotateR(Node* pn)
	{
		Node* ppn = pn->_parent;
		Node* subL = pn->_left;
		Node* subLR = subL->_right;
		if (ppn)
		{
			if (pn == ppn->_left)
			{
				ppn->_left = subL;
			}
			else
			{
				ppn->_right = subL;
			}
		}
		else
		{
			_root = subL;
		}
		subL->_parent = ppn;
		if (subLR)
		{
			subLR->_parent = pn;
		}
		pn->_left = subLR;

		pn->_parent = subL;
		subL->_right = pn;
	}

	void RotateL(Node* pn)
	{
		Node* ppn = pn->_parent;
		Node* subR = pn->_right;
		Node* subRL = subR->_left;
		if (ppn)
		{
			if (pn == ppn->_left)
			{
				ppn->_left = subR;
			}
			else
			{
				ppn->_right = subR;
			}
		}
		else
		{
			_root = subR;
		}
		subR->_parent = ppn;

		if (subRL)
		{
			subRL->_parent = pn;
		}
		pn->_right = subRL;

		subR->_left = pn;
		pn->_parent = subR;

	}



	Node* _root;
};

set.h

#pragma once
#pragma once
#include"RBTree.h"

namespace szg
{
	template<class K>
	class set
	{
	public:
		struct setKeyOfT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
		typedef typename RBTree<K, K, setKeyOfT>::iterator  iterator;
		iterator begin()
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();
		}

		bool insert(const K& k)
		{
			return _t.insert(k);
		}
	private:
		RBTree<K, K, setKeyOfT> _t;
	};



}

map.h

#pragma once
#include"RBTree.h"

namespace szg
{
	template<class K, class V>
	class map
	{
	public:
		struct mapKeyOfT
		{
			const K& operator()(const pair<K, V>& v)
			{
				return v.first;
			}
		};
		typedef typename RBTree<K, pair<const K, V>, mapKeyOfT>::iterator iterator;
		iterator begin()
		{
			return _m.begin();
		}

		iterator end()
		{
			return _m.end();
		}

		bool insert(const pair<K, V>& p)
		{
			return _m.insert(p);
		}

	private:
		RBTree<K, pair<const K, V>, mapKeyOfT> _m;
	};

	void test_map()
	{
		int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		map<int, int> m;
		for (auto e : a)
		{
			m.insert(make_pair(e, e));
		}

		for (auto e : m)
		{
			cout << e.first << ":" << e.second << endl;
		}


	}

}

 

 

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

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

相关文章

【C++】30h速成C++从入门到精通(stack、queuepriority_queue以及deque介绍)

stackstack的介绍https://cplusplus.com/reference/stack/stack/?kwstackstack是一种容器适配器&#xff0c;专门在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特…

详解一致性哈希算法

在单机系统中&#xff0c;所有的数据都存储在同一个服务器下&#xff0c;当数据量越来越多的时候&#xff0c;超过了单机存储容量的上限&#xff0c;就需要使用分布式存储系统&#xff0c;在分布式存储系统重&#xff0c;数据会被拆分到不同的存储服务下&#xff0c;减少单机服…

[数据结构]:12-快速排序(顺序表指针实现形式)(C语言实现)

目录 前言 已完成内容 快速排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortCommon.cpp 05-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代…

【Linux】canal1.1.7同步MySQL8.0.3和Redis

目录前言一、MySQL8配置1. 修改my.cnf2. 重启mysql3. 建用户、授权二、Canal服务端配置1. 下载2. 修改配置3. 启动服务与验证三、Canal客户端编写1. yml配置文件添加canal服务端配置信息和Redis信息2. 配置pom文件3. 代码4. MySQL建表storage.storage5. 启动客户端与验证参考前…

中微8S6990使用过程的一些记录--GPIO初始化、定时器、PWM、ADC、休眠等外设的配置和使用

前言 最近把一款产品的代码从新唐MS51移植到了中微8S6990平台上&#xff0c;记录下移植过程遇到的各种情况。 目录前言定时器初始化、中断服务函数GPIO配置ADC模数转换初始化PWM初始化Main函数休眠的一些注意事项最后定时器初始化、中断服务函数 void TMR0_Config(void) {/*(…

keepalived+nginx 双机热备搭建

keepalivednginx 双机热备搭建一、准备工作1.1 准备两台centos7.91.2 nginx 与 keepalived软件 双机安装1.3 ip分配1.4 修改主机名1.5 关闭selinux&#xff08;双机执行&#xff09;1.6 修改hosts&#xff08;双机执行&#xff09;二、安装keepalived2.1 执行一下命令安装keepa…

MidiaPipe +stgcn(时空图卷积网络)实现人体姿态判断(单目标)

文章目录前言Midiapipe关键点检测stgcn 姿态评估效果前言 冒个泡&#xff0c;年少无知吹完的牛皮是要还的呀。 那么这里的话要做的一个东西就是一个人体的姿态判断&#xff0c;比如一个人是坐着还是站着还是摔倒了&#xff0c;如果摔倒了我们要做什么操作&#xff0c;之类的。…

【模型复现】-alexnet,nn.Sequential顺序结构构建网络

深度卷积神经网络&#xff08;AlexNet&#xff09; 在LeNet提出后的将近20年里&#xff0c;神经网络一度被其他机器学习方法超越&#xff0c;如支持向量机。虽然LeNet可以在早期的小数据集上取得好的成绩&#xff0c;但是在更大的真实数据集上的表现并不尽如人意。一方面&#…

第五章 事务管理

1.事务概念 *什么是事务&#xff1a;事务是数据库操作最基本单元&#xff0c;逻辑上是一组操作&#xff0c;要么都成功&#xff0c;要么都失败 *事务的特性&#xff08;ACID&#xff09;&#xff1a;原子性、隔离性、一致性、持久性 2.搭建事务操作环境 *模拟场景&#xff…

uart串口接收模块

uart串口接收模块 1、UART&#xff08;异步串行接口&#xff09; 串行通信&#xff1a;指利用一条数据线将资料一位位的顺序传输。   异步通信&#xff1a;以一个字符为传输单位&#xff0c;通信中两个字符间的时间间隔是不固定的&#xff0c;然而在同一个字符的两个相邻位代…

【微信小程序】-- 页面事件 - 下拉刷新(二十五)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

高盐废水除钙镁的技术解析

高盐废水指含有机物和至少总溶解固体(totaldissolvedsolids&#xff0c;tds)的质量分数大于3.5&#xff05;的废水&#xff0c;具有水量大&#xff0c;无机盐离子k、na、ca2、mg2、cl-、so42-等含量高&#xff0c;水质水量变化大&#xff0c;成分复杂&#xff0c;难生化降解等特…

2023年中职网络安全竞赛——CMS网站渗透解析

需求环境可私信博主 解析如下: CMS网站渗透 任务环境说明: 服务器场景:Server2206(关闭链接) 服务器场景操作系统:未知 1.使用渗透机对服务器信息收集,并将服务器中网站服务端口号作为flag提交; Flag:8089

华为套件生态

华为套件生态前言蓝牙设备华为耳机华为鼠标智慧互联超级终端多屏协同远程访问文件共享华为电脑管家我的设备控制中心前言 华为的手机、平板、电脑、耳机、手环、手表等设备可以组成华为生态。以下分享一些生态体验。 蓝牙设备 华为耳机 快速连接 在手机/电脑附近打开华为耳…

里奇RIDGID管线定位仪/探测仪维修SR-20 SR-24 SR-60

美国里奇SeekTech SR-20管线定位仪对于初次使用定位仪的用户或经验丰富的用户&#xff0c;都同样可以轻易上手使用SR-20。SR-20提供许多设置和参数&#xff0c;使得大多数复杂的定位工作变得很容易。此外&#xff0c;当你在不复杂的环境下完成些基本的定位工作时&#xff0c;这…

软件测试7

一 CS和BS软件架构 CS&#xff1a;客户端-服务器端&#xff0c;BS&#xff1a;浏览器端-服务器端 区别总结&#xff1a; 1.效率&#xff1a;c/s效率高&#xff0c;某些内容已经安装在系统中了&#xff0c;b/s每次都要加载最新的数据 2.升级&#xff1a;b/s无缝升级&#xff0c…

【Maven】(五)Maven模块的继承与聚合 多模块项目组织构建

文章目录1.前言2.模块的继承2.1.可继承的标签2.2.超级POM2.3.手动引入自定义父POM3.模块的聚合3.1.聚合的注意事项3.2.反应堆(reactor)4.依赖管理及属性配置4.1.依赖管理4.2.属性配置5.总结1.前言 本系列文章记录了 Maven 从0开始到实战的过程&#xff0c;Maven 系列历史文章清…

三天吃透SpringMVC面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

【操作系统原理实验】命令解释器模拟实现

选择一种高级语言如C/C等&#xff0c;编写一类似于DOS、UNIX中的命令行解释程序。 1)设计系统命名行提示符&#xff1b; 2)自定义命令集&#xff08;8-10个&#xff09;&#xff1b; 3)用户输入help命令以查找命令的帮助&#xff1b; 4)列出命令的功能&#xff0c;区分内部命令…

fuse文件系统调试环境

libfuse源码&#xff1a;GitHub - libfuse/libfuse: The reference implementation of the Linux FUSE (Filesystem in Userspace) interface 一、ubuntu20.04挂载fuse文件系统 1&#xff0c;安装编译工具 apt install ninja-build apt install meson apt install build-ess…