C C++最新C++STL之map、set的使用和模拟实现(2),2024年最新2024最新C C++开发面试解答

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2.3 multimap的概念和使用

与multiset一样,都是可以出现重复的值,只是其中没有了map的operator[ ],就不过诉了。

3.map、set的底层实现

3.1红黑树的修改

为了给set、map做底层,我们需要完善一下红黑树在里面主要是

  1. 模板的改变:将原本第二个参数V改成T,T代表的是K,V组成成的键值对pair<K,V>
  2. 添加迭代器以及begin、end函数,让map、set也能用迭代器
  3. 修改插入的返回值:将原本的iterator改成pair<iterator,bool>,(这是STL源码内的设计,也是为了map的[]做准备)

修改后的源码:

#pragma once
#include<iostream>
using namespace std;
enum Color
{
	BLACK,
	RED
};

template<class T>
struct RBTreeNode {

	RBTreeNode<T>\* _left = nullptr;
	RBTreeNode<T>\* _right = nullptr;
	RBTreeNode<T>\* _parent = nullptr;

	T _data;

	Color _col = RED;//默认生成的节点颜色是红色

	RBTreeNode(const T& data)
		:\_data(data)
	{}
};

//迭代器
template<class T, class Ptr, class Ref>
struct \_TreeIterator
{
	typedef _TreeIterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;
	Node\* _node;//迭代器的成员变量

	\_TreeIterator(Node\* node)
		:\_node(node)
	{}

	Self& operator++()
	{
		Node\* cur = _node;
		if (cur->_right)//若右边不为空,则找到其左边的右边节点
		{
			cur = cur->_right;
			while (cur->_left)
			{
				cur = cur->_left;
			}
			_node = cur;
		}
		else
		{
			Node\* parent = cur->_parent;
			while (parent && parent->_left != cur)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return \*this;
	}

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

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

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

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

};

template<class K, class T, class Compare>
class RBTree
{
	typedef RBTreeNode<T> Node;
	Compare kot;
public:
	
	typedef _TreeIterator<T,T\*,T&> iterator;
	typedef _TreeIterator<T,const T\*,const T&> const_iterator;

	//typedef typename RBTree<K, pair<K, V>, Mapofkey>::iterator iterator;

	// 在红黑树中插入值为data的节点,插入成功返回true,否则返回false
	// 注意:为了简单起见,本次实现红黑树不存储重复性元素
	iterator begin()
	{
		Node\* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);//end指向最后数据的后面故为空
	}

	const_iterator begin() const
	{
		Node\* cur = _root;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const\_iterator(cur);
	}
	const_iterator end() const
	{
		return const\_iterator(nullptr);//end指向最后数据的后面故为空
	}
	//此处用Node\* 的原因set处的iterator为了防止被修改所以set内的iterator本质还是const\_iterator,
	//所以这里用了 Node\* 来代替iterator 的返回

	pair<Node\*, bool> Insert(const T& data)
	{
		//此处和AVL平衡二叉树的性质一样找到所要插入节点的位置 大的在右 、 小的在左
		Node\* parent = nullptr;
		Node\* cur = _root;
		
		if (cur == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make\_pair(_root, true);
		}

		//找到插入的位置!
		while (cur)//当为null时表示此处就是要插入的位置!
		{
			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(nullptr, false);
			}
		}
		//找到位置后,插入
		cur = new Node(data);//建立新节点
		Node\* ret = cur;
		//建立链接
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//插入时要判断插入后是否会导致不平衡!对于红黑树来说主要问题有
			//1. 不能出现连续的红节点
			//2. 最长路径不超过最短路径的两倍
		//判断是否需要变色/旋转
		// 
		//1.当父亲节点为黑色时,当新增了一个红色节点时就结束插入了
		// 
		//2.当父为红时:
		// 情况一(仅变色即可):当parent为红 grandfather为黑 uncle存在且为黑 插入一个新节点

		while (parent && parent->_col == RED)
		{
			Node\* g = parent->_parent;//grandfather
			if (g->_left == parent)
			{
				Node\* u = g->_right;//uncle

				if (u && u->_col == RED)//u存在且为红
				{
					//变色即可
					u->_col = parent->_col = BLACK;
					g->_col = RED;

					//向上调整
					cur = g;
					parent = g->_parent;
					//当g 的 父亲为黑时或者为null时停止调整
				}
				else //u不存在或者为黑
				{
					if (cur == parent->_left)//此处u不存在和当插入节点在左边时的情况一样直接右旋加变色即可
					{
						//旋转加变色
						RotateR(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else
					{
						//旋转加变色
						RotateL(parent);
						RotateR(g);

						cur->_col = BLACK;
						g->_col = RED;
					}
				}
			}
			else
			{
				Node\* u = g->_left;//uncle
				if (u && u->_col == RED)//u存在且为红
				{
					//变色即可
					u->_col = parent->_col = BLACK;
					g->_col = RED;

					//向上调整
					cur = g;
					parent = g->_parent;

					//当g 的 父亲为黑时或者为null时停止调整
				}
				else //u不存在或者为黑
				{
					if (cur == parent->_right)//此处u不存在和当插入节点在左边时的情况一样直接右旋加变色即可
					{
						RotateL(g);
						parent->_col = BLACK;
						g->_col = RED;
					}
					else
					{
						RotateR(parent);
						RotateL(g);

						cur->_col = BLACK;
						g->_col = RED;
					}
				}
			}
		}
		_root->_col = BLACK;
		return make\_pair(ret, true);
	}


	

	void Inorder()
	{
		\_Inorder(_root);
		cout << endl;
	}
	// 获取红黑树最左侧节点
	Node\* LeftMost()
	{
		Node\* cur = _root;
		while (cur)
		{
			if (cur->_left == nullptr)
			{
				return cur;
			}
			cur = cur->_left;
		}
		return nullptr;
	}
	// 获取红黑树最右侧节点
	Node\* RightMost()
	{
		Node\* cur = _root;
		while (cur)
		{
			if (cur->_right == nullptr)
			{
				return cur;
			}
			cur = cur->_right;
		}
		return nullptr;
	}
	// 检测红黑树是否为有效的红黑树,注意:其内部主要依靠\_IsValidRBTRee函数检测
	// 1.每条路径中的黑色节点个数是否一样
	// 2.最长路径不超过最短路径的两倍
	// 3.不能出现连续的红色节点
	// 4.根节点为黑色
	bool IsValidRBTRee()
	{
		if (_root == nullptr) return true;

		if (_root->_col == RED) return false;

		Node\* cur = _root;
		int blackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				blackCount++;
			}
			cur = cur->_left;
		}
		return \_IsValidRBTRee(_root, blackCount, 0);
	}

	int Height()
	{
		if (_root == nullptr) return 0;
		return \_Height(_root);
	}

	int Size()
	{
		if (_root == nullptr) return 0;

		return \_Size(_root);
	}
	//检测红黑树中是否存在值为data的节点,存在返回该节点的地址,否则返回nullptr
	Node\* Find(const K& val)
	{
		Node\* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) == val)
			{
				return cur;
			}
			else if (kot(cur->_data) > val)
			{
				cur = cur->_left;
			}
			else {
				cur = cur->_right;
			}
		}
		return nullptr;
	}
private:
	int \_Size(Node\* root)
	{
		if (root == nullptr)return 0;

		return \_Size(root->_left) +
			\_Size(root->_right) + 1;
	}

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

		int lefthight = \_Height(root->_left);

		int righthight = \_Height(root->_right);

		return lefthight > righthight ? lefthight + 1 : righthight + 1;
	}

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

		\_Inorder(root->_left);
		cout << root->_data.first << " ";
		\_Inorder(root->_right);
	}

	bool \_IsValidRBTRee(Node\* root, size_t blackCount, size_t pathBlack)
	{
		if (root == nullptr)
		{
			if (blackCount != pathBlack)//当为null时表示该路径已经结束,那么判断改路径的黑色节点(pathblack) 和其他路径的黑色节点(blacCount)是否相同
			{
				return false;
			}
			return true;
		}

		if (root->_col == RED)
		{
			if (root->_left && root->_right && (root->_left->_col == RED || root->_right->_col == RED))
			{
				cout << "有连续的红色节点" << endl;
				return false;
			}
		}
		if (root->_col == BLACK)
		{
			pathBlack++;
		}

		return \_IsValidRBTRee(root->_left, blackCount, pathBlack) &&
			\_IsValidRBTRee(root->_right, blackCount, pathBlack);
	}

	// // 为了操作树简单起见:获取根节点
		//Node\*& GetRoot();
	void RotateR(Node\* parent)
	{
		Node\* SubL = parent->_left;//此处就为 cur
		Node\* SubLR = SubL->_right;

		//parent的左换成cur的右
		parent->_left = SubLR;
		//把cur的右孩子换成parent
		SubL->_right = parent;

		//注意还要修改其父指针
		Node\* Ppnode = parent->_parent;

		parent->_parent = SubL;
		if (SubLR)//cur的右边可能为空
			SubLR->_parent = parent;

		if (_root == parent)//如果parent为根节点,则需要把subR改变成根节点并且其父亲为nullptr
		{
			_root = SubL;
			SubL->_parent = nullptr;
		}
		else
		{
			//同时还要考虑父亲 是祖先的左或右
			if (Ppnode->_left == parent)
			{
				Ppnode->_left = SubL;
			}
			else
			{
				Ppnode->_right = SubL;
			}
			SubL->_parent = Ppnode;
		}
	}

	// 左单旋
	// 同理
	void RotateL(Node\* parent)
	{
		Node\* SubR = parent->_right;//此处就为 cur
		Node\* SubRL = SubR->_left;

		//parent的右换成cur的左
		parent->_right = SubRL;
		//把cur的左孩子换成parent
		SubR->_left = parent;

		Node\* Ppnode = parent->_parent;

		//注意 还要修改其父指针

		parent->_parent = SubR;
		if (SubRL)//右边可能为空
			SubRL->_parent = parent;

		if (_root == parent)//如果parent为根节点,则需要把subR改变成根节点并且其父亲为nullptr
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			//同时还要考虑父亲 是祖先的左或右
			if (Ppnode->_left == parent)
			{
				Ppnode->_left = SubR;
			}
			else
			{
				Ppnode->_right = SubR;
			}
			SubR->_parent = Ppnode;
		}
	}

private:
	Node\* _root = nullptr;
};

为了让set、map能实现迭代器所以还要写好迭代器常用的重载operator*、operator++、operator!=、…

下面是实现过程也就是再将以及写好的红黑树进行再一次的封装,实现set、map所需要的功能。

3.2set的实现

#pragma once
#include"RBTree.h"

template<class K>
class MySet
{
public:

	struct Setofkey
	{
	//自己所写的适配器用来从pair中调出key值
		const K& operator()(const K& key)
		{
			return key;
		}
	};

//通过红黑树的迭代器来重定义生成自己的
//typename的意思是声明他是类型名
	typedef typename RBTree<K, K, Setofkey>::const_iterator iterator;
	typedef typename RBTree<K, K, Setofkey>::const_iterator const_iterator;

	pair<iterator, bool> Insert(const K& key)
	{
		return _rb.Insert(key);
		// pair<Node\*, bool> 给 pair<iterator, bool>
		// pair的构造函数: template<class U, class V> pair (const pair<U,V>& pr);
		// 这样就能通过一个pair去构造另外一个pair
		// 所以返回来的pair<Node\*,bool> 就会对应给到要返回的pair<iterator,bool>的 iterator 和 bool 来进行构造
		// 这样就能iterator避免内外的不一样,外部的是iterator其实是const\_iterator
	}

	pair<iterator, bool> Insert(const K& key)const
	{
		return _rb.Insert(key);//pair<iterator, bool> 
	}

	//普通对象、const对象都能调用
	iterator begin() const
	{
		return _rb.begin();
	}

	iterator end() const
	{
		return _rb.end();
	}

private:
	RBTree<K,K,Setofkey> _rb;
};


3.3map的实现

#pragma once
#include"RBTree.h"

template<class K,class V>
class MyMap
{
public:
//自己所写的适配器用来从pair中调出key值
	struct Mapofkey
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};

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


	pair<iterator, bool> Insert(const pair<K,V>& kv)
	{
		return _rb.Insert(kv);
	}

	iterator begin()
	{
		return _rb.begin();
	}

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

	const_iterator begin() const
	{
		return _rb.begin();
	}

	const_iterator end() const
	{
		return _rb.end();
	}

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

private:
	RBTree<K, pair<const K,V>,Mapofkey> _rb;
};


img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

{
	return _rb.end();
}

const_iterator begin() const
{
	return _rb.begin();
}

const_iterator end() const
{
	return _rb.end();
}

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

private:
RBTree<K, pair<const K,V>,Mapofkey> _rb;
};




---


[外链图片转存中...(img-9Ji8Ev99-1715713212384)]
[外链图片转存中...(img-BURV05fc-1715713212384)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值