【高阶数据结构】Map 和 Set(详解)(下)

简介: 【高阶数据结构】Map 和 Set(详解)(下)

四. C++中的Map


⚡Map的介绍


map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。

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

在内部,map中的元素总是按照键值key进行比较排序的。

map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。


⚡Map的用法


💦Map的模板参数


0a2653c851af460fa595bd959398a8f1.png


参数说明:


key: 键值对中key的类型


T: 键值对中value的类型


Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)


Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器


使用map要包含map的头文件哦


💦Map的迭代器

//map<string, string>::iterator it = dict.begin();
  auto it = dict.begin();
  while (it != dict.end())
  {
  //pair没有重载流插入
  //cout << (*it).first << (*it).second << endl;
  //cout << it->->first << it->->second << endl; 
  //operator重载
  cout << it->first << it->second << endl;
  ++it;
  }
  for (const auto& kv : dict)//给const & 引用
  {
  cout << kv.first << ":" << kv.second << endl;
  }
  cout << endl;


💦Map的构造


0a2653c851af460fa595bd959398a8f1.png

void testmap1()
{
  map<int, int> map1;//空构造
  int num[] = { 1,5,9,4,8,2,3,1,5,4,5,7 };
  for (auto e : num)
  {
  map1.insert(make_pair(e,e));
  }
  map<int, int> map2(map1.begin(),map1.end());//迭代区间构造
  map<int, int> map3(map2);//拷贝构造
  for (auto& e : map3)
  {
  cout << e.first << ":" << e.second << endl;
  }
}


💦Map的常见修改操作


🌏Insert


0a2653c851af460fa595bd959398a8f1.png


find插入的是pair的结构体,此版本是不支持冗余的,插入只看key值,有相等的就不需要了


void test_map1()
{
  map<string, string> dict;
  //pair<string, string> kv1("sort", "排序");
  //dict.insert(kv1);
  //匿名对象
  dict.insert(pair<string, string>("sort", "排序"));
  dict.insert(pair<string, string>("test", "测试"));
  dict.insert(pair<string, string>("string", "字符串"));
  //宏替换
  typedef pair<string, string> Dictkv;
  dict.insert(Dictkv("string", "字符串"));
}


还有一个很便捷的操作:make_pair:构造匿名的pair,返回


dict.insert(make_pair("string", "字符串"));//很常用


0a2653c851af460fa595bd959398a8f1.png


💦Map的[ ]的使用(重头戏)


2d65d23f6d4748949b924e4057485923.png


[]:的功能:查找 + 修改


map中有这个key,就返回value的引用(查找 + 修改)

map中没有这个key,会插入一个pair(key, K()),会插入这个key值,value就要去调用默认构造,最后还是返回value的引用 (插入+ 修改)

如果是int,就是0;是指针,就默认空指针;


map<string, string> dict;
  dict.insert(make_pair("sort", "排序"));
  dict["insert"];                       //插入
  dict["insert"] = "插入";              //插入 + 修改value返回值
  dict["left"] = "左边";                //插入 + 修改


0a2653c851af460fa595bd959398a8f1.png


可以看见Insert的返回值和实现


2d65d23f6d4748949b924e4057485923.png


key已经在map中,返回pair(key_iterator,false)

key不在map中,返回pair(new_key_iterator,true)

[]其底层实现:有两个pair结构


✨返回值是一个pair结构pair<iterator, bool>;第一个值是迭代器,第二个值是bool;第二个bool是用来反应是否插入成功,如果成功,则 第一个值迭代器就是指向插入的pair数据

第二个是kv的pair,是插入的pair数据

V& operator[](const K& key)
{
  pair<iterator, bool> ret = insert(make_pair(key, V());
  //返回key节点的迭代器,迭代器是map的,再调用->就是pair*,取second
  return ret.first->second;
}


⚡统计次数的两种方法


第一次找不到的时候make_pair,使得count = 1

后面每遍历一次就++一次

//统计次数
void test_map2()
{
  string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
  map<string, int> countMap;
  for (auto& str : arr)
  {
  map<string, int>::iterator it = countMap.find(str);
  if (it != countMap.end())
  {
    /*(*it).second++;*/
    //it->->second++;  第一个箭头是调用operator->返回的是pair*,再->指向成员
    it->second++;
  }
  else
  {
    //找不到
    countMap.insert(make_pair(str, 1));
  }
  }
  //遍历
  map<string, int>::iterator it = countMap.begin();
  while(it != countMap.end())
  {
  cout << it->first << ":" << it->second << endl;
  ++it;
  }
  cout << endl;
}


0a2653c851af460fa595bd959398a8f1.png


第二种方法:[]


map<string, int> countMap;
  for (auto& str : arr)
  {
  //1、str不在countMap中,插入pair(str, int()),然后对返回次数++
  //2、str在的话,就返回value的引用次数++
  countMap[str]++;
  }


0a2653c851af460fa595bd959398a8f1.png


⚡multimap的用法


multimap容器与map容器的底层实现以及成员函数的接口都是基本一致,区别是multimap 允许键值冗余,即multimap容器当中存储的元素是可以重复的


multimap<string, string> mdict;
  mdict.insert(make_pair("sort", "排序"));
  mdict.insert(make_pair("left", "左边"));
  mdict.insert(make_pair("left", "左边"));
  mdict.insert(make_pair("left", "你猜"));


0a2653c851af460fa595bd959398a8f1.png


五. 来两道题目练练手


1️⃣前K个高频单词


题目地址:传送


2d65d23f6d4748949b924e4057485923.png


要注意要按出现次数按大到小的来,如果出现次数相等,要按字母顺序排序,比如:i和love都出现了两次,那么 按字母顺序i要在love之前


思路:


使用一个countMap统计各种单词的出现的次数(统计次数 此时是i在上面,love在下面)

再写一个sortMap按照字符出现的次数进行降序排列

不同的单词出现相同的次数,会按字典序排列

0a2653c851af460fa595bd959398a8f1.png

注意不能用反向迭代来进行,不然同样的次数,love会在前面,i在后;就违背了题意


class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string, int> countMap;
        //统计次数   此时是i在上面,love在下面
        for(auto& str : words)
        {
            countMap[str]++;   
        }
        // apple 3  \  i  2  \  love  2 \ j  5
        multimap<int, string, greater<int>> sortMap;//排降序
        for(auto& kv : countMap)
        {
            sortMap.insert(make_pair(kv.second, kv.first)); 
        }
        vector<string> v;
        multimap<int, string, greater<int>> :: iterator it = sortMap.begin();
        for(size_t i = 0; i < k; i++)
        {
            v.push_back(it->second);
            it++;
        }
        return v;
    }
};


2️⃣两个数组的交集


题目地址:传送


0a2653c851af460fa595bd959398a8f1.png


思路:


2019082413041482.gif


class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        set<int> s1 (nums1.begin(), nums1.end());
        set<int> s2 (nums2.begin(), nums2.end());
        set<int> :: iterator it1 = s1.begin();
        auto it2 = s2.begin();
        vector<int> v;
        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 < *it2)
            {
                ++it1;
            }
            else if(*it1 > *it2)
            {
                ++it2;
            }
            else
            {
                v.push_back(*it1);
                ++it1;
                ++it2;
            }
        }
        return v;
    }
};


那如果是求差集呢?


2d65d23f6d4748949b924e4057485923.png


相关文章
|
6天前
|
存储 算法 Java
【Java高阶数据结构】并查集-最小生成树(下)
【Java高阶数据结构】并查集-最小生成树
11 3
|
6天前
|
存储 算法 Java
【Java高阶数据结构】并查集-最小生成树(上)
【Java高阶数据结构】并查集-最小生成树(上)
11 2
|
6天前
|
算法 Java
【Java高阶数据结构】图-图的表示与遍历(下)
【Java高阶数据结构】图-图的表示与遍历
14 1
|
5天前
|
JavaScript 前端开发 Java
ES6 逐点突破系列 -- Set Map,工作感悟,完美收官
ES6 逐点突破系列 -- Set Map,工作感悟,完美收官
|
5天前
|
存储 缓存 JavaScript
JavaScript中的Set和Map:理解与使用
JavaScript中的Set和Map:理解与使用
|
6天前
|
存储 算法 搜索推荐
【Java高阶数据结构】图补充-拓扑排序
【Java高阶数据结构】图补充-拓扑排序
7 1
|
6天前
|
算法 Java
【Java高阶数据结构】图的最短路径问题(下)
【Java高阶数据结构】图的最短路径问题
8 1
|
6天前
|
算法 Java
【Java高阶数据结构】图的最短路径问题(上)
【Java高阶数据结构】图的最短路径问题
6 1
|
6天前
|
机器学习/深度学习 存储 Java
【Java高阶数据结构】图-图的表示与遍历(上)
【Java高阶数据结构】图-图的表示与遍历
10 2
|
6天前
|
存储 JavaScript
ES6+新特性-Symbol与Set/Map数据结构
ES6 引入了三种新的数据结构:Symbol、Set和Map。Symbol是唯一且不可变的值,常用于定义对象的独特属性;Set存储不重复值,适合数组去重;Map则是键值对集合,键可为任意类型,提供了更灵活的存储方式。这些新数据结构提供了更高效的操作手段,分别解决了属性命名冲突、数据去重和复杂键值对存储的问题。示例展示了如何使用Symbol、Set和Map进行基本操作。