C++ set map

news2025/4/3 2:47:55

1.set和map是什么

set和map是 C++ STL 提供的容器,用于高效的查找数据,底层采用红黑树实现,其中set是Key模型,map是Key-Value模型

set和map的基本使用较为简单,这里不再叙述,直接进入实现环节

2.set和map的底层实现

2.1 节点的定义

既然set和map的底层采用红黑树,那就少不了我们之前实现过的红黑树,直接搬过来复用

但我们实现的红黑树是Key-Value模型,难道要针对Key模型再copy一份红黑树,将节点里面的值修改成Key吗?

STL 中采用模板的方式,只需要一份红黑树,就能完成Key和Key-Value模型的统一

既然如何,红黑树节点的定义中,数据就不能只是key或pair对象,而应当是泛型,外部传递什么类型,数据就是什么类型

template<typename T>
struct RBTreeNode
{
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}

	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _col;
};

// set.h
template<typename K>
class set
{
private:
    RBTree<K, K> _tree;
};

// map.h
template<typename K, typename V>
class map
{
private:
    RBTree<K, std::pair<K, V>> _tree;
};

2.2 set和map的设计

同时,插入数据时,插入的值也应当为修改,此时,数据的比较就有问题了,之前的比较都是针对单一的key或pair进行比较,但红黑树这层并不知道外部传递给我的是一个什么类型的数据,只有set/map层知道传递的数据类型是什么,该如何进行数据的比较?

这里的解决方法是使用仿函数,在set/map层传递一个仿函数给红黑树层,该仿函数的功能用于取出key值,如果是set,那就直接取出key值,如果是map,那就取出pair中的first

// set.h
template<typename K>
class set
{
private:
    struct SetKeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
    
private:
    RBTree<K, K, SetKeyOfT> _tree;
};

// map.h
template<typename K, typename V>
class map
{
private:
    struct MapKeyOfT
    {
        const K& operator()(const std::pair<K, V>& kv)
        {
            return kv.first;
        }
    };
    
private:
    RBTree<K, std::pair<K, V>, MapKeyOfT> _tree;
};

// RBTree.h
template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:
	bool insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		KeyOfT kot;
		Node* cur = _root, * parent = nullptr;
		while (cur)
		{
			if (kot(data) < kot(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(data) > kot(cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else return false;
		}

		Node* newnode = new Node(data);
		if (kot(data) < kot(parent->_data)) parent->_left = newnode;
		else parent->_right = newnode;
		newnode->_parent = parent;
        // .......
		return true;
	}
    
private:
	Node* _root = nullptr;
};

至此,set/map的整体框架建立完毕

set/map中,红黑树的定义时,还多传递了一个K类型,这样set有两个K类型,会不会很多余?

虽然插入数据不同的类型要采用不同的比较方式,但双方的查找操作都是根据key进行查找的,因此,多添加的K类型是为了方便map进行find操作的

2.3 迭代器

接下来,便是迭代器的设计了

在vector和list中,迭代器都是对指针的封装,再通过重载*和->运算符,达到屏蔽底层,上层统一遍历的效果

这里我们的设计思想也是一样的

template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBreeIterator<T, Ref, Ptr> Self;

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

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

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

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

	Node* _node;
};

唯一不同的是对于红黑树,++操作该如何实现?

红黑树的遍历要采用中序遍历才有意义,左子树 根 右子树

遍历到到某一节点时,如果左子树不为空,那么要一直遍历,直到左子树为空

左子树遍历完了,接下来访问根

根访问完了,如果右子树不为空,下一个应该访问右子树的最左节点

如果右子树为空,表明当前子树已经访问完了,此时,如果父亲是祖父的右子树,表明父亲的那颗子树也访问完了,要一直向上返回,直到当前节点是其父亲的左子树

在这里插入图片描述

在这里插入图片描述

template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{
    typedef RBTreeNode<T> Node;
	typedef __RBreeIterator<T, Ref, Ptr> Self;
    // ...
	Self& operator++()
	{
		if (_node->_right != nullptr)
		{
			_node = _node->_right;
			while (_node->_left) _node = _node->_left;
		}
		else
		{
			Node* parent = _node->_parent;
			while (parent && _node == parent->_right)
			{
				_node = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;
	}

	Node* _node;
};

set和map在 STL 中是双向迭代器,意味着能迭代器不仅支持++操作,也支持–操作,而–操作无非反过来遍历

在这里插入图片描述

在这里插入图片描述

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

规定迭代器的begin为红黑树的最左节点,end为空

template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef __RBreeIterator<T, T&, T*> iterator;

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

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

而set/map,无论是插入操作,还是迭代器,无非是对红黑树的接口的封装罢了

// set.h
template<typename K>
class set
{
private:
    struct SetKeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };

public:
    typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;

    bool insert(const K& key)
    {
        return _tree.insert(key);
    }

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

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

private:
    RBTree<K, const K, SetKeyOfT> _tree;
};

// map.h
template<typename K, typename V>
class map
{
private:
    struct MapKeyOfT
    {
        const K& operator()(const std::pair<K, V>& kv)
        {
            return kv.first;
        }
    };

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

    bool insert(const std::pair<K, V>& kv)
    {
        return _tree.insert(kv);
    }

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

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

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

2.4 map中operator[]

STL 中map支持使用[]运算符进行value的修改,使用起来相当方便

实际上,其内部设计利用了insert操作,将insert的返回值修改为std::pair<iterator, bool>

使用[]时,会传递一个key值,希望拿到key对应value的引用,这样就能方便修改value

因此,可以在[]内部调用insert,如果key不存在则插入成功,返回新节点的迭代器 + true的pair对象,否则返回值中的pair的second为false,表示插入失败

template<typename K, typename V>
class map
{
    // ...
public:
    typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;

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

2.5 拷贝构造 + 赋值重载

set和map都只有RBTree一个成员,直接调用红黑树的拷贝构造和赋值重载即可

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

	~RBTree()
	{
		this->destroy(_root);
		_root = nullptr;
	}

	RBTree() = default;

	RBTree(const RBTree<K, T, KeyOfT>& t)
	{
		_root = this->create(t._root);
		_root->_parent = nullptr;
	}

	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		std::swap(_root, t._root);
		return *this;
	}
    
    // ...
    
private:
	void destroy(Node* root)
	{
		if (root == nullptr) return;
		destroy(root->_left);
		destroy(root->_right);
		delete root;
		root = nullptr;
	}

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

		Node* newnode = new Node(root->_data);
		newnode->_col = root->_col;
		newnode->_left = create(root->_left);
		if (newnode->_left) newnode->_left->_parent = newnode;
		newnode->_right = create(root->_right);
		if (newnode->_right) newnode->_right->_parent = newnode;
		return newnode;
	}

private:
	Node* _root = nullptr;
}

剩下的还有const迭代器和find的实现了,由于较为简单,不再详讲

**注意:**还有个细节,我们在传递红黑树的模板参数时,将第二个模板参数中的K加上了const

如果是set,那就是const K,如果是map,那就是pair<const K, V>,这是为了防止使用迭代器修改节点中的值,为什么不直接使用const迭代器呢?如果使用const迭代器,那么map中的Key和Value都不能修改,而map是要能支持修改Value的

2.6 测试

void Test_set()
{
    set<int> s;
    std::vector<int> v = { 1, 3, 2, 8, 11, 9, 4, 5, 7, 10, 6 };
    for (auto& e : v) s.insert(e);

    std::cout << "正向遍历: ";
    for (auto& e : s) std::cout << e << " ";
    std::cout << std::endl;

    std::cout << "反向遍历: ";
    set<int>::iterator it = s.find(11);
    while (it != s.end())
    {
        std::cout << *it << " ";
        --it;
    }
    std::cout << std::endl;

    std::cout << "拷贝构造: s2->";
    set<int> s2(s);
    for (auto& e : s2) std::cout << e << " ";
    std::cout << std::endl;

    std::cout << "赋值重载: ";
    set<int> s3(s);
    s3.insert(1);
    s3 = s;
    std::cout << "s3->";
    for (auto& e : s3) std::cout << e << " ";
    std::cout << "    s->";
    for (auto& e : s) std::cout << e << " ";
}

在这里插入图片描述

void Test_map()
{
    std::vector<std::string> v = { "apple", "tassel", "watermelon", "apple", "banana", "tassel", "orange", "apple" };
    map<std::string, int> m;
    for (auto& e : v) m[e] ++;
    for (auto& it : m)
    {
        std::cout << it.first << " " << it.second << std::endl;
    }
}

在这里插入图片描述

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

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

相关文章

Spring AI Alibaba 对话记忆使用

一、对话记忆 (ChatMemory)简介 1、对话记忆介绍 ”大模型的对话记忆”这一概念&#xff0c;根植于人工智能与自然语言处理领域&#xff0c;特别是针对具有深度学习能力的大型语言模型而言&#xff0c;它指的是模型在与用户进行交互式对话过程中&#xff0c;能够追踪、理解并利…

Ubuntu24.04 离线安装 MySQL8.0.41

一、环境准备 1.1 官方下载MySQL8.0.41 完整包 1.2 上传包 & 解压 上传包名称是&#xff1a;mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundle.tar # 切换到上传目录 cd /home/MySQL8 # 解压&#xff1a; tar -xvf mysql-server_8.0.41-1ubuntu24.04_amd64.deb-bundl…

SOME/IP-SD -- 协议英文原文讲解10

前言 SOME/IP协议越来越多的用于汽车电子行业中&#xff0c;关于协议详细完全的中文资料却没有&#xff0c;所以我将结合工作经验并对照英文原版协议做一系列的文章。基本分三大块&#xff1a; 1. SOME/IP协议讲解 2. SOME/IP-SD协议讲解 3. python/C举例调试讲解 5.1.5 Non…

Ubuntu上给AndroidStudio创建桌面图标

最近使用了Ubuntu开发了&#xff0c;默认的android studio没有桌面图标&#xff0c;还是很不方便&#xff0c;每次都要cd到bin目录启动studio.sh。 步骤1&#xff1a;cd /usr/share/applications linux系统里面&#xff0c;所有的应用启动入口都在 /usr/share/applications …

简单视图函数

视图函数 文章目录 视图函数[toc]一、什么是视图函数二、简单视图函数三、返回错误视图 一、什么是视图函数 所谓视图函数&#xff08;简称视图&#xff09;&#xff0c;本质上就是一个Python函数&#xff0c;用于接收Web请求并且返回Web响应。Web响应可以包含很多类型&#x…

蓝桥杯备考----》完全背包模板

其实这个完全背包的步骤和01背包也是差不多滴&#xff0c;不过他有一些优化是我们必须要说一说的 老样子&#xff0c;我们先定义一下状态表示 step1: f[i][j]表示从1到i个物品里选出体积不超过j的最大价值 step2:状态转移方程 写成一行就是 我们再写一下f[i][j-v[i]]的表达…

小白入门机器学习概述

文章目录 一、引言二、机器学习的基础概念1. 机器学习的定义2. 机器学习的类型&#xff08;1&#xff09;监督学习&#xff08;Supervised Learning&#xff09;&#xff08;2&#xff09;无监督学习&#xff08;Unsupervised Learning&#xff09;&#xff08;3&#xff09;半…

微软 GraphRAG 项目学习总结

微软2024年4月份发布了一篇《From Local to Global: A GraphRAG Approach to Query-Focused Summarization》&#xff08;GraphRAG&#xff1a;从局部到全局的查询式摘要方法&#xff09;论文&#xff0c;提出了一种名为GraphRAG的检索增强生成&#xff08;RAG&#xff09;方法…

C# dataGridView 自动生成几行几列及手动输入整型字符

C# dataGridView生成12号4列的表格 private void Form1_Load(object sender, EventArgs e) {// 清除默认列dataGridView1.Columns.Clear();// 添加4列&#xff08;首列为序号列&#xff09;dataGridView1.Columns.Add("ColIndex", "序号");dataGridView1.…

Day17 -实例:利用不同语言不同框架的特征 进行识别

前置&#xff1a;我们所需的web站点&#xff0c;都可以利用fofa去搜索&#xff0c;例如&#xff1a;app"flask"这样的语句去找对应的站点&#xff0c;找到后&#xff0c;我们模拟不知道是什么框架&#xff0c;再根据特征去判断它的框架。 ***利用工具可以再去结合大…

Pycharm(八):字符串切片

一、字符串分片介绍 对操作的对象截取其中一部分的操作&#xff0c;比如想要获取字符串“888666qq.com前面的qq号的时候就可以用切片。 字符串、列表、元组都支持切片操作。 语法&#xff1a;字符串变量名 [起始:结束:步长] 口诀&#xff1a;切片其实很简单&#xff0c;只顾头来…

Mysql从入门到精通day5————子查询精讲

本文主要讲述子查询的几种方法&#xff0c;读者注意体会它们的不同场合的适用情况及功能&#xff0c;本篇文章也融入了小编实践过程遇到的坑&#xff0c;希望读者不要再踩坑 一.带IN关键字的子查询 in关键字可以检测结果集中是否存在某个特定的值&#xff0c;检测成功则执行外…

虫洞数观系列二 | Python+MySQL高效封装:为pandas数据分析铺路

目录 系列文章 1. 引言 2. 常规写法mysql 3. 封装设计接口mysql 3.1dbname.py文件 3.1.1. 导入和基类定义 3.1.2. 具体表定义类 3.1.3. 表定义整合函数 3.1.4. 全局字典和测试代码 3.2mysql_dao文件 3.2.1. 模块导入与配置 3.2.2. 数据库连接池初始化 3.2.3. Comm…

MySQl之Binlog

前言 Binlog&#xff08;Binary Log&#xff09;是MySQL中至关重要的日志模块&#xff0c;它直接关系到数据恢复、主从复制等高阶架构设计。无论你是刚入门的新手还是有一定经验的开发者&#xff0c;掌握Binlog的原理和应用都是进阶的必经之路。 BinLog是什么&#xff1f; Bin…

开源项目解读(https://github.com/zjunlp/DeepKE)

1.DeepKE 是一个开源的知识图谱抽取与构建工具&#xff0c;支持cnSchema、低资源、长篇章、多模态的知识抽取工具&#xff0c;可以基于PyTorch实现命名实体识别、关系抽取和属性抽取功能。同时为初学者提供了文档&#xff0c;在线演示, 论文, 演示文稿和海报。 2.下载对应的de…

「MethodArgumentTypeMismatchException:前端传递 ‘undefined‘ 导致 Integer 类型转换失败」

遇到的问题&#xff1a; Failed to convert value of type java.lang.String to required type java.lang.Integer; nested exception is java.lang.NumberFormatException: For input string: "undefined" 原因分析&#xff1a; 大致意思就是我传递的参数到后端没…

LabVIEW故障诊断数据处理方法

在LabVIEW故障诊断系统中&#xff0c;数据处理直接决定诊断的准确性和效率。工业现场常面临噪声干扰、数据量大、实时性要求高等挑战&#xff0c;需针对性地选择处理方法。本文结合电机故障诊断、轴承损伤检测等典型案例&#xff0c;详解数据预处理、特征提取、模式识别三大核心…

基于 SpringBoot 的火车订票管理系统

收藏关注不迷路&#xff01;&#xff01; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff08;免费咨询指导选题&#xff09;&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;希望帮助更多…

Python的概论

免责声明 如有异议请在评论区友好交流&#xff0c;或者私信 内容纯属个人见解&#xff0c;仅供学习参考 如若从事非法行业请勿食用 如有雷同纯属巧合 版权问题请直接联系本人进行删改 前言 提示&#xff1a;&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0c…

构建大语言模型应用:句子转换器(Sentence Transformers)(第三部分)

本系列文章目录 简介数据准备句子转换器&#xff08;本文&#xff09;向量数据库搜索与检索大语言模型开源检索增强生成评估大语言模型服务高级检索增强生成 RAG 在之前的博客中&#xff0c;我们学习了为RAG&#xff08;检索增强生成&#xff0c;Retrieval Augmented Generati…