• set | map | multiset | multimap 快速上手


    map和set

    1.关联式容器

    序列式容器:
    在初阶,我们接触过STL部分容器,如: vector、list、deque、forward_list(C++11)等,这些容器被称为序列式容器。

    原因:这些容器底层结构为线性序列的数据结构,里面存储的是元素本身。

    关联式容器:
    关联式容器也是用来存储数据的。

    原因:关联式容器序列式容器不同的是,其里面存储的是结构的键值对,在数据检索时比序列式容器效率更高,因为数据间具有关联性

    2.键值对

    概念:用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表剑指,value表示与key对应的信息。
    比如:英汉互译的字典,每个字典中的英文单词与其中文含义保持一一对应的关系,就是key和value保持一一对应的关系。

    SGI-STL中关于键值对的定义:

    template 
    struct pair
    {
    typedef T1 first_type;
    typedef T2 second_type;
    T1 first;
    T2 second;
    pair(): first(T1()), second(T2())
    {}
    pair(const T1& a, const T2& b): first(a), second(b)
    {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.树形结构的关联式容器

    根据场景不同,STL总共实现了两种不同结构的管理式容器:树形结构与哈希结构。
    树形结构的关联式容器:map、set、multimap、multiset。
    该四种容器的共同点:使用平衡搜索树(即红黑树)作为底层结构,容器中的元素是一个有序的序列。

    3.1 set

    3.1.1 介绍

    3.1.2 特点
    • set是按照一定次序存储元素的容器

    • 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
      set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。

    • 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行
      排序。

    • set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对
      子集进行直接迭代。

    • set在底层是用二叉搜索树(红黑树)实现的。

    注意:

    • 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放
      value,但在底层实际存放的是由构成的键值对。

    • set中插入元素时,只需要插入value即可,不需要构造键值对。

    • set中的元素不可以重复(因此可以使用set进行去重)。

    • 使用set的迭代器遍历set中的元素,可以得到有序序列

    • set中的元素默认按照小于来比较

    • set中查找某个元素,时间复杂度为:O(logN)

    • set中的元素不允许修改(为什么?),因为修改之后就不满足AVL树或红黑树的条件了。

    • set中的底层使用二叉搜索树(AVL树或红黑树)来实现

    3.1.3 用例

    #include 
    #include 
    using namespace std;
    void TestSet()
    {
    	 /*用数组array中的元素构造set*/
    
    	int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4,
    	6, 8, 0 };
    	int sz = sizeof(array) / sizeof(array[0]);
    	int i = 0;
    	printf(" 用数组array中的元素构造set\n");
    	for (i = 0; i < sz; i++)cout << array[i] << ' '; cout << endl;
    
    	set s(array, array + sizeof(array) / sizeof(array[0]));//迭代器(或原生指针)区间构造
    	printf("set中元素个数\n");
    	cout << s.size() << endl;
    	/* 正向打印set中的元素,从打印结果中可以看出:set可去重*/
    	printf(" 正向打印set中的元素,从打印结果中可以看出:set可去重\n");
    	for (auto& e : s)
    		cout << e << " ";
    	cout << endl;
    	/* 使用迭代器逆向打印set中的元素*/
    	printf(" 使用迭代器逆向打印set中的元素\n");
    	for (auto it = s.rbegin(); it != s.rend(); ++it)
    		cout << *it << " ";
    	cout << endl;
    	/* set中值为3的元素出现了几次*/
    	printf(" set中值为3的元素出现了几次\n");
    	cout << s.count(3) << endl;
    }
    int main()
    {
    	TestSet();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3.2 map

    3.2.1 介绍

    3.2.2 特点
    • map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
      素。
    • 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
      typedef pair value_type;
    • 在内部,map中的元素总是按照键值key进行比较排序的。
    • map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
    • map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
    • map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))
    3.2.3 用例
    3.2.3.1 统计计数
    //一、统计次数
    	//法1.迭代器
    	map countMap;
    	for (auto& str : arr)
    	{
    	map::iterator it = countMap.find(str);
    	if (it != countMap.end())
    	{
    		//(*it).second++;
    		it->second++;//本质是it-> 先找到Node; 再(it->)->second;找到second;但编译器优化成了it->second;
    					 //不能再写(it->)->second了
    	}
    	else
    	{
    		countMap.insert(make_pair(str, 1));
    	}
    }
        
    
     	//法2.map的[]重载,以前是支持数组(顺序表)等连续的存储空间的结构使用。
    	// map的[]的意思变,返回key对应的value的引用。countMap[key]=value;
    	map countMap;
    	for (auto& str : arr)
    	{
    		// 1、str不在countMap中,插入pair(str, int()),然后在对返回value的引用,次数++
    		// 2、str在countMap中,返回value(次数)的引用,次数++;
    		countMap[str]++;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    3.2.4 成员函数/方法
    3.2.4.1 operator[]重载

    特点

    • map中有这个key时,返回value的引用。(查找,可修改value)
    • map中没有这个key时,会插入一个pair(key,V()),并返回value的引用。-其中V()是value类型匿名对象,默认为空。(插入,可修改value)

    用例

    	map dict;
    	dict.insert(make_pair("sort", "排序"));
    	dict["insert"];//如果不插入对应的value,则默认插入一个value()
    	dict["insert"] = "插入";
    	dict["left"] = "左边";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    模拟实现

    mapped _type& operator[](const key_type& k)
    {
        //本质是return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
        return (*((insert(make_pair(k,mapped_type()))).first)).second;
    }
    
    //解读:
    //一、insert解释
    //single element (1)	pair insert (const value_type& val); 
    // 简化为:pair insert(pair val);
    //1. key已经在map中,插入失败,返回pair(key_iterator,false);   即返回pair(已经有的key的迭代器,false);
    //2.key不在map中,插入成功,返回pair(new_key_iterator,true);   即返回pair(新的key的迭代器,true);
    
    //二、return值的解释
    //可拆分为
    //pair ret=insert(make_pair(key,V()));插入成功返回的是新key的迭代器,插入失败返回的是已有key的迭代器。
    //return ret.first ->second;   ret是pair。ret.first找到pair的iterator,iterator也是pair类型的指针。再对(ret.first) ->second是找到迭代器的value。
    
    
    //疑问:
    //为什么不用find+insert实现。因为find要遍历一遍查找,insert又要遍历一遍插入。所以能单纯insert就单纯insert,只需要遍历一遍插入。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.3 multiset

    3.3.1 介绍

    是map的一种,在头文件map中,要#include 包含头文件。

    3.3.2 特点
    • multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
    • 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是组成的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器中进行修改(因为元素总是const的),但可以从容器中插入或删除。
    • 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
    • multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭代器遍历时会得到一个有序序列。
    • multiset底层结构为二叉搜索树(红黑树)。
    3.3.3 用例

    用例与multimap类似,区别在于pair中的value是空值,不对其进行赋值操作。

    3.4 multimap

    3.4.1 介绍

    是map的一种,在头文件map中,要#include 包含头文件。

    3.4.2 特点
    • 支持键值冗余,因为multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
    • 不支持operator[]重载,因为可能有多个key,[]无法知道返回哪个key的value。
    • 在multiset中,元素的value也会识别它(因为multiset中本身存储的就是组成的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器中进行修改(因为元素总是const的),但可以从容器中插入或删除。
    • 在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
    • multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭代器遍历时会得到一个有序序列。
    • multiset底层结构为二叉搜索树(AVL树或红黑树)。
    3.4.3用例
    	multimap mdict;
    	mdict.insert(make_pair("sort", "排序"));
    	mdict.insert(make_pair("left", "左边"));
    	mdict.insert(make_pair("left", "左边"));
    	mdict.insert(make_pair("left", "剩余"));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.5 面试题/编程题

    • 前K个高频词汇

    题目链接:692. 前K个高频单词 - 力扣(LeetCode)

    题目描述:

    法一 优先级队列+仿函数

      struct Less//仿函数 stable_sort/sort传的less是排成升序,priority传的less是排成降序(大堆),注意区分。
          	     //即,sort传的less,若前<后为真则不需要交换,使的前<后。priority传的less,若前<后为真则需要交换,使得前>后。
        {
            bool operator()(const pair& kv1,const pair&kv2)const
            {
                if(kv1.secondkv2.first)//若次数相同,则kv1的字母值大于kv2的字母值,则交换
                return true;    										   //把字母小的往前挪,字母大的往后挪
                
                
                return false;
            }
        };
    
        vector topKFrequent(vector& words, int k) {
            map countMap;
            //统计次数
            for(auto& str:words)
            {
                countMap[str]++;//按字符大小顺序排一次序并且计数
            }
            
            //topk
            //法1.push构造 优先级队列
            // priority_queue,vector,Less>maxHeap;
            // for(auto& kv:countMap)
            // {
            //     maxHeap.push(kv);
            // }
       
            //法2.迭代器区间构造 优先级队列
            typedef  priority_queue,vector>,Less>MaxHeap;
            MaxHeap mh(countMap.begin(),countMap.end());
            //传Less优先级队列建大堆,即建起来之后是parent v;
            while(k--)
            {
                v.push_back(mh.top().first);//取topk
                mh.pop();
            }
            return v;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    法二 默认排序sort+稳定性仿函数 / stable_sort稳定性排序 (相同的值相对顺序不变)

    //stable_sort/sort无法对map进行排序,sort要求是联系物理空间的迭代器,所以先得把map的数据放到vector里。
    		 struct Greater
        	{
            bool operator()(const pair& kv1,const pair& kv2)
            {
                if(kv1.second>kv2.second)//若为真则不需要交换
                return true;
                
                if(kv1.second==kv2.second&&kv1.first topKFrequent(vector& words, int k) {
            map countMap;
            //统计次数
            for(auto& str:words)
            {
                countMap[str]++;//按字符大小顺序排一次序并且计数
            }
         
            vector> sortV(countMap.begin(),countMap.end());
            sort(sortV.begin(),sortV.end(),Greater());//也可以直接使用库里的stable_sort()进行排序,此时仿函数中可以不用考													 //虑相同值的先后顺序,因为stable_sort()会默认进行处理。
          	
            vector v;
            for(size_t i=0;i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    法三 multimap

      vector topKFrequent(vector& words, int k) {
            map countMap;
            //统计次数
            for(auto& str:words)
            {
                countMap[str]++;//按字符大小顺序排一次序并且计数。
            }
         
            multimap> sortMap;//不能使用map,因为map会去重,可能会舍去很多出现次数相同的单词。
          	//multimap的greater和less是对于key来说的,也就是现在的int。按降序来排,和sort的less和greater含义相同,
          	//若 前>后 为真,则不交换。使得大的在前,小的在后。
            //这里必须加个greater,因为如果不加的话排出来的是默认按less,这样排出来的multimap sortV是升序的。
          	//要取次数topk大只能用反向迭代器在sortV里倒着去取,但是因为sortV里的字母的顺序是已经在前面计数+字符大小排序时排好的			//了,倒着去取的话会使字母的顺序逆序。是不行的。
    
            for(auto& kv:countMap)
            {
                sortMap.insert(make_pair(kv.second,kv.first));//再按出现次数插入multimap排一次序,按出现次数排降序。
            }
    
            vector v;
            multimap>::iterator it=sortMap.begin();
            for(size_t i=0;isecond);//取topk
                it++;
            } 
            return v;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 两个数组的交集

    题目链接:https://leetcode.cn/problems/intersection-of-two-arrays/submissions/

    题目描述:

    法一 不相同,则小的++;相等,则同时++,并插入vector。最后的vector即为交集。

     vector intersection(vector& nums1, vector& nums2) {
            set s1(nums1.begin(),nums1.end());//默认构造set里已经排好序,升序。
            set s2(nums2.begin(),nums2.end());
    
            auto it1=s1.begin();
            auto it2=s2.begin();
    
            vector v;
            while(it1!=s1.end()&&it2!=s2.end())
            {
                //让小的++,因为小的一定不属于交集。
                if(*it1<*it2)//不相等的则为差集
                {
                    it1++;
                }
                else if(*it2<*it1)
                {
                    it2++;
                }
                else//找到相等才是交集。
                {
                    v.push_back(*it1);
                    it1++;
                    it2++;
                }
            }
            return v;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    从零开始了解协同OA办公系统,一篇文章就够了!
    @ControllerAdvice
    竞赛选题 深度学习 机器视觉 车位识别车道线检测 - python opencv
    设计模式- 代理模式(Proxy Pattern)结构|原理|优缺点|场景|示例
    React 全栈体系(十一)
    vue使用json-server 模拟数据
    移动Web第三天 1 移动端特点 && 2 百分比布局 && 3 Flex布局
    小网SIM卡QMI拨号无法获取IPv6地址问题的分析
    Spring设计模式,事务管理和代理模式的应用
    SpringBoot2.0---------------5、SpringBoot自动配置原理
  • 原文地址:https://blog.csdn.net/anyway33/article/details/127678932