深入篇【C++】set和map(multiset/multimap)特性总结与使用

深入篇【C++】set和map(multiset/multimap)特性总结与使用

  • 一.set/multiset总结
  • 二.map/multiset总结
  • 三.set/map应用

一.set/multiset总结

在这里插入图片描述

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代(迭代完后有序)。
  5. set在底层是用二叉搜索树(红黑树)实现的。
    底层封装着一个pair类型的对象。这个对象里面存着两个成员变量即key和value。

【函数注意1】

1.set具有排序+去重功能。
2.set的查找函数find:找到了会返回当前结点的迭代器,找不到就会返回最后一个位置的迭代器end()。
3.set还有count函数,这个是用来计算出现的个数的,但对于set而言来说没有用,因为set是去重后,的最后的结果要么是0要么是1,所以count函数还可以用来具有查找功能。
4.count函数主要用于mutilset,mutilset可以允许重复的数据。
5.set与multiset的区别就在于set里面的数据是唯一的,不能出现重复的,是去重的,而multiset是允许出现重复的,不去重的。

【结构注意2】

1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放value,但在底层实际存放的是由构成的键值对.
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较compare=Less<.T.>
6. set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
7. set中的元素不允许修改(为什么---->const修饰)
8. set中的底层使用二叉搜索树即红黑树

二.map/multiset总结

1.在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
typedef pair value_type;

template <class T1, class T2>
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)
{}
};

2.底层封装一个pair类型的成员变量。存储着也是pair类型的对象,当要插入一个数据时,这个对象是pair类型的才可以被插入,所以可以使用匿名对象的形式插入

 dict.insert(pair<string, string>("run", "启动"));

也可以使用一个make_pair(T,T)函数来返回pair<.T>类型的对象

dict.insert(make_pair("string", "字符串"));

还可以以一种隐式类型转化的方式插入

 dict.insert({ "int","整形" });

这里会将这个数组类型转化成pair类型,怎么转化的?调用pair的构造函数,C++11支持双参构造函数调用时会发生隐式类型转化。

3.使用迭代器访问的不是真正的数据,而是pair类型的对象,所以要真正的访问数据,还需要进一步访问。而且第一个数据是不能被修改的,只能访问,第二个数据可以被修改可以被访问

map<string, string>::iterator it = dict.begin();

	while (it != dict.end())
	{
		//it是迭代器就相当于指针
		//正常是*it就是解引用到要访问的对象了,但这里的对象是pair不可以直接打印

		cout << (*it).first << (*it).second << endl;
		
		//it->first ="xxx";第一个数据不能被修改
		//it->second = "xxxx";第二个数据可以被修改
	    cout << it->first << it->second << endl;
	    //可以直接通过指针访问到里面的数据
		++it;
	}

4.对于map来说也是具有排序和去重功能,而且当插入相同的值。并不会插入进去,不会覆盖原来的数据。
5. 在内部,map中的元素总是按照键值key进行比较排序的。key是无法被修改的,而value的值可以被修改。
6. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
7. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
8. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。只不过与正常的下标访问不同,而是传key值,给你返回的是value值的引用。[ ]的本质就是通过insert完成的。

insert的返回值是pair
深入篇【C++】set和map(multiset/multimap)特性总结与使用_第1张图片

①当key在树里面,返回的是pair<在树里面key对于结点的iterator,false>。
②当key不在树里面,返回的是pair<刚插入树里面对应结点的iterator,true>。
所以[]里面其实具有两个功能,一是将数据插入进去,二是返回对应key的value值的引用。

V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.iterator->second;
	}
    map<string, string>kv;
	kv.insert(make_pair("apple", "苹果"));
	kv.insert(make_pair("yellow", "黄色"));
	kv.insert(make_pair("insert", "插入"));
    //[ ]具有读取value的功能
	//跟正常的[]类似,只不过map的[]读的是value值
	cout << kv["apple"] << endl;

	kv["sort"];//插入一个空value

	kv["sort"] = "排序";
	//修改
	kv["man"] = "男人";
	//插入+修改

【注意】

1. map中的的元素是键值对
2. map中的key是唯一的,并且不能修改
3. 默认按照小于的方式对key进行比较
4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
5. map的底层为平衡搜索树(红黑树),查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
6. 支持[]操作符,operator[]中实际进行插入查找。而multimap是不支持[],因为multimap一个key值可能对应多个value值,所以不知道返回哪一个了。

三.set/map应用

1.计算水果个数

void test3()
{
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	
	//map的find查找特点:当找到要要的key值后,会返回对应key-value的迭代器
	//如果没找到就会返回最后一个位置的后一个迭代器即end()
	map<string, int> count;
	for (auto e : arr)
	{
		auto ret = count.find(e);
		if (ret == count.end())//说明树里没有,那就插入进去,并给1
		{
			count.insert(make_pair(e, 1));
		}
		else//说明找到了,那就将对应的数字++
		{
			ret->second++;
		}
	}
	for (auto e : count)
	{
		cout << e.first << ":" << e.second << endl;
	}
	
}

另一种计算方法

for (auto e : arr)
	{
		count[e]++;
		//实现的原理:
		//第一步先插入,第二步返回value值
		//如果不存在就插入,并且value值就是匿名对象对应类型的缺省值,然后++就变成1
		// 如果存在插入也没事,不会覆盖,返回value 
	}

深入篇【C++】set和map(multiset/multimap)特性总结与使用_第2张图片
2.两个序列的交集
深入篇【C++】set和map(multiset/multimap)特性总结与使用_第3张图片

思路
1.两个序列中可能存在多个相同的数,但最终结点只会有一个,所以需要先去重,这就需要使用set。使用完set后,序列就变得有序了。
2.两个有序序列如何找交集呢?
交集:两个序列同时从头开始遍历,较小的一方就++,相同的就是交集然后再同时++。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {

     //首先要考虑两个序列都去重,那就都放进set里。
     set<int> s1(nums1.begin(),nums1.end());
     set<int> s2(nums2.begin(),nums2.end());
     //去重+排序
     vector<int> v;
     auto it1=s1.begin();
     auto it2=s2.begin();
     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;
    }
};

3.前K个高频单词
深入篇【C++】set和map(multiset/multimap)特性总结与使用_第4张图片
要求获取前k个出现最多的单词,不过这里还有个要求,当出现的次数相同时,就要按照字典序排序。

思路:
1.统计出现的次数我们可以利用map来统计。
2.再对根据出现的次数进行排序。选出前K个单词
3.不过这里的问题是当次数相同时,如何按照字典序再排序?
4.我们想map统计完后,单词的顺序是排序好的,每个单词的个数可能相同也可能不同。但如果当单词个数不相同时,对出现的个数排序就可以完成任务,因为没有出现相同的频率,但如果单词个数出现相同时,排序完后,如果它们的相对顺序没有被改变,那么也可以完成任务,因为相对顺序就是按照字典序排的,所以这个排序得要求是稳定的。
5.还有如何进行比较排序呢?我们需要使用仿函数来修改比较规则。根据次数进行比较,并且进行降序排序。

class Solution {
public:

  //要求先按照频率由高到低排序也就是出现的次数,如何统计出现的次数呢?用map统计次数


    struct Greater//仿函数重构比较方式,利用出现的次数进行比较,并且降序排,大的在前面,小的在后面
    {
    bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
    {
          return kv1.second>kv2.second;
    }
    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> countmap;
        for(auto &e:words)
        {
            countmap[e]++;
        }
        //已经统计完次数了接下来就要按照出现的频率来排序,不过map其实已经按照字典序的排序方法将数据排好
        //只要要按照频率排序后稳定性不变,相对顺序不变就可以了,但sort稳定性不行
        //注意sort无法对map排序,所以我们可以将map里的数据放进vector里

        vector<pair<string,int>> v; 
        v(countmap.begin(),countmap.end());
        stable_sort(v.begin(),v.end(),Greater());
        //stable_sort排序比较稳定
        vector<string> vs;
       for(int i=0;i<k;i++)
       {
         vs.push_back(v[i].first);//选取前k个单词
       }
      return vs;
    }
};

还有一种方法,可以直接使用sort,不用管稳定不稳定,那就是直接更改sort的比较排序规则,除了按照次数进行降序排序外,再添加上,当次数相同时,就按照字典序对单词进行比较。

class Solution {
public:

  //要求先按照频率由高到低排序也就是出现的次数,如何统计出现的次数呢?用map统计次数


    struct Greater//仿函数重构比较方式
    {

    bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
    {
          return kv1.second>kv2.second||(kv1.second==kv2.second&&kv1.first<kv2.first);
          //大于就是降序前面大,后面小
    }

    };
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> countmap;
        for(auto &e:words)
        {
            countmap[e]++;
        }
        //已经统计完次数了接下来就要按照出现的频率来排序,不过map其实已经按照字典序的排序方法将数据排好

        vector<pair<string,int>> v;
        v(countmap.begin(),countmap.end());
        sort(v.begin(),v.end(),Greater());
        //也可以利用仿函数的比较规则来使用sort排序,首先要按照频率比较,当频率相同时,按照字典序比较
       
        vector<string> vs;
       for(int i=0;i<k;i++)
       {
         vs.push_back(v[i].first);
       }
      return vs;
    }
};

你可能感兴趣的:(C++(进阶学习),c++,java,开发语言)