【高阶数据结构】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


目录
打赏
0
0
0
0
5
分享
相关文章
|
5月前
|
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
118 2
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
128 2
哈希表模拟封装unordered_map和unordered_set
哈希表模拟封装unordered_map和unordered_set
Go语言中的map数据结构是如何实现的?
Go 语言中的 `map` 是基于哈希表实现的键值对数据结构,支持快速查找、插入和删除操作。其原理涉及哈希函数、桶(Bucket)、动态扩容和哈希冲突处理等关键机制,平均时间复杂度为 O(1)。为了确保线程安全,Go 提供了 `sync.Map` 类型,通过分段锁实现并发访问的安全性。示例代码展示了如何使用自定义结构体和切片模拟 `map` 功能,以及如何使用 `sync.Map` 进行线程安全的操作。
|
3月前
|
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
83 18
你对Collection中Set、List、Map理解?
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
80 20
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
76 3
【C++】map、set基本用法
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
43 5
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
51 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等