基于红黑树对map和set容器的封装

news/2024/7/10 23:06:01 标签: javascript, jquery, ecmascript, c++

在这里插入图片描述

本章代码gitee仓库:map和set模拟实现、stl_map_set_tree源码

文章目录

  • 🐱1. 红黑树的泛型
    • 🐈1.1 红黑树节点
    • 🐈1.2 红黑树迭代器
    • 🐈1.3 仿函数
  • 🐯2. 对set的封装
  • 🦄3. 对map的封装

🐱1. 红黑树的泛型

我们通过查看源码,发现mapset的底层都是红黑树,用的同一个类模板,通过控制传不同的模板参数,从而实例化出不同的类
image-20230913160728813

🐈1.1 红黑树节点

因为要对mapset进行封装,set是K模型,map是KV模型,所以采用模板来对数据类型进行控制

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)
	{}
};

🐈1.2 红黑树迭代器

STL库里面红黑树设置了一个头节点,这样在而我们采用的不是库里面方式,用的自己手搓的红黑树,所以在迭代器++--的时候,采用遍历树的方式
在这里插入图片描述

迭代器结构:

这里有三个模板参数,PtrRef可以通过传过来的是普通参数还是const参数来控制采用什么样的迭代器(普通迭代器或者const迭代器);同时也为mappair的两个参数通过了很好的访问方式Ref operator*()Ptr operator->()

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(const Iterator&it)
		:_node(it._node)
	{}

	__TreeIterator(Node* node)
		:_node(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 = cur->_parent;
			//孩子是父亲左的祖先节点
			while (parent && cur == parent->_right)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_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 && cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}
};

迭代器:

template<class K,class T,class KeyOfT>
struct RBTree
{
	typedef RBTreeNode<T> Node;

public:
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;	//const迭代器

	//迭代器
	iterator begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return leftMin;
	}

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

	const_iterator begin() const
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return leftMin;
	}

	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
    
    // ... ...
    // ... ...
}

🐈1.3 仿函数

当对元素进行插入或者查找的时候,都要进行比较,而mappair无法直接比较,所以我们设置了仿函数,用来提取key
这里以Find接口为例,具体可以看代码仓库的完整代码

Node* Find(const K& key)
{
	Node* cur = _root;
	//仿函数
	KeyOfT kot;
	while (cur)
	{
		//提出key值
		if (kot(cur->_data) > key)
		{
			cur = cur->_left;
		}
		else if (kot(cur->_data) < key)
		{
			cur = cur->_right;
		}
		else
			return cur;
	}
	return nullptr;
}

🐯2. 对set的封装

对于set的封装相对简单,在整个设计中,set由于只有一个K值,所以很多模板参数对于set而已作用并不大

image-20230914174644288

set的K值是不允许修改的,所以不管是普通迭代器还是const迭代器,都是采用的const迭代器

这里关于插入的操作,本质上也是为了兼容map,放到下面和map一起说

namespace My_map_set
{
	template<class K>
	class set
	{
        //仿函数
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		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;
	};
}

我们在封装的时候,采用了一个仿函数SetKeyOfT,这是因为在数据比较的时候,mappair需要取出里面的key值,为了保持统一,我们对set也使用了仿函数

🦄3. 对map的封装

  • mapkey值是不允许修改的,而value是允许修改的,使用有const修饰pair里面的key

    image-20230914185106723

  • map是支持[]的,而[]底层又是调用的insert,所以对于insert的返回值需要进行更改,而map有普通迭代器和const迭代器,不需要指定调用树里面的

    set的迭代器都是const迭代器,所以我们直接调用树里面的迭代器,库里面就是这么实现的:

    image-20230914193944187

    当这个类被实例化成const迭代器的时候,是一个构造函数,支持了普通迭代器构造const迭代器;

    当这个类被实例化为普通迭代器,那就是拷贝构造

    image-20230914195622767

namespace My_map_set
{
	template<class K,class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		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<K, V>& kv)
		{
			return _t.Insert(kv);
		}

	private:
		RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};
}

这里的封装相比之前的vectorlist这些容器模拟实现,会麻烦一点,本章也只是对于mapset的核心功能进行实现,具体的可以参考源码进行学习,

那本期的分享就到这里咯,我们下期再见,如果还有下期的话。


http://www.niftyadmin.cn/n/5025909.html

相关文章

微信公众号迁移步骤

公众号账号迁移的作用是什么&#xff1f;只能变更主体吗&#xff1f;长期以来&#xff0c;由于部分公众号在注册时&#xff0c;主体不准确的历史原因&#xff0c;或者公众号主体发生合并、分立或业务调整等现实状况&#xff0c;在公众号登记主体不能对应实际运营人的情况下&…

校园营销推广怎么做?VR全景助力打造数字可视化校园

现在的学校存在多种营销模式&#xff0c;因为学校是学生和教职工主要的消费场所&#xff0c;因此不少学校也会在校园营销方面投入大量的资源和精力&#xff0c;同时也希望提升自身学校知名度。在各大高校陆续迎新的过程中&#xff0c;校园的营销推广应该怎么做呢&#xff1f; 在…

11.Vue3.2弹窗组件的封装

以前一直想&#xff0c;每次封装一个弹窗组件的时候&#xff0c;一直特别复杂&#xff0c;父传子&#xff0c;子传父&#xff0c;各种来回绕&#xff0c;来回修改。 一直想如何才能更加简化&#xff0c;但是一直没时间&#xff0c;今天终于抽时间出来封装了一下 本次封装简化…

OpenCV(四十一):图像分割-分水岭法

1.分水岭方法介绍 OpenCV 提供了分水岭算法&#xff08;Watershed Algorithm&#xff09;的实现&#xff0c; 使用分水岭算法对图像进行分割&#xff0c;将图像的不同区域分割成互不干扰的区域。分水岭算法模拟了水在图像中的扩散和聚集过程&#xff0c;将标记的边界被看作是阻…

运算放大器学习笔记

目录 一、基本定理二、基本定义三、负反馈电路四、同向放大电路五、反向放大电路六、差分放大电路 一、基本定理 【电路示意图】 开环放大公式 VOAvo(V-V-) 开环放大倍数&#xff08;增益&#xff09;非常大&#xff0c;105 或 106 输入阻抗超级大&#xff08;可以理解为电…

esbuild中文文档-基础配置项(General options- Cancel)

文章目录 基础配置项 General options - 取消构建&#xff08;Cancel&#xff09;结语 哈喽&#xff0c;大家好&#xff01;我是「励志前端小黑哥」&#xff0c;我带着最新发布的文章又来了&#xff01; 老规矩&#xff0c;小手动起来~点赞关注不迷路&#xff01; 最近开始翻译…

CRC(循环冗余校验码的校验方法)

5个关键点&#xff1a; 1.信息码&#xff1a;即给出要校验的二进制码 2.生成多项式&#xff1a;一般多项式会给&#xff0c;从最高位的指数位数就可以得到有几个校验码&#xff1b;如果没给多项式&#xff0c;肯定会给个多项式二进制码&#xff0c;根据它来推就行&#xff08;…

OpenWrt kernel install分析(1)

一. 前言 本文主要介绍OpenWrt内核的编译和安装流程的流程。 二. 流程分析 1. 在include/toplevel.mk中加入如下语句 include/toplevel.mk: kernel_install: prepare_kernel_conf$(_SINGLE)$(NO_TRACE_MAKE) -C target/linux install 此时&#xff0c;在OpenWrt源码的根目录下…