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

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

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

3.1 set

3.1.1 介绍

set | map | multiset | multimap 快速上手_第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 用例

set | map | multiset | multimap 快速上手_第2张图片

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

3.2 map

3.2.1 介绍

set | map | multiset | multimap 快速上手_第3张图片

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]++;
	}
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"] = "左边";

set | map | multiset | multimap 快速上手_第4张图片

模拟实现

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,只需要遍历一遍插入。

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", "剩余"));

set | map | multiset | multimap 快速上手_第5张图片

3.5 面试题/编程题

  • 前K个高频词汇

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

题目描述:

set | map | multiset | multimap 快速上手_第6张图片

法一 优先级队列+仿函数

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

法二 默认排序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

法三 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;
    }
  • 两个数组的交集

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

题目描述:

set | map | multiset | multimap 快速上手_第7张图片

法一 不相同,则小的++;相等,则同时++,并插入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;
    }

你可能感兴趣的:(STL,c++,算法,数据结构)