C++第三十八弹---一万六千字使用红黑树封装set和map

news2024/11/18 9:42:23

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1、set/map基本结构

2、红黑树基本结构改造

 3、红黑树的迭代器

4、set的模拟实现

5、map的模拟实现

6、完整代码


1、set/map基本结构

在封装set/map之前,我们先看看stl源码大致是如何实现的。

set基本结构

map基本结构

 

从上面两张图我们可以看到,set和map的底层都是红黑树,红黑树的模板参数有5个,我们先知道前面两个即可,第一个key_type指的是key值,value_type指的是"value"值,我们知道set只有key值,而map有key和value,那这里是怎么通过一个模板参数实现两个容器的呢?

        答案是set传参时,第一个和第二个传的都是key值;map传参时,第一个传key值,第二个传pair<K,V>键值对。

为什么有了pair<K,V>键值对还需要key值呢?

因为查找的时候需要通过key值查找。

2、红黑树基本结构改造

红黑树结点结构

只需对有效数据进行修改,改为T类型的模板,根据需要实例化红黑树。

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;// set传key值,map传pair键值对
	
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

红黑树结构 

红黑树的基本结构只需对插入进行修改增加拷贝构造赋值操作符重载析构函数即可。

插入函数

插入时直接插入T类型的data值,但是在查找插入的位置时,有两种情况,如果插入的值是key值时,可以直接通过data比较大小,但是插入pair<K,V>值时,需要通过该值的first成员比较大小,那么如何能够让两者统一呢?

        此处的解决办法是使用仿函数,获取key值。

set仿函数

struct SetKeyOfT
{
	const K& operator()(const K& key)
	{
		return key;// 直接返回key值
	}
};

map仿函数

struct MapKeyOfT
{
	const K& operator()(const pair<K,V>& kv)
	{
		return kv.first;// 返回first成员
	}
};

插入函数 

只需在比较时进行修改,使用仿函数,返回值为键值对,第一个成员为插入位置的迭代器,第二个成员为bool值,插入成功返回true,失败返回false。

pair<Iterator,bool> Insert(const T& data)
{
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		// 插入新结点,返回插入位置迭代器+true
		return make_pair(Iterator(_root),true);
	}

	KeyOfT kot;// 仿函数对象,获取key值
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		// kot对象取T类型中data对象的key
		if (kot(cur->_data) < kot(data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(cur->_data) > kot(data))
		{
			parent = cur;
			cur = cur->_left;
		}
		// 二叉搜索树默认不能冗余,因此相等则返回false
		else
		{
			// 冗余返回当前结点迭代器+false
			return make_pair(Iterator(cur),false);
		}
	}
	cur = new Node(data);
	Node* newnode = cur;//存新插入节点的地址
	cur->_col = RED;
	if (kot(parent->_data) < kot(data))
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续向上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			// 叔叔不存在或叔叔为黑
			else
			{
				if (cur == parent->_left)
				{
					//    g
					//  p   u
					//c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//    g
					//  p   u
					//    c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			// 叔叔存在且为红,-》变色即可
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else // 叔叔不存在,或者存在且为黑
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				//      g
				//   u     p
				//            c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//		g
					//   u     p
					//      c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break;
			}
		}
	}
	_root->_col = BLACK;

	// 返回插入节点位置迭代器+true
	return make_pair(Iterator(newnode), true);
}

拷贝构造

拷贝构造需要拷贝原结点的有效数据结构的链接关系结点的颜色。此处还有一个问题,如果自己实现了构造函数,编译器就不会自动生成默认构造,当使用无参构造红黑树时会报错,因此此处需要实现默认构造

拷贝函数

Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;

	// 新创建结点,使用前序链接结点
	Node* newroot = new Node(root->_data);
	newroot->_col = root->_col;// 更新颜色
	
	newroot->_left = Copy(root->_left);
    // 左孩子不为空需要链接双亲结点
	if (newroot->_left)
		newroot->_left->_parent = newroot;

	newroot->_right = Copy(root->_right);
    // 右孩子不为空需要链接双亲结点
	if (newroot->_right)
		newroot->_right->_parent = newroot;

	return newroot;
}

拷贝构造函数 

// 强制生成默认构造
RBTree() = default;

// 拷贝构造 解决深拷贝问题,写了拷贝构造则不自动生成默认构造
RBTree(const RBTree<K, T, KeyOfT>& t)
{
	_root = Copy(t._root);
}

赋值操作符重载 

赋值操作符重载的实现与stl容器类似,可以直接使用现代写法,交换地址即可,但是形参不能加const。

RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
{
	swap(_root, t._root);// 使用现代写法,交换地址即可
	return *this;
}

析构函数

析构函数将动态开辟的空间手动释放即可。

释放空间函数

void Destroy(Node* root) 
{
	if (root == nullptr)
		return;
    // 使用后序释放空间
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;// 手动置空
}

析构函数 

~RBTree()
{
	Destroy(_root);
	_root = nullptr;// 手动将_root置空
}

 3、红黑树的迭代器


迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:
begin()与end()
STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?此处为了简单实现红黑树,我们将end()给nullptr

迭代器结点类

解引用,箭头,不等于这些函数重载与链表的迭代器实现类似,因此此处不做详细讲解,单独对前置++详细讲解,uu们可以自己分析实现后置++和--函数。

前置++的实现有两种情况:

  • 1、当前结点的右子树不为空,下一个访问的结点为右子树的最左结点
  • 2、当前结点的右子树为空,下一个访问的结点为孩子是父亲的左祖先

右子树不为空

右子树为空 

前置++ 

Self& operator++()
{
	// 右子树不为空 访问右子树的最左结点
	if (_node->_right)
	{
		Node* leftMin = _node->_right;
		while (leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		_node = leftMin;
	}
	// 右子树为空,找孩子是父亲的左祖先
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		// 当前结点是最右结点时,parent为空
		while (parent && cur != parent->_left)
		{
			cur = parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

迭代器完整代码 

template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	Node* _node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;

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

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

begin()和end()函数的获取

begin()在红黑树中最小节点(即最左侧节点)的位置。end()放在最大节点(最右侧节点)的下一个位置,此处给为nullptr。

实现上面的函数之前需要在红黑树类中typedef该迭代器。

typedef __RBTreeIterator<T, T&, T*> Iterator;// 普通迭代器
typedef __RBTreeIterator<T, const T&, const T*> ConstIterator;// const迭代器

begin()函数的获取

begin()在红黑树中最小节点(即最左侧节点)的位置。

// 普通迭代器
Iterator Begin()
{
	Node* leftMin = _root;
	// leftMin可能为空
	while (leftMin && leftMin->_left)
	{
		leftMin = leftMin->_left;
	}
    // 返回最左侧结点
	return Iterator(leftMin);
}
// const迭代器
ConstIterator Begin() const
{
	Node* leftMin = _root;
	// leftMin可能为空
	while (leftMin && leftMin->_left)
	{
		leftMin = leftMin->_left;
	}

	return ConstIterator(leftMin);
}

end()函数的获取

end()放在最大节点(最右侧节点)的下一个位置,此处给为nullptr。

// 结尾为空,开始为空时则不进入不等于的循环
Iterator End()
{
	return Iterator(nullptr);
}
ConstIterator End() const
{
	return ConstIterator(nullptr);
}

4、set的模拟实现

template<class K>
class set
{
    // set获取key值仿函数,传入红黑树第三个模板参数
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	// Iterator不知道是静态成员变量还是类型,使用typename
	typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

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

	iterator begin()
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}
	pair<iterator, bool> insert(const K& key)
	{
		return _t.Insert(key);
	}
	iterator find(const K& key)
	{
		return _t.Find(key);
	}
private:
	// 加const 时key不能修改
	RBTree<K, const K, SetKeyOfT> _t;
};

set测试代码

void PrintSet(const set<int>& s)
{
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}
void test_set()
{
	set<int> s;
	s.insert(4);
	s.insert(2);
	s.insert(5);
	s.insert(15);
	s.insert(7);
	s.insert(1);
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//*it += 5;// 不能修改
		cout << *it << " ";
		++it;
	}
	cout << endl;

	PrintSet(s);
	// 浅拷贝问题
	set<int> copy(s);// 拷贝构造
	PrintSet(copy);

	copy = s;// 赋值操作符重载
	PrintSet(copy);
}

5、map的模拟实现

template<class K,class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K,V>& kv)
		{
			return kv.first;
		}
	};
public:
	// Iterator不知道是静态成员变量还是类型,使用typename
	typedef typename RBTree<K, pair<const K,V>, MapKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, pair<const K,const V>, MapKeyOfT>::ConstIterator const_iterator;

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

	iterator begin()
	{
		return _t.Begin();
	}
	iterator end()
	{
		return _t.End();
	}
	pair<iterator, bool> insert(const pair<K,V>& kv)
	{
		return _t.Insert(kv);
	}
	iterator find(const K& key)
	{
		return _t.Find(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	// pair加const,key不能修改
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

map测试代码

void test_map1()
{
	map<string,int> m;
	m.insert({ "苹果",1 });
	m.insert({ "草莓",3 });
	m.insert({ "香蕉",2 });
	m.insert({ "苹果",4 });

	map<string, int>::iterator it = m.begin();
	while (it != m.end())
	{
		//it->first += 'x';
		it->second += 'y';
		//cout << it.operator->()->first << ":" << it->second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;

	// const迭代器初始化时需要给模板参数加const
	const map<string, const int> m1;
	
	map<string, const int>::const_iterator it1 = m1.begin();
	while (it1 != m1.end())
	{
		//it1->second += 'y';
		cout << it1->first << ":" << it1->second << endl;
		++it1;
	}
	cout << endl;
}
void test_map2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
	// 使用map计算水果的个数
	map<string, int> countMap;
	for (auto& str : arr)
	{
		// 水果存在则value值++,不存在则新增该水果并将value值++
		countMap[str]++;
	}
	// 按照key值排序
	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

6、完整代码

RBTree.h

#pragma once
#include<vector>

enum Colour
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};

template<class T,class Ref,class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	Node* _node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;

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

	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	bool operator!=(const Self& s)
	{
		return _node != s._node;
	}
	Self& operator++()
	{
		// 右子树不为空 访问右子树的最左结点
		if (_node->_right)
		{
			Node* leftMin = _node->_right;
			while (leftMin->_left)
			{
				leftMin = leftMin->_left;
			}

			_node = leftMin;
		}
		// 右子树为空,找孩子是父亲的左祖先
		else
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			// 当前结点是最右结点时,parent为空
			while (parent && cur != parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

template<class K,class T,class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> Iterator;
	typedef __RBTreeIterator<T, const T&, const T*> ConstIterator;

	ConstIterator Begin() const
	{
		Node* leftMin = _root;
		// leftMin可能为空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return ConstIterator(leftMin);
	}
	
	ConstIterator End() const
	{
		return ConstIterator(nullptr);
	}

	Iterator Begin()
	{
		Node* leftMin = _root;
		// leftMin可能为空
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return Iterator(leftMin);
	}
	// 结尾为空,开始为空时则不进入不等于的循环
	Iterator End()
	{
		return Iterator(nullptr);
	}

	// 强制生成默认构造
	RBTree() = default;

	// 拷贝构造 解决深拷贝问题,写了拷贝构造则不自动生成默认构造
	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = Copy(t._root);
	}

	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		swap(_root, t._root);
		return *this;
	}

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

	Iterator Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				// 找到返回当前结点迭代器
				return Iterator(cur);
			}
		}
		// 没找到返回End()
		return End();
	}

	pair<Iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			// 根节点为黑色
			_root->_col = BLACK;
			// 插入新结点,返回插入位置迭代器+true
			return make_pair(Iterator(_root),true);
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			// 插入值更大则插入到右边
			// kot对象取T类型中Data对象的key
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			// 小则在左边
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			// 二叉搜索树默认不能冗余,因此相等则返回false
			else
			{
				// 冗余返回当前结点迭代器+false
				return make_pair(Iterator(cur),false);
			}
		}
		// 为空则找到插入位置 需要先找到父亲的位置
		// key值大于父亲的值则在右侧
		cur = new Node(data);
		Node* newnode = cur;//存新插入节点的地址
		// 新增结点为红色
		cur->_col = RED;
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 父亲为红色节点则需要调整颜色
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				// 叔叔不存在或叔叔为黑
				else
				{
					if (cur == parent->_left)
					{
						//    g
						//  p   u
						//c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//    g
						//  p   u
						//    c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				// 叔叔存在且为红,-》变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在,或者存在且为黑
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}
		_root->_col = BLACK;

		// 返回插入节点位置迭代器+true
		return make_pair(Iterator(newnode), true);
	}
	// 右单旋 左边较高
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		// 更新parent
		parent->_left = subLR;
		// subLR不为空则更新父亲
		if (subLR)
			subLR->_parent = parent;

		subL->_right = parent;//xxx

		// 提前存parent的父亲
		Node* ppNode = parent->_parent;
		parent->_parent = subL;

		// parent为根节点
		if (parent == _root)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			// parent为ppNode的左结点
			if (parent == ppNode->_left)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}
	}
	// 左单旋 右边较高
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		// 更新parent
		parent->_right = subRL;
		// subRL不为空
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		// 提前存parent的父结点
		Node* ppNode = parent->_parent;
		parent->_parent = subR;

		// parent为根节点
		if (parent == _root)
		{
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			// 左
			if (parent == ppNode->_right)
			{
				ppNode->_right = subR;
			}
			else
			{
				ppNode->_left = subR;
			}
			subR->_parent = ppNode;
		}
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance()
	{
		// 1、判断根节点是否为黑
		if (_root->_col == RED)
		{
			return false;
		}

		int refNum = 0;// 最左路径的黑色结点个数,参考数量
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				refNum++;
			}
			cur = cur->_left;
		}
		return Check(_root,0,refNum);
	}
private:
	void Destroy(Node* root) 
	{
		if (root == nullptr)
			return;

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		// 新创建结点
		Node* newroot = new Node(root->_data);
		newroot->_col = root->_col;// 更新颜色
		
		newroot->_left = Copy(root->_left);
		if (newroot->_left)
			newroot->_left->_parent = newroot;

		newroot->_right = Copy(root->_right);
		if (newroot->_right)
			newroot->_right->_parent = newroot;

		return newroot;
	}

	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 refNum)
	{
		if (root == nullptr)
		{
			// 2、判断是否存在相同数量的黑色节点路径
			if (blackNum != refNum)
			{
				cout << "存在黑色结点数量不相等的路径" << endl;
				return false;
			}
			return true;
		}
			
		if (root->_col == RED && root->_parent->_col == RED)
		{
			// 3、判断是否存在连续的红色结点
			cout << root->_kv.first << "存在连续的红色结点" << endl;
			return false;
		}

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

		return Check(root->_left,blackNum,refNum)
			&& Check(root->_right, blackNum, refNum);
	}
private:
	Node* _root = nullptr;
	//size_t _size = 0;
};

Myset.h

#pragma once
#include "RBTree.h"

namespace lin
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		// Iterator不知道是静态成员变量还是类型,使用typename
		typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

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

		iterator begin()
		{
			return _t.Begin();
		}
		iterator end()
		{
			return _t.End();
		}
		pair<iterator, bool> insert(const K& key)
		{
			return _t.Insert(key);
		}
		iterator find(const K& key)
		{
			return _t.Find(key);
		}
	private:
		// 加const 时key不能修改
		RBTree<K, const K, SetKeyOfT> _t;
	};
	void PrintSet(const set<int>& s)
	{
		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void test_set()
	{
		set<int> s;
		s.insert(4);
		s.insert(2);
		s.insert(5);
		s.insert(15);
		s.insert(7);
		s.insert(1);
		s.insert(5);
		s.insert(7); 
		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			//*it += 5;

			cout << *it << " ";
			++it;
		}
		cout << endl;

		//for (auto e : s)
		//{
		//	cout << e << endl;
		//}
		PrintSet(s);
		// 浅拷贝问题
		set<int> copy(s);
		PrintSet(copy);

		copy = s;
		PrintSet(copy);
	}
}

Mymap.h

#pragma once
#include "RBTree.h"

namespace lin
{
	template<class K,class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
	public:
		// Iterator不知道是静态成员变量还是类型,使用typename
		typedef typename RBTree<K, pair<const K,V>, MapKeyOfT>::Iterator iterator;
		typedef typename RBTree<K, pair<const K,const V>, MapKeyOfT>::ConstIterator const_iterator;

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

		iterator begin()
		{
			return _t.Begin();
		}
		iterator end()
		{
			return _t.End();
		}
		pair<iterator, bool> insert(const pair<K,V>& kv)
		{
			return _t.Insert(kv);
		}
		iterator find(const K& key)
		{
			return _t.Find(key);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		// pair加const,key不能修改
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
	void test_map1()
	{
		map<string,int> m;
		m.insert({ "苹果",1 });
		m.insert({ "草莓",3 });
		m.insert({ "香蕉",2 });
		m.insert({ "苹果",4 });

		map<string, int>::iterator it = m.begin();
		while (it != m.end())
		{
			//it->first += 'x';
			it->second += 'y';
			//cout << it.operator->()->first << ":" << it->second << endl;
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;

		// const迭代器初始哈时需要给模板参数加const
		const map<string, const int> m1;
		
		map<string, const int>::const_iterator it1 = m1.begin();
		while (it1 != m1.end())
		{
			//it1->second += 'y';
			cout << it1->first << ":" << it1->second << endl;
			++it1;
		}
		cout << endl;
	}
	void test_map2()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
		// 使用map计算水果的个数
		map<string, int> countMap;
		for (auto& str : arr)
		{
			// 水果存在则value值++,不存在则新增该水果并将value值++
			countMap[str]++;
		}
		// 按照key值排序
		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
}

Test.cpp 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include "RBTree.h"
#include "Myset.h"
#include "Mymap.h"

int main()
{
	//lin::test_set();
	lin::test_map2();
	return 0;
}

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

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

相关文章

三元组损失Triplet loss 详解

深度神经网络在识别模式和进行预测方面表现出色,但在涉及图像识别任务时,它们常常难以区分相似个体的图像。三元组损失是一种强大的训练技术,可以解决这个问题,它通过学习相似度度量,在高维空间中将相似图像准确地嵌入到彼此接近的位置。 在这篇文章中,我们将以简单的技术术语解…

程序员为什么要一直写bug ,不能一次性写好吗?

文章目录 一、前言二、为什么要写bug1、程序员的疑问&#xff1f;2、bug产生的原因3、减少bug的措施4、程序员不是机器 三、写代码的好习惯&#xff0c;减少80%的bug1、修改完代码&#xff0c;记得自测一下2、方法入参尽量都检验3、修改老接口的时候&#xff0c;思考接口的兼容…

SpringBoot3核心特性-快速入门

目录 传送门前言一、简介1、前置知识2、 环境要求3、SpringBoot是什么 二、快速体验1、开发流程2、特性小结3、Spring Initializr 创建向导 三、应用分析1、依赖管理机制2、自动配置机制2.1、初步理解2.2、完整流程2.3、如何学好SpringBoot 四、核心技能1、常用注解1.1、组件注…

Spring Boot的自动装配机制?(Spring Boot怎么完成自动装配的?)----面试常问

Spring Boot的自动装配机制&#xff1f;(Spring Boot怎么完成自动装配的?) 目录 一、概念版&#xff08;重要&#xff09; 二、实操版 1. 依赖管理 (pom.xml导坐标) 2. 自动配置类 2.1 SpringBootApplication 注解 2.2 EnableAutoConfiguration 2.3 Import({AutoCon…

基于node.js的宠物寄存管理系统,基于express的宠物寄存系统

摘 要 伴随着社会以及科学技术的发展&#xff0c;互联网已经渗透在人们的身边&#xff0c;网络慢慢的变成了人们的生活必不可少的一部分&#xff0c;紧接着网络飞速的发展&#xff0c;系统管理这一名词已不陌生&#xff0c;越来越多的宠物店等机构都会定制一款属于自己个性化…

2.8销毁窗口

目录 1.实验原理 2.实验代码 3.运行结果 1.实验原理 销毁某一个指定名称的窗口 destoryWindow 函数原型 Destroys a window.void destroyWindow(const string& winname)&#xff1b;含义 功能: 销毁指定名称的窗口。 参数: const string& winname: 一个字符串&am…

香港租云服务器多少钱一台?

香港租云服务器多少钱一台&#xff1f;香港云服务器的租用价格因配置、带宽、服务等级等因素而异&#xff0c;从数百元到数千元不等。例如&#xff0c;入门级服务器的价格在数百元至一千元之间&#xff0c;适用于个人网站、小型博客等低流量应用。标准型服务器的价格在一千元至…

电商人必看!4招拿捏消费者心理,没有卖不出去的产品

在竞争激烈的电商领域&#xff0c;产品如何脱颖而出&#xff0c;成为消费者心中的首选&#xff0c;不仅依赖于产品的质量与性价比&#xff0c;更在于如何精准把握并巧妙运用消费者心理。今天&#xff0c;我们就来探讨4个关键策略&#xff0c;结合选品建议&#xff0c;帮助电商人…

欠债还钱 天经地义李秘书专业写作:这是一篇涉借款纠纷的民事起诉状

欠债还钱 天经地义 李秘书专业写作&#xff1a;这是一篇涉借款纠纷的民事起诉状 &#xff08;精品范文&#xff09; 民 事 诉 状 原告&#xff1a;李某军&#xff0c;男&#xff0c;现年46岁&#xff0c;无业&#xff0c;现住黑龙江省大兴安岭地区漠河市汉东路阳和门。 被告…

儿童可以用挖耳勺吗?六大挑选妙招需掌握!

耳垢会随着人体的运动量增加&#xff0c;1岁以下的儿童运动量较小&#xff0c;可以不用经常掏耳朵&#xff0c;但随着年龄增长&#xff0c;耳垢也会增多&#xff0c;这时可以适当地给儿童掏耳勺。但掏耳朵的工具要选对&#xff0c;目前市面上不少宣称是儿童专用的掏耳工具&…

汇凯金业:清洗黄金首饰的方法

黄金首饰&#xff0c;作为我们日常生活中常见的饰品&#xff0c;不仅能够提升我们的穿着品味&#xff0c;更彰显了我们的经济实力。然而&#xff0c;黄金首饰戴久了&#xff0c;难免会出现一些污渍&#xff0c;甚至失去原有的光泽。这时候&#xff0c;很多人会选择去专业的珠宝…

【MySQL】SQL语句执行流程

目录 一、连接器 二、 查缓存 三、分析器 四、优化器 五、执行器 一、连接器 学习 MySQL 的过程中&#xff0c;除了安装&#xff0c;我们要做的第一步就是连接上 MySQL 在一开始我们都是先使用命令行连接 MySQL mysql -h localhost -u root -p 你的密码 使用这个命令…

Qt (9)【Qt窗口 —— 如何在窗口中创建菜单栏和工具栏】

阅读导航 引言一、Qt窗口简介二、如何在窗口中创建菜单栏1. 创建菜单栏2. 在菜单栏中添加菜单3. 创建菜单项4. 在菜单项之间添加分割线 三、如何在窗口中创建工具栏1. 创建工具栏2. 设置停靠位置3. 设置浮动属性4. 设置移动属性 引言 在上一篇文章中&#xff0c;我们深入探讨了…

掌握 BM25:深入了解算法及其在 Milvus 中的应用

我们可以通过 Milvus 轻松实现 BM25 算法&#xff0c;将文档和查询转化为稀疏向量。然后&#xff0c;这些稀疏向量可用于向量搜索&#xff0c;根据特定查询找到最相关的文档。 信息检索算法在搜索引擎中非常重要&#xff0c;可确保搜索结果与用户的查询相关。 想象一下&#…

Vue2中watch与Vue3中watch对比和踩坑

上一节说到了 computed计算属性对比 &#xff0c;虽然计算属性在大多数情况下更合适&#xff0c;但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法&#xff0c;来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时&#…

conda环境下在pycharm中调试scrapy项目

前提条件 已经创建好了conda环境已经安装好了scrapy框架项目初始化完成 编写一个爬虫脚本 import scrapyclass StackOverflowSpider(scrapy.Spider):name stackoverflowstart_urls [http://stackoverflow.com/questions?sortvotes]def parse(self, response):print("…

阿一网络安全实战演练之利用 REST URL 中的服务器端参数污染

所需知识 要解决这个实验室问题&#xff0c;您需要了解以下内容&#xff1a; 如何确定用户输入是否包含在服务器端的 URL 路径或查询字符串中。如何使用路径遍历序列尝试更改服务器端请求。如何查找 API 文档。 这些内容在我们的 API 测试学院主题中有涵盖。 进入实验室 研…

终极解决CondaValueError: Malformed version string ‘~’: invalid character(s)问题

conda 创建环境时出现&#xff1a; Solving environment: failed CondaValueError: Malformed version string ‘~’: invalid character(s)以下两种方法都不行时&#xff1a; 原因一&#xff1a; 添加的镜像源中&#xff0c;清华镜像源是https&#xff08;错误&#xff09;&a…

软件测试 - 测试分类(静态测试、动态测试、白盒测试、黑盒测试、灰盒测试、单元测试、集成测试、系统测试、验收测试等)

一、为什么要对软件测试进⾏分类&#xff1f; 软件测试是软件⽣命周期中的⼀个重要环节&#xff0c;具有较⾼的复杂性&#xff0c;对于软件测试&#xff0c;可以从不同的⻆度 加以分类&#xff0c;使开发者在软件开发过程中的不同层次、不同阶段对测试⼯作进⾏更好的执⾏和管理…

R语言管道操作详解-高效编程

引言 R语言是一种广泛应用于统计分析和图形表示的编程语言和软件环境。随着数据分析和数据科学的发展&#xff0c;R语言的管道操作符已经成为提高代码可读性和效率的重要工具。本文将详细介绍R语言中的管道操作符&#xff0c;包括它们的用途、语法和一些实用的示例。 目录 引…