模拟实现map和set笔记

🌲一棵红黑树封装出map和set

默认你已经模拟实现了自己的红黑树
红黑树的模拟实现👉👉 传送门_

🌴问题

用同一棵红黑树模拟实现map和set的实现过程主要有以下几个问题

  1. map的元素是key-value键值对,而set的元素仅仅是key,怎么去用同一棵树封装?
  2. 怎么实现这棵树的迭代器?
  3. 底层的红黑树也是二叉搜索树,构建二叉搜索树的过程中需要比较,而两个容器的元素类型不同,怎么拿出key-value的key进行比较?

自己碰到的一些小细节

🌴解决

🌵问题一

map的元素是key-value键值对,而set的元素仅仅是key,怎么去用同一棵树封装?

解决:加一个模板参数(下图中RBTree的第二个参数T)

使用:set实例化红黑树时传入Key,map实例化红黑树时传入KV键值对即可

image-20220509084521636

🌵问题二

怎么实现这棵树的迭代器?

下面Node表示树的结点

迭代器要去遍历所有元素,还能访问元素的具体内容,类型肯定是Node*(指向树的结点的指针)。

Node的存储不是连续的,不能直接++,也不能直接解引用拿到里面的数据,所以需要重载,所以这里实现迭代器得把Node*封装成一个对象。

此外,vector的迭代器++就是下标+1找到下一个元素,list通过next指针找到下一个结点,那红黑树的迭代器++要找到下一个结点,那怎么实现

  • 下一个结点指的是什么?

以整数321456789去构建红黑树,1的下一个就是2,2的下一个就是3。

  • 如何实现?

转化一下问题,在一棵二叉搜索树中怎么找到中序遍历的下一个结点?

当前结点记为cur

在父亲的左边
在父亲的右边
cur=cur->parent
看右子树
右子树不为空
右子树为空
访问右子树最左边的结点
看父亲
访问父亲
往上递归

找上一个结点则相反

🍃代码实现
  • 为什么传三个模板参数?

区分const迭代器和普通迭代器

传模板参数时普通迭代器传T&,const迭代器传const T&,
同理普通迭代器Ptr传T*,const迭代器传const T*,
如果不这么做,这里实现一个普通版本的迭代器,还得再写一个类单
独实现const迭代器,重复的代码会比较多

image-20220509100143531

template<class T,class Ref,class Ptr>
//和链表一样的用法,目的是为了区分const迭代器和普通迭代器
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> self;
	Node* _node;//结点指针
	RBTreeIterator(Node* node=nullptr)//全缺省
		:_node(node)
	{}

	//const T& operator*()
	// T& operator*() 通过模板的第二个参数简化
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &(_node->_data);
	}
	self& operator++()//前置++
	{
		Node* cur = _node;
		if (cur->_right)
		{
			//右子树不为空,不能直接把右子树的根给_node
			//因为右子树最左边的叶子才是下一个应该访问的结点
			Node* right = cur->_right;
			while (right&&right->_left)
			{
				right = right->_left;
			}
			_node = right;
			return *this;
		}
		else//右子树为空
			//看父亲
		{
			Node* parent = cur->_parent;
			while (parent&&parent->_right == cur)
			{
				cur = parent;
				parent = cur->_parent;
			}
			if (parent == nullptr)
			{
				_node = nullptr;
			}
			else
			{
				_node = parent;
			}
			
			return *this;
		}
	}
	self& operator--()
	{
		//前置--
		/*	左子树为空往上面去找
			看父亲cur是父亲的右边直接访问父亲
			cur是父亲的左边代表这颗子树访
			问完毕,往上递归
			左子树不为空访问左子树最右边的结点
		*/
		Node* cur = _node;
		if (cur->_left)//左子树不为空
		{
			Node* left = cur->_left;//左子树 取名改成subRight好一点
			while (left&&left->_right)
			{
				left = left->_right;
			}
			_node = left;
		}
		else//左子树为空
		{
			Node* parent = cur->_parent;
			while (parent&&parent->_left==cur)
			{
				cur = parent;
				parent = cur->_parent;
			}

			_node = parent;
		}
		return *this;
	}
	self operator++(int)
	{
		Node* tmp = _node;
		++(*this);
		return self(tmp);
	}
	self operator--(int)
	{
		Node* tmp = _node;
		--(*this);
		return self(tmp);
	}
	bool operator==(const self& s)const 
	{
		return _node == s._node;//注意这里的s是个对象 不是对象指针
	}
	bool operator!=(const self& s) const
	{
		return _node != s._node;
	}
};

🌵问题三

set比较时拿到key,map比较时从pair里拿到key?

利用模板+仿函数,即下面的第三个参数

template<class K,class T,class KeyOfT>
//传参
RBTree<K,pair<const K,V>,MapKeyOfT> _rbt;
RBTree<K, K, setKeyOfT> _rbt;

image-20220509100555525

仿函数的使用

image-20220509100657894

随笔记录:为什么需要第三个参数?

第三个参数是为了解决第二个参数带来的问题,第二个参数能让红黑树去适配key和key-value,但是因此红黑树也不知道传进来的是什么类型,也就无法进行比较,就得通过仿函数,仿函数是写在map和set里的,map和set是知道确定的类型的,再通过仿函数返回一个要比较的值即可

为什么需要第一个模板参数K?

find函数需要

🌵细节

typedef  typename  RBTree<K, K, setKeyOfT>::iterator iterator;

必须加typename告诉编译器后面这一堆是个类型,不然编译器不知道后面的这个是静态成员还是类型

不加typename报错

image-20220509102517489

C++typename的由来和用法

  • delete会将对象的这块空间完全释放,但是对象里面开的空间则需要析构函数

    具体表现为delete后原属于对象的空间全部变成随机值,就算析构函数里把成员变量置空也仍然会是随机值,因为析构函数是针对对象内部开的空间不针对对象自身的这块空间,对象自身空间的释放由delete完成了

  • const pair<K,V>& kv;修饰的引用,意义为不能通过引用来修改kv的值

  • 有些参数可以传常量时千万别用变量接收!

  • map里的pair<const K,T> 注意K具有const属性

    image-20220509103617973

  • map和set插入的返回值是pair<iterator, bool>,通过这个返回值既可以找到插入的元素,还可以知道插入是否成功

  • map重载的[]很实用,具体实现是先插入,根据插入的返回值拿到value

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

🌴总结

  • 红黑树的封装从另一个角度看就是模板的应用

  • 迭代器也没那么神奇,本质上也就是一个类

map和set代码汇总github

🌴闲谈

至此模拟实现STL就告一段落了

模拟实现STL代码汇总:CCLCK/CPP_STL: STL库笔记 (github.com)

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜欢乙醇的四氯化碳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值