面试官让手撕红黑树,我直接向他秀一手手撕map与set

news2024/11/28 21:44:59

文章目录

  • 一、map与set的STL源码分析
  • 二、改造红黑树
    • 1.基本结构
    • 2.比较
  • 三、迭代器
    • 1.STL源码分析
    • 2. 迭代器
    • 3. 与库里面的迭代器的差异
  • 四、map的[]操作
  • 五、map与set完整代码

一、map与set的STL源码分析

我们首先可以观察到,在set和map中包含有如下的头文件image-20230930161427708

image-20230930161445363

于是我们可以先打开这些头文件,我们先不考虑multimap与multiset

我们可能会因为set只有一个数据存储而会误以为他里面使用的是key模型的红黑树,但是其实不是的。在stl库里面map和set使用的是同一棵key-val的红黑树。

如下所示,可以看出来

image-20230930162019767

不过虽然使用的是key-val的红黑树,但是它的key和val都是key

image-20230930162200834

根据上面的结构,我们似乎可以猜测,难道库里面以空间换时间,为了只使用同一颗树,牺牲了一半的空间吗?当然以上都仅仅只是我们个人的猜测

我们先再来看一下map的底层

image-20230930163006057

我们似乎发现,这个也很奇怪,key是Key这个是可以的,但是val竟然是个pair类型的。

也就是说

  • set是<K,K>模型的树
  • map是<K,pair>模型的树

这样的话,我们就必须得观察一下这棵树到底是什么情况了

如下所示,header就是这棵树的根节点,它的类型是通过的两次typedef出来的,我们可以注意到,这个结点的类型,存储的就是value。

image-20230930163852461

但是这个val不是我们前面所说的key-val中的val。这里的val对于map而言是pair,对于set而言是key

所以说,真正决定树里面存储的是什么,看的是第二个模板参数,而不是第一个模板参数。

我们现在再来观测一下这个红黑树结点里面是什么,如下所示,这里是通过一个继承关系来搞定的。派生类存储的是value,而基类存储的是三叉链的指针,以及颜色

image-20230930164640847

那么在这里我们似乎看到了,在这棵树里面,我们好像并不是很需要这个key类型的模板参数,那么事实上是如此的吗?其实不是的,这个key还必须得传入,因为有一个接口会使用到他。即对于map而言,find这个接口它是通过key来进行比较的。所以必须传入key的类型

image-20230930173856064

二、改造红黑树

1.基本结构

对于我们之前所实现的红黑树,我们可以对其进行修改,以便适应我们的map和set

如下所示,是我们红黑树结构的改变,红色圈出来的部分就是改变的部分。由于map和set都需要使用这颗红黑树,那么我们就得在红黑树这一层实现一层模板。所以,我们利用T类型的不同来进控制,如果是map的话那么T就是pair,如果是set,T就是K

image-20230930182206083

如下是map与set的基本结构

image-20230930182337940

image-20230930183319694

可以看到,我们很巧妙的使用了一层红黑树上的泛型,使用同一个树模板来实现的

如下是调用图

image-20230930183740824

2.比较

如下所示,当我们将原来的kv改为了_data的时候,下面的代码也要随之改变,但是下面的代码真的正确吗?

对于set而言,_data就是目标值的类型,这个类型是什么我们是不确定的,所以需要它自己提供一个运算符重载,或者仿函数来解决。

如果是map的话,这里的_data就是pair类型的,我们就需要去取出第一个元素,也就是key类型的来进行比较。(这里需要注意,虽然pair本身提供了比较,但是这里的比较并非我们所期望的比较方式,我们只需要比较first,而库里面的重载先比较first,然后比较second的)

image-20230930194026975

所以,我们先来解决当_data为pair时候,如何取出第一个元素呢?,因为我们的是泛型,所以这个_data的取法还是需要注意的。这里我们可以使用一共仿函数,但是这里的仿函数与之前不同的是这里的仿函数主要集中解决取数的问题

如下所示,我们在map和set里面写两个内部类,这个内部类里面是一个运算符重载,充当仿函数。对于map萃取处它的key,对于set萃取出它本身

image-20230930200602138

image-20230930200642546

这样的话,我们就可以将仿函数应用于我们的红黑树中了

image-20230930200846472

现在也就成功的解决了如何辨别是map还是set的比较

如下是他们之间的调用关系

image-20230930204819388

对于我们这样的处理,当我们写find函数的时候,也是可以去使用的

image-20230930204914428

然后我们就可以去写出insert的接口了

image-20230930210145612

可以看到代码并未报错

image-20230930210219139

三、迭代器

1.STL源码分析

我们前面的插入功能已经可以正常运行了,现在需要的就是迭代器

image-20230930213146006

从上图中我们就可以很容易的看到,在set中,两个迭代器其实都是const迭代器,这个迭代器也是依靠着红黑树里面的迭代器去实现的

那么我们可以去溯源,去找到红黑树里面这两个迭代器的定义,如下所示

image-20230930213858499

我们可以看到,这个迭代器与链表的迭代器是比较相似的,都是通过使用一个自定义类型来实现的,我们可以继续找到这个迭代器所在的位置,我们还可以发现,这个其实还搞了一层继承

image-20230930214129783

那么我们就在去找到继承的基类,如下所示,可以看到里面就有一个结点指针了

image-20230930214246229

同样的,在派生类中,我们是可以看到很多迭代器的操作的

image-20230930214625594

2. 迭代器

对于这里的迭代器,我们可以参考库里面的代码去进行实现,即专门封装一个迭代器类。

这个迭代器类专门用来处理红黑树中的迭代器

如下所示,是我们完成的迭代器类,这个迭代器我们先只考虑++功能,解引用功能,还有比较功能。这是为了方便后面进行测试,后续可以进行补充。

在这个迭代器类中,只有一个成员变量_node,这里的解引用功能的重载是比较简单的,直接返回对该_node解引用返回即可。

需要注意的就是++操作不是那么容易想到的。

对于++操作,即如下所示的红黑树

  • 右树不为空,访问的是右树的最左结点(即最小结点)

  • 右树为空,那么下一个访问的就是孩子是父亲的左孩子的那个祖先(这句话听起来可能有点绕口,因为我们可以根据下图去分析,如果parent的右孩子是cur,那么就说明此时的parent结点已经被访问过了,如果parent的左孩子是cur,那么就说明此时的parent没有被访问。当然还需要考虑parent为空的时候,也代表着已经访问结束了,此时说明,整棵树全部被访问了)

image-20231001200726053

template<class T>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T> Self;
	Node* _node;

	__TreeIterator(Node* node)
		:_node(node)
	{}

	T& operator*()
	{
		return _node->_data;
	}

	T* operator->()
	{
		return &_node->_data;
	}
	
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}

	Self& operator++() 
	{
		if (_node->_right)
		{
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent)
			{
				if (parent->_right == cur)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					break;
				}
			}
			_node = parent;
		}
		return *this;
	}

};

有了迭代器类,我们可以在红黑树层次去调用这个迭代器类了

image-20231001201203688

然后我们依次到map和set层次去封装

如下是set

image-20231001201245943

如下是map

image-20231001201305547

不过在这里我们需要考虑到的问题就是,在我们使用typedef去定义一个类型的时候,这里我们需要将红黑树中的迭代器给再次typedef,但是编译器一开始没有实例化模板,并不知道这个iterator是内嵌类型还是一个静态成员变量。所以我们需要使用typename来说明他是一个类型。

有了如上的封装之后,我们就可以简单的跑一下迭代器了

image-20231001201609092

不过在这里我们似乎发现了一个问题,那就是我们的set里面的数据应该是不可以被修改的,但是我们对他进行了修改,而且还可以成功使用。这是我们不可以接收的,因为一旦修改了set里面的数据,那么就意味着这棵树已经乱了。可能不再是一个搜索二叉树了。

而在库里面是这样实现的,对于set,两个迭代器本质都是const迭代器。对于map,它的key应该不可以被修改,这里是通过对pair的第一个参数进行const限定的,从而锁死了第一个参数。

image-20231001202458858

那么我们先来解决一下set的问题,首先我们需要提供一个const迭代器。这个迭代器与之前链表的十分相似,我们可以直接通过多传两个模板参数来进行控制,因为const的本质其实就仅仅只是返回值不可以修改罢了。

下面是红黑树中的修改

image-20231001215606560

image-20231001215625616

然后我们来修改set中的迭代器函数,如下所示,注意这里我们千万不能写上我们注释掉的部分,因为我们只是想把set的迭代器给控制住。但是我们的set对象本身还是需要增加和删除的。所以我们可以用const修饰this指针,来使得set的权限缩小,以至于里面的红黑树就变成了const类型了,就可以去调用红黑树中的const迭代器了。如果我们不去进行权限缩小,那么这颗红黑树就是一个普通对象,就会去调用红黑树中的普通迭代器,就会产生类型不匹配问题了。

image-20231001215719827

image-20231001215959252

然后我们来处理map的迭代器问题

如下所示,处理了set以后,map就简单了,我们只需要让pair中的第一个参数给带上const即可。这样一来,无论如何,是无法修改pair的第一个参数的。

image-20231001221739471

通过以下代码的报错,我们可以证明我们的代码确实是可以的

image-20231001221920262

然后这样一来已经处理好了const迭代器的问题了

现在我们继续完善–操作

如下代码所示,完全是与++操作相反的。以前判断右子树,现在判断左子树即可

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent)
			{
				if (parent->_left == cur)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					break;
				}
			}
			_node = parent;
		}
		return *this; 
	}

3. 与库里面的迭代器的差异

上面是我们自己实现的迭代器,但是与库里面的迭代器还是有很大的区别的

实际上在库里面的红黑树还有一个哨兵位

image-20231001234714388

这个哨兵位的意义它的parent指向根节点,左孩子和右孩子分别指向最小和最大,这样的话会使得迭代器找最小结点和最大结点的时候会方便很多

不过虽然如此,但是也是需要付出一定的代价的,那就是对于实现的难度上来说,更加困难了,因为旋转以后这个哨兵位的指针就需要做出一定的改变

而且上面的话对于正常情况下++和–都是没有什么问题的,但是当出现下面的这种特殊情况的时候,就会出现死循环,即parent和cur陷入死循环了。我们就需要额外做出特殊判断了

image-20231001235105791

四、map的[]操作

我们在前面已经知道,map的[]操作主要是依靠insert操作完成的。所以我们还得将insert在进一步完善

image-20231001235453486

于是,我们就要从底层开始修改,先处理红黑树的插入操作

如下就是我们想办法将红黑树的插入操作由bool变为了pair的返回值

	pair<iterator,bool> Insert(const T& data)
	{
		KeyOfT kot;
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK; //根节点必须是黑色
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data); //插入红色结点
		//用于保存新节点的指针
		Node* newnode = cur;

		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//开始处理颜色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;

						break;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;

						break;
					}
				}
			}

		}

		_root->_color = BLACK;
		return make_pair(iterator(newnode), true);
	}

解决了红黑树的返回值,然后我们可以直接修改map与set的返回值了

image-20231002004710948

image-20231002004722848

如上图所示,但是当我们运行的时候,很遗憾,报错了

我们注意到是set中的pair的迭代器出问题了

image-20231002004758120

我们也不难发现问题的根源,就是因为set中的迭代器都是const迭代器,而红黑树是一个普通对象,它返回的是一个普通的迭代器。

所以我们上面的写法出现了类型不兼容的问题

那么我们有没有什么办法可以处理呢?其实是有的,我们可以看库里面是这样处理的

image-20231002005536501

于是我们可以仿照它去实现

image-20231002005409526

但是可惜的是还是没有通过

这里其实涉及到pair的构造函数了

如下代码所示

image-20231002005841491

可以看到,pair其实是通过初始化列表完成的。所以说在将first初始化的时候,会调用它的构造函数。去用它的迭代器去构造一个const迭代器。

这里我们需要注意的一个问题是,这个构造如何写?我们参照库里面的代码。可以看到,库里面专门将普通迭代器和const迭代器给再次typedef了,这样的好处就在于,当这个迭代器类是一个const迭代器的时候,那么这里就是构造函数,如果这个迭代器被实例化为了普通迭代器的时候,这里就是一个拷贝构造函数了

image-20231002010405054

于是我们可以仿照库里面的代码,写出这样的代码

image-20231002010632703

最终我们再次运行,编译成功了

image-20231002010720872

那么最后这个[]操作就很容易实现了

image-20231002011847596

五、map与set完整代码

  1. 红黑树代码
#pragma once



enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	T _data;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	Color _color;
	RBTreeNode(const T& data)
		:_data(data)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_color(RED) // 插入红色结点,违反规则3,只影响一条路径,甚至可能不影响。如果插入黑色结点,违反规则4,影响所有路径
	{}
};

template<class T, class Ptr, class Ref>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;
	typedef __TreeIterator<T, T*, T&> Iterator;

	Node* _node;

	__TreeIterator(Node* node)
		:_node(node)
	{}

	__TreeIterator(const Iterator& it)
		:_node(it._node)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}
	
	bool operator!=(const Self& s) const 
	{
		return _node != s._node;
	}

	bool operator==(const Self& s) const
	{
		return _node == s._node;
	}

	Self& operator++() 
	{
		if (_node->_right)
		{
			Node* subLeft = _node->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
			_node = subLeft;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _node->_parent;
			while (parent)
			{
				if (parent->_right == cur)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					break;
				}
			}
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent)
			{
				if (parent->_left == cur)
				{
					cur = parent;
					parent = parent->_parent;
				}
				else
				{
					break;
				}
			}
			_node = parent;
		}
		return *this; 
	}
};



template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T,T*,T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;


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

	const_iterator begin() const
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return const_iterator(leftMin);
	}
	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
	RBTree()
		:_root(nullptr)
	{}
	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	pair<iterator,bool> Insert(const T& data)
	{
		KeyOfT kot;
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK; //根节点必须是黑色
			return make_pair(iterator(_root), true);
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		cur = new Node(data); //插入红色结点
		//用于保存新节点的指针
		Node* newnode = cur;

		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//开始处理颜色
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
						break;
					}
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = BLACK;
					uncle->_color = BLACK;
					grandfather->_color = RED;

					cur = grandfather;
					parent = grandfather->_parent;
				}
				else if (uncle == nullptr || uncle->_color == BLACK)
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						grandfather->_color = RED;
						parent->_color = BLACK;

						break;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;

						break;
					}
				}
			}

		}

		_root->_color = BLACK;
		return make_pair(iterator(newnode), true);
	}

	void RotateL(Node* parent)
	{
		_rotatecount++;
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		Node* ppnode = parent->_parent;
		//改变parent
		parent->_right = curleft;
		parent->_parent = cur;
		//改变curleft
		if (curleft != nullptr)
		{
			curleft->_parent = parent;
		}
		//改变cur
		cur->_left = parent;
		cur->_parent = ppnode;
		//改变ppnode
		if (ppnode == nullptr)
		{
			_root = cur;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
		}
	}

	void RotateR(Node* parent)
	{
		_rotatecount++;

		Node* cur = parent->_left;
		Node* curright = cur->_right;
		Node* ppnode = parent->_parent;

		//改变parent
		parent->_left = curright;
		parent->_parent = cur;
		//改变curright
		if (curright != nullptr)
		{
			curright->_parent = parent;
		}
		//改变cur
		cur->_right = parent;
		cur->_parent = ppnode;
		//改变ppnode
		if (ppnode == nullptr)
		{
			_root = cur;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
		}
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}
	bool IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_color == RED)
		{
			return false;
		}
		int benchmark = 0;
		Node* cur = root;
		while (cur)
		{
			if (cur->_color == BLACK)
			{
				benchmark++;
			}
			cur = cur->_left;
		}
		return CheckColor(root, 0, benchmark);
	}
	bool CheckColor(Node* root, int blacknum, int benchmark)
	{

		//检查每条路径的黑色结点数量是否相等
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				return false;
			}
			return true;
		}
		if (root->_color == BLACK)
		{
			blacknum++;
		}
		//检查颜色
		if (root->_color == RED && root->_parent && root->_parent->_color == RED)
		{
			cout << root->_kv.first << ":" << "出现两个连续的红色结点" << endl;
			return false;
		}
		return CheckColor(root->_left, blacknum, benchmark) && CheckColor(root->_right, blacknum, benchmark);
	}


	int Height()
	{
		return Height(_root);
	}
	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftheight = Height(root->_left);
		int rightheight = Height(root->_right);

		return leftheight > rightheight ? 1 + leftheight : 1 + rightheight;
	}
	int Getrotatecount()
	{
		return _rotatecount;
	}
private:
	Node* _root;
	int _rotatecount = 0;
};
  1. map代码
#pragma once
#include"RBTree.h"


namespace Sim
{
	template<class K,class V>
	class map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};

		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;


		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		const_iterator begin() const
		{
			return _t.begin();
		}
		const_iterator end() const
		{
			return _t.end();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

		pair<iterator,bool> insert(const pair<const K, V>& kv)
		{
			return _t.Insert(kv);
		}
	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}
  1. set代码
#pragma once

#include"RBTree.h"

namespace Sim
{
	template<class K>
	class set
	{
	public:

		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;


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

		const_iterator begin() const 
		{
			return _t.begin();
		}
		const_iterator end() const
		{
			return _t.end();
		}
		pair<iterator,bool> insert(const K& key)
		{
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
			return pair<iterator, bool>(ret.first, ret.second);
		}
	private:
		RBTree<K, K, SetKeyOfT > _t;
	};
}


  1. 测试代码
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
#include<map>
#include<set>
using namespace std;
#include "MyMap.h"
#include "MySet.h"


void func(const Sim::map<int, int>& m)
{
	Sim::map<int, int>::const_iterator itm = m.begin();
	while (itm != m.end())
	{
		//itm->first = 1;
		//itm->second = 1;
		cout << (*itm).first << " " << itm->second << endl;
		++itm;
	}
	cout << endl;
}

void test1()
{
	Sim::map<int, int> m;
	m.insert(make_pair(1, 1));
	m.insert(make_pair(2, 2));
	m.insert(make_pair(3, 3));
	m.insert(make_pair(5, 5));
	m.insert(make_pair(3, 3));
	m.insert(make_pair(4, 4));


	Sim::map<int, int>::iterator itm = m.begin();
	while (itm != m.end())
	{
		itm->second = 1;
		cout << (*itm).first << " " << itm->second << endl;
		++itm;
	}
	cout << endl;

	Sim::set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(2);
	s.insert(5);
	s.insert(0);

	Sim::set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
void test2()
{
	Sim::map<int, int> m;
	m.insert(make_pair(5, 5));
	m[1] = 2;
	m[2] = 3;
	m[4] = 5;
	m[5] = 1;
	Sim::map<int, int>::iterator itm = m.begin();
	while (itm != m.end())
	{
		cout << (*itm).first << " " << itm->second << endl;
		++itm;
	}
	cout << endl;
}

int main()
{
	test2();

	return 0;
}

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

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

相关文章

28299-2012 结构用热轧翼板钢 课堂随笔

声明 本文是学习GB-T 28299-2012 结构用热轧翼板钢. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了结构用热轧翼板钢的订货内容、尺寸、外形、重量及允许偏差、技术要求、试验方法、检 验规则、包装、标志和质量证明书等。 本…

Python绘图系统24:添加辅助坐标轴

文章目录 辅助坐标增减坐标轴时间轴**代码优化源代码 Python绘图系统&#xff1a; 前置源码&#xff1a; Python打造动态绘图系统&#x1f4c8;一 三维绘图系统 &#x1f4c8;二 多图绘制系统&#x1f4c8;三 坐 标 轴 定 制&#x1f4c8;四 定制绘图风格 &#x1f4c8;五 数据…

面试必考精华版Leetcode199. 二叉树的右视图

题目&#xff1a; 代码&#xff08;首刷看解析&#xff09;&#xff1a; class Solution { public:vector<int> rightSideView(TreeNode* root) {unordered_map<int,int> rightmostvalue;queue<TreeNode*> nodeQueue;queue<int> depthQueue;int maxDe…

上古神器:十六位应用程序 Debug 的基本使用

文章目录 参考环境上古神器 DebugBug 与 DebuggingDebugDebug 应用程序淘汰原因使用限制 DOSBox学习 Debug 的必要性DOSBox-X Debug 的基本使用命令 R查看寄存器的状态修改寄存器的内容 命令 D显示内存中的数据指定起始内存空间地址指定内存空间的范围 命令 A使用命令语法错误查…

第8章 Spring(二)

8.11 Spring 中哪些情况下,不能解决循环依赖问题 难度:★★ 重点:★★ 白话解析 有一下几种情况,循环依赖是不能解决的: 1、原型模式下的循环依赖没办法解决; 假设Girl中依赖了Boy,Boy中依赖了Girl;在实例化Girl的时候要注入Boy,此时没有Boy,因为是原型模式,每次都…

量化交易全流程(四)

本节目录 数据准备&#xff08;数据源与数据库&#xff09; CTA策略 数据源&#xff1a; 在进行量化分析的时候&#xff0c;最基础的工作是数据准备&#xff0c;即收集数据、清理数据、建立数据库。下面先讨论收集数据的来源&#xff0c;数据来源可分为两大类&#xff1a;免…

最新宽字节注入攻击和代码分析技术

点击星标&#xff0c;即时接收最新推文 本文选自《web安全攻防渗透测试实战指南&#xff08;第2版&#xff09;》 点击图片五折购书 宽字节注入攻击 宽字节注入攻击的测试地址在本书第2章。 访问id1&#xff0c;页面的返回结果如图4-51所示&#xff0c;程序并没有报错&#xff…

基于树莓派CM4制作img系统镜像批量制作TF卡

文章目录 前言1. 环境与工具2. 制作镜像3. 烧录镜像4. 总结 前言 树莓派烧录完系统做定制化配置比较费时间。在面对大批量的树莓派要配置&#xff0c;那时间成本是非常巨大的。第一次配置完可以说是摸着石头过河&#xff0c;但是会弄了以后再配置&#xff0c;都是一些重复性操…

Canal实现数据同步

1、Canal实现数据同步 canal可以用来监控数据库数据的变化&#xff0c;从而获得新增数据&#xff0c;或者修改的数据。 1.1 Canal工作原理 原理相对比较简单&#xff1a; 1、canal模拟mysql slave的交互协议&#xff0c;伪装自己为mysql slave&#xff0c;向mysql master发送…

零代码编程:用ChatGPT将特定文件标题重命名为特定格式

一个文件夹里面是同一系列文件&#xff0c;但是有两种命名方法&#xff0c;现在想把文件标题格式统一。 在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个批量重命名的任务&#xff0c;具体步骤如下&#xff1a; 打开本地电脑文件夹&#xf…

38 翻转二叉树

翻转二叉树 理解题意&#xff0c;翻转即每个结点的左右子树翻转/对调题解1 递归——自下而上题解2 迭代——自上而下 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 提示&#xff1a; 树中节点数目范围在 [0, 100] 内-100 < Node.…

树莓派CM4开启I2C与UART串口登录同时serial0映射到ttyS0 开启多串口

文章目录 前言1. 树莓派开启I2C与UART串口登录2. 开启多串口总结&#xff1a; 前言 最近用CM4的时候使用到了I2C以及多个UART的情况。 同时配置端口映射也存在部分问题。 这里集中记录一下。 1. 树莓派开启I2C与UART串口登录 输入指令sudo raspi-config 跳转到如下界面&#…

机器人过程自动化(RPA)入门 9. 管理和维护代码

仅仅创建一个自动化项目是不够的。无论是决定使用哪种布局,还是正确命名步骤,以正确的方式组织项目都很重要。项目也可以在新的项目中重用,这对用户来说非常方便。本章解释了我们可以重用项目的方法。我们还将学习配置技术并看到一个示例。最后,我们将学习如何集成TFS服务器…

第十二章 类和对象——封装

C面向对象的三大特性为&#xff1a;封装、继承、多态 C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 例如&#xff1a; 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...&#xff0c;行为有走、跑、跳、吃饭、唱歌... 车也可以作为对象&#xff0c;…

89、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->Zset 相关命令

本次讲解要点&#xff1a; ** Set相关命令&#xff1a;是指value中的数据类型** 启动redis服务器&#xff1a; 打开小黑窗&#xff1a; C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe …

Android-Q 对 startActivity() 做了限制,怎么适配?

一. Q 禁用后台启动 Activity 今天来聊聊“Android Q 中后台禁止启动 Activity 对现有国内 App 中启动页设计的影响”这个话题&#xff0c;再聊聊 Android Q 限制后台启动 Activity 的具体细节。 有人可能会觉得 P 还没用上&#xff0c;Q 还远着。如果只是对于普通用户来说&a…

为什么要使用虚拟机?VMware安装使用

前言 大家好&#xff0c;本文是讲述了为什么需要使用虚拟机、使用虚拟机的好处&#xff0c;以及如何在Windows系统中安装VMware。希望对大家有所帮助~ 目录 前言一、为什么要安装使用虚拟机&#xff1f;1.1、什么是虚拟机&#xff1f;1.2、虚拟机的核心组件1.3、使用虚拟机的好…

【RabbitMQ】常用消息模型详解

文章目录 AMQP协议的回顾RabbitMQ支持的消息模型第一种模型(直连)开发生产者开发消费者生产者、消费者开发优化API参数细节 第二种模型(work quene)开发生产者开发消费者消息自动确认机制 第三种模型(fanout)开发生产者开发消费者 第四种模型(Routing)开发生产者开发消费者 第五…