【二十六】【C++】Map和Set

K模型与KV模型

在数据结构中,二叉搜索树(BST)的应用通常围绕着两种基本模型:键模型(K模型)和键值对模型(KV模型)。这两种模型定义了树中节点存储数据的方式,以及如何通过这些数据进行查找、插入和删除操作。

K模型(键模型)

在K模型中,每个节点存储一个单一的数据项,称为“键”(Key)。这个模型的核心特性是:

唯一性:树中每个键都是唯一的,不允许重复。

有序性:树中任意节点的左子树只包含键小于该节点的键的节点,其右子树只包含键大于该节点的键的节点。

操作:基本操作包括插入新键、查找键和删除键。所有操作都依赖于键之间的比较。

K模型的二叉搜索树适用于需要快速查找、插入和删除单一数据项的场景,如集合数据类型的实现。

KV模型(键值对模型)

KV模型扩展了K模型,使得每个节点存储两个数据项:一个是“键”(Key),另一个是与键相关联的“值”(Value)。这个模型的特性包括:

键的唯一性和有序性:与K模型相同,键在树中是唯一的,并且树的结构保证了键的有序性。

值的关联性:每个键都关联一个特定的值。值可以是任何类型的数据,如数字、字符串或更复杂的对象。

操作:除了基本的插入、查找和删除操作外,KV模型还支持根据键更新关联值的操作。

KV模型的二叉搜索树广泛应用于实现映射(Map)或字典(Dictionary)数据结构,其中需要根据唯一键快速查找、更新或删除关联的值。

改造二叉搜索树为KV结构

 
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;template<class K, class V>
struct BSTNode {BSTNode(const K&key = K(), const V& value = V()): _pLeft(nullptr), _pRight(nullptr), _key(key), _value(value) {}BSTNode<K, V>* _pLeft;BSTNode<K, V>* _pRight;K _key;V _value;};template<class K, class V>
class BSTree {typedef BSTNode<K, V> Node;typedef Node* PNode;public:BSTree(): _pRoot(nullptr) {}PNode Find(const K& key) {Node* cur = _pRoot;while (cur) {if (key == cur->_key)return cur;else if (key < cur->_key)cur = cur->_pLeft;elsecur = cur->_pRight;}return nullptr;};bool Insert(const K& key, const V&value) {// 1. 空树---// 新插入的节点应该是跟节点if (nullptr == _pRoot) {_pRoot = new Node(key, value);return true;}// 2. 树非空// a. 找待插入节点在树中的位置--->按照二叉搜索树的性质Node* cur = _pRoot;Node* parent = nullptr;while (cur) {parent = cur;if (key < cur->_key)cur = cur->_pLeft;else if (key > cur->_key)cur = cur->_pRight;elsereturn false;}// 树中不存在值为data的节点// b. 插入新节点cur = new Node(key, value);if (key < parent->_key)parent->_pLeft = cur;elseparent->_pRight = cur;return true;};bool Erase(const K& key) {return false;};void InOrder() {_InOrder(_pRoot);cout << endl;}private:void _InOrder(Node* proot) {if (proot) {_InOrder(proot->_pLeft);cout << proot->_key << " 个数:" << proot->_value << endl;_InOrder(proot->_pRight);}}PNode _pRoot;};void TestBSTree3() {BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");string str;while (cin >> str) {if(str[0] == EOF){return;}BSTNode<string, string>* ret = dict.Find(str);if (ret == nullptr) {cout << "单词拼写错误,词库中没有这个单词:" << str << endl;} else {cout << str << "中文翻译:" << ret->_value << endl;}}}void TestBSTree4() {// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉"};BSTree<string, int> countTree;for (const auto& str : arr) {
// 先查找水果在不在搜索树中// 1、不在,说明水果第一次出现,则插入<水果, 1>// 2、在,则查找到的节点中水果对应的次数++//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL) {countTree.Insert(str, 1);} else {ret->_value++;}}countTree.InOrder();}int main() {TestBSTree3();TestBSTree4();return 0;}

Map、MultMap的探究

Map是一种关联容器,它存储由键值和映射值组成的元素,遵循特定的顺序。

Map中,键值通常用于对元素进行排序和唯一标识,而映射值存储与该键相关联的内容。键和映射值的类型可能不同,并且被组合在一起作为成员类型value_type,它是一个pair类型,结合了键和映射值:

typedef pair<const Key, T> value_type;

Map中,元素总是按其键值进行排序,遵循特定的严格弱排序准则,由其内部比较对象(Compare类型)指示。

相比unordered_map容器,Map容器通常访问单个元素时速度较慢,但它们允许根据其顺序直接迭代子集。

Map中,可以使用方括号运算符(operator[])直接通过其相应的键访问映射值。

Map通常以二叉搜索树的形式实现。

 
#include <iostream>
using namespace std;#include <map>
#include <string>void TestMap1(){map<string, string> m1;map<string, string> m2{ {"apple","苹果"}, {"orange", "橙子"}, {"banana", "香蕉"} };auto m3 = m2;   // 调用拷贝构造map<string, string> m4(m2.begin(), m2.end());}void TestMap2(){map<string, string> m;// 通过insert直接往map中插入键值对pair<string, string> p("orange", "橙子");m.insert(p);m.insert(pair<string, string>("banana", "香蕉"));m.insert(make_pair("apple", "苹果"));cout << m.size() << endl;// m[key]--->表明:返回key对应的valuecout << m["orange"] << endl;// m[key] = newvalue;  使用newvalue给map中key对应的value进行赋值m["orange"] = "橘子";// 如果通过m[key]访问key对应的value时,如果key不存子?// m会使用当前key与一个默认的value构成一个键值对<key, 默认value>插入到m中// 然后将key对应的默认的value返回来cout<<m["peach"]<<endl;m["pair"] = "梨";// map中已经存在orange,在插入一个orange看能否成功m.insert(make_pair("orange", "橙子"));cout << m.size() << endl;// 对map中的元素进行遍历//std::map<std::string, std::string>::iterator it = m.begin();auto it = m.begin();while (it != m.end()){cout << "<" << it->first << "," << it->second << ">" << endl;++it;}cout << endl;// e 就是m中所存在的键值对的别名for (auto& e : m){cout << "<" << e.first << "," << e.second << ">" << endl;}cout << endl;m.erase("apple");it = m.find("orange");if (it != m.end()){m.erase(it);}for (auto& e : m){cout << "<" << e.first << "," << e.second << ">" << endl;}cout << endl;}void TestMultiMap(){multimap<string, string> m;m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.erase("apple");}int main(){TestMap1();TestMap2();TestMultiMap();}

Map的创建

 
void TestMap1(){map<string, string> m1;map<string, string> m2{ {"apple","苹果"}, {"orange", "橙子"}, {"banana", "香蕉"} };auto m3 = m2;   // 调用拷贝构造map<string, string> m4(m2.begin(), m2.end());}

map<string, string> m1;:创建了一个空的map容器m1

map<string, string> m2{ {"apple","苹果"}, {"orange", "橙子"}, {"banana", "香蕉"} };:创建了一个带有初始值的map容器m2,其中包含三个键值对,键为字符串类型,值为字符串类型。

auto m3 = m2;:创建了一个新的map容器m3,并将m2拷贝到m3中。这里使用了auto进行类型推导,实际上调用了拷贝构造函数。

map<string, string> m4(m2.begin(), m2.end());:创建了一个新的map容器m4,并通过迭代器范围初始化,从m2的开始迭代器到结束迭代器,复制了m2的所有元素。

Map的基本使用

 
void TestMap2(){map<string, string> m;// 通过insert直接往map中插入键值对pair<string, string> p("orange", "橙子");m.insert(p);m.insert(pair<string, string>("banana", "香蕉"));m.insert(make_pair("apple", "苹果"));cout << m.size() << endl;// m[key]--->表明:返回key对应的valuecout << m["orange"] << endl;// m[key] = newvalue;  使用newvalue给map中key对应的value进行赋值m["orange"] = "橘子";// 如果通过m[key]访问key对应的value时,如果key不存子?// m会使用当前key与一个默认的value构成一个键值对<key, 默认value>插入到m中// 然后将key对应的默认的value返回来cout<<m["peach"]<<endl;m["pair"] = "梨";// map中已经存在orange,在插入一个orange看能否成功m.insert(make_pair("orange", "橙子"));cout << m.size() << endl;// 对map中的元素进行遍历//std::map<std::string, std::string>::iterator it = m.begin();auto it = m.begin();while (it != m.end()){cout << "<" << it->first << "," << it->second << ">" << endl;++it;}cout << endl;// e 就是m中所存在的键值对的别名for (auto& e : m){cout << "<" << e.first << "," << e.second << ">" << endl;}cout << endl;m.erase("apple");it = m.find("orange");if (it != m.end()){m.erase(it);}for (auto& e : m){cout << "<" << e.first << "," << e.second << ">" << endl;}cout << endl;}

MultiMap

 
void TestMultiMap(){multimap<string, string> m;m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.insert(make_pair("apple", "苹果"));m.erase("apple");}

创建了一个空的multimap容器 m

使用 insert 函数向multimap容器中插入多个键值对,这些键值对的键都是 "apple",值都是 "苹果"。

使用 erase 函数删除multimap容器中键为 "apple" 的所有键值对。由于multimap容器允许存储相同的键,因此会删除所有键为 "apple" 的键值对,而不仅仅是第一个或最后一个。

Set、MulitSet的探究

集合(Sets)是一种容器,它存储具有唯一性的元素并遵循特定的顺序。

在一个集合中,元素的值也是唯一标识它的(值本身即是键,类型为 T),每个值必须是唯一的。集合中元素的值一旦在容器中,就不能被修改(元素始终是 const),但它们可以被插入或从容器中移除。

在内部,集合中的元素总是按照特定的严格弱排序准则进行排序,这个准则由其内部比较对象(Compare 类型)指示。

相比 unordered_set 容器,集合容器通常访问单个元素时速度较慢,但它们允许根据其顺序直接迭代子集。

集合通常被实现为二叉搜索树。

 
#include <set>
#include <iostream>
using namespace std;#include <string>
// set使用的场景:去重+排序
void TestSet(){int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };set<int> s(array, array+sizeof(array)/sizeof(array[0]));for (auto e : s){cout << e << " ";}cout << endl;}// multiset作用:排序
void TestMultiSet(){int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));for (auto e : s){cout << e << " ";}cout << endl;}int main(){TestSet();TestMultiSet();}
 
void TestSet(){int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };set<int> s(array, array+sizeof(array)/sizeof(array[0]));for (auto e : s){cout << e << " ";}cout << endl;}

定义了一个整型数组 array,其中包含了一些整数。

使用数组范围初始化了set容器 s。在初始化时,重复的元素会被自动去重,因此set中不会有重复的元素。

使用范围-based for 循环遍历set容器中的所有元素,并输出它们。由于set容器中的元素是按照一定顺序排列的,因此输出结果也是有序的。

 
// multiset作用:排序
void TestMultiSet(){int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };multiset<int> s(array, array + sizeof(array) / sizeof(array[0]));for (auto e : s){cout << e << " ";}cout << endl;}

定义了一个整型数组 array,其中包含了一些整数。

使用数组范围初始化了multiset容器 s。在初始化时,重复的元素会被保留,因此multiset中可以包含重复的元素,并且会按照一定顺序排列。

使用范围-based for 循环遍历multiset容器中的所有元素,并输出它们。由于multiset容器中的元素是按照一定顺序排列的,因此输出结果也是有序的。

Map的水果数量问题探究

统计输入的水果中每种水果出现的次数,并输出出现次数最多的前 k 种水果。

 
#include <vector>
#include<map>#include<iostream>
using namespace std;void GetFavoriteFruit(const vector<string>& fruits,size_t k){map<string, size_t> m;for (auto e : fruits)m[e]++;multimap<size_t, string, greater<int>> mm;for (auto& e : m){mm.insert(make_pair(e.second, e.first));}for (auto& e : mm){if (k == 0)return;cout << e.second << " ";k--;}cout << endl;}int main(){vector<string> fruits{"1111", "2222", "3333", "2222", "4444", "3333", "5555", "9999", "0000", "3333", "1111", "5555", "2222"};GetFavoriteFruit(fruits, 3);return 0;}

定义一个map来存储每个水果和它出现的次数:

map<string, size_t> m;:这个map以水果名称为键(string类型),以水果出现次数为值(size_t类型)。

遍历fruits向量,统计每种水果的出现次数:

for (auto e : fruits) m[e]++;:对于fruits向量中的每个元素e,在map m中增加它的计数。

定义一个multimap来按出现次数对水果进行排序:

multimap<size_t, string, greater<int>> mm;:这个multimap以水果出现的次数为键(size_t类型),以水果名称为值(string类型)。greater<int>使得元素按键(出现次数)的降序排序。

map m中的元素插入到multimap mm中:

for (auto& e : m) { mm.insert(make_pair(e.second, e.first)); }:遍历map m,将每个元素(即每种水果及其出现次数)插入到multimap mm中,键值对颠倒,使得次数成为键,水果名称成为值。

打印出现次数最多的前k个水果:

for (auto& e : mm) { if (k == 0) return; cout << e.second << " "; k--; }:遍历multimap mm,打印出键值对中的值(即水果名称)。每打印一个减少k的值,直到k为0或遍历完成。由于mm是按次数降序排列的,这保证了最先打印的是出现次数最多的水果。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

指针的进阶(C语言)(下)

目录 4、数组参数、指针参数传参 4.1一维数组传参 4.2二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5、函数指针 6、函数指针数组 7、指向函数指针数组的指针 8、回调函数 总结 续上篇 4、数组参数、指针参数传参 在写代码的时候难免把【数组】或者【指针】传给…

CVE-2024-0918 TEW-800MB RCE漏洞分析

漏洞描述 固件版本为1.0.1.0的TEW-800MB路由器存在命令注入漏洞。如果攻击者获得了web管理权限&#xff0c;他们可以将命令注入到httpd未知函数中的post请求参数DeviceURL中&#xff0c;从而获得shell权限。。 参考链接 TEW-800MB (notion.site)https://warp-desk-89d.notio…

Leetcode 11.盛水最多的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。…

2.5《Python3 网络爬虫开发实战》学习之实例实战1

目录 1 实战内容 2 确定思路 3 代码实操 3.1 实现一个个网页的爬取 3.2 爬取每一个网页的电影详情页url ​编辑 3.3 连接链接&#xff0c;针对每个详情页链接进行爬取、汇总内容 3.4 存储在txt文件中 4 结尾&#xff1a;整体代码 1 实战内容 爬取Scrape | Movie中所有…

Redis之缓存击穿问题解决方案

文章目录 一、书接上文二、介绍三、解决方案1. 单例双检锁2. 缓存预热和定时任务 一、书接上文 Redis之缓存雪崩问题解决方案 二、介绍 缓存击穿就是大量并发访问同一个热点数据&#xff0c;一旦这个热点数据缓存失效&#xff0c;则请求压力都来到数据库。 三、解决方案 1…

基于FPGA的I2C接口控制器(包含单字节和多字节读写)

1、概括 前文对IIC的时序做了详细的讲解&#xff0c;还有不懂的可以获取TI的IIC数据手册查看原理。通过手册需要知道的是IIC读、写数据都是以字节为单位&#xff0c;每次操作后接收方都需要进行应答。主机向从机写入数据后&#xff0c;从机接收数据&#xff0c;需要把总线拉低来…

echarts series中的data属性添加动态数据后不显示问题,一处儿异步细节问题

当从后端获取到数据后&#xff0c;发现饼图并没有顺利加载数据出来&#xff0c;使用console.log()测试先后执行顺序&#xff0c;会发现饼图的方法会比请求先执行 此时就可以把饼图的方法放入到请求执行结束后 以下为修改前&#xff1a; 修改后&#xff1a; 一处儿异步的细节问…

网络编程知识整理

目录 1.1 引言 1.2 分层 1.3 TCP/IP的分层 1.4 互联网的地址 1.5 域名服务 1.6 封装 1.7 分用 1.8 端口号 1.1 引言 很多不同的厂家生产各种型号的计算机&#xff0c;它们运行完全不同的操作系统&#xff0c;但 T C P / I P协议族允许它们互相进行通信。这一点很让人感…

Shell 脚本系列 | shell三剑客

目录 1、三剑客介绍2、三剑客之—grep1. 常用参数2. 常用示例1.过滤以#开头的行和空白行2.找出所有的mp3文件包含艺术家jayZ&#xff0c;不包含remix3.计算匹配项的数目4.在匹配字符串周围打印出行5.匹配显示所有IP 3、三剑客之一sed1.常用参数2.常用示例1. 奇数行后增加2. 删除…

虚拟机 安装 centos7 带桌面

虚拟机 安装 centos7 流程 https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/isos/x86_64/ CentOS-7-x86_64-DVD-2009.iso vmware 安装 centos7 的时候&#xff0c; 如果 不是 选择的 稍后 安装操作系统 &#xff0c; 会不让你选择配置选项&#xff0c;自动帮你把系统…

百度地图海量点方案趟坑记录(百度地图GL版 + MapVGL + vue3 + ts)

核心需求描述 不同层级有不同的海量图标展示底层海量图标需要展示文字拖动、放大缩小都需要重新请求数据并展示固定地图中心点&#xff08;拖动、放大缩小&#xff0c;中心点始终在地图中心&#xff09; 示例图片&#xff1a;&#xff08;某些图片涉及公司数据&#xff0c;就未…

【力扣 - 翻转二叉树】

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 提示&#xff1a; 树中节点数目范围在 [0, 100] 内 -100 < Node.val < 100题解&#xff1a;递归 思路与算法 这是一道很经典的二叉树问题。显然&#xff0c;我们从…