您的位置:首页 > 编程语言 > C语言/C++

C++(标准库):19---STL容器之(关联式容器map、multimap)

2020-04-19 21:19 447 查看

一、关联式容器概述

  • multiset:和set的唯一差别是,其元素可以重复
  • map:每个元素都是key/value pair,其中key是排序准则的基准。每个key只能出现一次,不允许重复。Map也被视为一种关联式数组,也就是“索引可为任意类型”的数组
  • multimap:和map的唯一差别是,其元素都可以重复。multimap可被当做字典使用
  • 关联式容器依据特定的排序准则,自动为其元素排序。元素可以是任何类型的value,也可以是key/value pair,其中key可以是任何类型,映射至一个相关value,而value也可以是任意类型
  • 排序准则:
      对于只有value的关联式容器,例如set、multiset,其对value进行排序。对于key/value pair的关联式容器,例如map、multimap,其根据key进行排序
    • 默认情况所有容器都以操作符<进行排序,不过你可以提供自己的比较函数,定义出不同的排序准则
  • 关联式的底层实现数据结构:
      关联式容器底层以平衡二叉树(红黑树)实现,源码剖析参阅:https://blog.csdn.net/qq_41453285/article/details/103645839
    • 因为使用平衡二叉树实现,所以对于特定的key或value,其能很快的找到特定的元素,因为其操作具备的是对数复杂度,而普通的循序式容器的复杂度是线型的。因此,使用关联式容器,面对1000个元素,平均而言你只要10次比较操作而不是500比较就可以查找特定的元素
  • 关联式容器拥有自动排序能力,并不意味着它们在排序方面的执行效能更高。实际上,由于股演练时容器每安插一个新元素都要进行一次排序,速度反而不及序列式容器经常采用的收发:先安插所有元素,然后调用STL提供的排序算法进行一次完全排序
  • 二、map

    格式

    • map<key,value>:由“键值(key)与值(value)”两部分组成,这两者形成映射关系
    • 头文件:#include <map>

    特点

    • key是唯一的,不可重复;但value可以重复出现
    • key与value必须具有可赋值、可拷贝的性质
    • key的数据类型必须是可比较的,根据key来进行排序(默认为升序)
    • key与value可以是任何类型:int、doule、字符串、结构体、类......
    • map的每一组key与value相当于一个pair容器对象(pair容器,见文章:https://blog.csdn.net/qq_41453285/article/details/100628402

    key的比较操作

    • 我们知道key必须是可以比较的,如果key的类型不能进行比较(例如“<”运算符进行比较),则程序出错
    • 如果使我们自定义的类,如果没有提供比较运算符的重载,那么就不能作为关联容器的key使用,因为自定义的类不能进行比较
    • 如果key可以进行比较,且遵循下面图片中的规则:

    升序、降序操作

    • map的key默认为升序排序,但是我们也可以通过一些操作来实现降序操作,或者显式的进行升序操作
    • 下面两个标准库函数(less、greater)都在头文件#include <xfunctional>中
    • 下面我们先演示默认的升序排序
    [code]map<string, int,std::less<string>> word_count;//显式地升序
    //map<string, int,> word_count;//默认的升序
    
    string word;
    while (cin >> word) {
    ++word_count[word];
    }
    for (const auto &w : word_count) {
    cout << w.first << "  occurs  " << w.second <<
    ((w.second>1) ? "  times" : "  time") << endl;
    }

    • 演示降序排序:
    [code]map<string, int,std::greater<string>> word_count;//降序
    
    string word;
    while (cin >> word) {
    ++word_count[word];
    }
    for (const auto &w : word_count) {
    cout << w.first << "  occurs  " << w.second <<
    ((w.second>1) ? "  times" : "  time") << endl;
    }

    初始化

    • 如果没有给出初始化值,就默认为空容器(使用系统的默认值初始化)
    • 手动给出值初始化

    下标运算符和at函数

    • map和unordered_map提供下标操作,因为其key唯一;multimap与unordered_multimap不提供下标操作,因为其key不唯一
    • 下标运算符可以用来创建一个键值对或访问键所对应的值
    • 由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下表操作
    • 下标运算符则:

    • 演示案例:
    [code]map<string, size_t> word_count;
    word_count["a"] = 1; //添加key与value对
    word_count["b"] = 2; //添加key与value对
    word_count["c"] = 3; //添加key与value对
    
    word_count["a"] = 4; //更改key对应的value
    cout << word_count["a"] << endl; //打印4
    cout << word_count.at("a") << endl; //打印4

    迭代器与遍历

    • 当解引用一个关联容器迭代器时,我们得到的是容器的value_type的值的引用
    • 对于map来说,value_type为一个pair类型,因此得到pair类型之后,我们就可以调用first和second来访问key与value
    • 重点:得到的迭代器,我们可以改变其second(value)的值,但不能改变其first(key)的值(因为为const类型)
    [code]map<string, int> word_count;
    word_count["Hello"] = 3;
    word_count["World"] = 3;
    
    auto map_it = word_count.begin(); //得到第一个元素的迭代器
    //*map_it是指向一个pair<const string,size_t>的引用
    cout << map_it->first << endl;;  //打印Hello
    cout << map_it->second << endl;  //打印3
    
    map_it->first = "new key";//错误,key关键字不可以改变(为const类型)
    ++map_it->second; //正确,key关键字对应的value可以改变
    • 通过迭代器遍历容器:可以使用iterator或者const_iterator类型的迭代器遍历
    [code]map<string, int> word_count;
    word_count["Hello"] = 3;
    word_count["World"] = 3;
    
    //map_it为map<string, int>::const_iterator类型
    auto map_it = word_count.cbegin();
    while (map_it != word_count.cend()) {
    cout << map_it->first << "" << map_it->second << endl;
    ++map_it;
    }

    添加元素(insert、emplace)

    • 这里介绍的添加元素方式,适合所有关联容器,后面不再介绍

    • 对于key唯一的,插入时会先判断key是否存在,不存在则插入;存在则什么都不做
    • 对于key不唯一的,每次插入都插入新值

    insert有3个版本:

    • 参数为value_type或参数为一对迭代器或者参数为一个初始化列表

    演示set的插入

    [code]vector<int> ivec = { 2,4,6,8,10 };
    set<int> set1;
    set1.insert(ivec.cbegin(), ivec.cend()); //迭代器版本
    
    set1.insert({ 1,3,5,7,9 }); //初始化器版本插入

    map的插入

    • 由于map的key是pair容器类型,因此map的插入有一些特殊情况
    [code]map<string, size_t> word_count;
    word_count.insert({ "a",1 });
    word_count.insert(make_pair("a",1));
    word_count.insert(pair<string,size_t>("a", 1));
    word_count.insert(map<string,size_t>::value_type("a", 1));

    insert/emplace的返回值

    [code]//案例:单词计数
    
    map<string, size_t> word_count; //空的
    string word;
    while (cin >> word) {
    //插入一个元素,关键字等于word,值为1
    //若word已经存在,insert什么都不做
    auto ret = word_count.insert({ word,1 });
    if (!ret.second) //检查返回值的bool部分,若为false,则word已在word_count中
    ++ret.first->second; //递增计数器
    }

    展开递增语句 

    删除元素(erase)

    • 关联容器定义了3个版本的erase: ①传递一个key_type参数。如果元素存在(删除所有匹配给定关键字的元素,返回实际删除的元素数量);如果元素不存在(函数返回0)
    • ②传递给erase一个迭代器来删除一个元素,函数返回void(与顺序容器类似)
    • ③传递给erase一个迭代器对来删除一个元素范围,函数返回void(与顺序容器类似)

    • 演示案例

    [code]map<string, size_t> word_count;
    word_count["a"] = 1;
    word_count["b"] = 2;
    word_count["c"] = 3;
    
    string removal_word = "a";
    if (word_count.erase(removal_word))
    cout << "ok:"<< removal_word <<"  removed" <<endl;
    else
    cout << "error:" << removal_word << "  not found" << endl;

    其他操作

    • 其他关联容器都可以参数这里的知识点

    对map使用find代替下标操作

    • 如果使用下标运算符来查找map、unordered_map中的一个关键字,会有一个缺点,就是如果这个关键字不存在,则会插入这个关键字。因此想要寻找一个关键字是否存在map中,最好使用find函数
    [code]map<string, size_t> word_count; //空的
    //如果不存在,返回map的尾后迭代器
    if (word_count.find("abcd") == word_count.end())
    cout <<"abcd is not in the map" << endl;

    在multimap、multiset中查找元素

    • multimap、multiset中的key可以重复,并且相同的key会相邻存储,因此如果想要查看相同的key键值,可以使用count和find函数配合查看
    [code]multimap<string, string> author; //空的
    author.insert({ "C Primer","Bob" });
    author.insert({ "C++ Primer","Tom" });
    author.insert({ "C++ Primer","Alice" });
    author.insert({ "Java","Alan" });
    
    auto entried = author.count("C++ Primer"); //计算出键为“C++ Primer”的数量
    auto iter= author.find("C++ Primer");//先找到第一个迭代器位置
    while (entried) { //循环遍历
    cout << iter->second<< endl;
    ++iter;
    --entried;
    }

    lower_bound、upper_bound函数

    • 在key可以重复的容器中,lower_bound返回这个key所对应的第一个位置,upper_bound返回最后一个匹配给定关键字的元素的下一个位置
    • 如果key不存在,lower_bound和upper_bound返回相等的迭代器——指向一个不影响排序的关键字插入位置

    • 根据上面的图片可知,如果key不存在容器中,且大于所有的关键字,则lower_bound返回的就是尾后迭代器
    • 这两个函数可以实现上面在multimap、multiset中查找重复的key。见下面案例
    [code]multimap<string, string> author; //空的
    author.insert({ "C Primer","Bob" });
    author.insert({ "C++ Primer","Tom" });
    author.insert({ "C++ Primer","Alice" });
    author.insert({ "Java","Alan" });
    
    //beg指向于key为"C++ Primer"的第一个位置,end指向于最后一个位置的下一个位置
    for (auto beg = author.lower_bound("C++ Primer"),
    end = author.upper_bound("C++ Primer");
    beg != end; ++beg)
    {
    cout << beg->second << endl;
    }

    equal_range函数

    • 该函数接受一个关键字,然后一个参数为迭代器的pair容器类型
    • 若关键字存在,则pair容器中的第一个迭代器指向于第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置;若不存在关键字,两个迭代器都指向关键字可以插入的位置
    • 使用来函数也可以实现上面find、lower_bound等函数来遍历相同的key的操作,见下面演示案例
    [code]multimap<string, string> author; //空的
    author.insert({ "C Primer","Bob" });
    author.insert({ "C++ Primer","Tom" });
    author.insert({ "C++ Primer","Alice" });
    author.insert({ "Java","Alan" });
    
    for (auto pos = author.equal_range("C++ Primer");
    pos.first != pos.second; ++pos.first)
    {
    cout <<pos.first->second << endl;
    }
    
    /*pos为pair<iterator1,iterator2>类型,
    iterator1为指向于第一个"C++ Primer"的迭代器,
    iterator2指向于最后一个"C++ Primer"的后一个位置*/

    演示案例

    •  利用map来输入保存的字符串,然后输出字符串出现的次数
    [code]#include <iostream>
    #include <map>
    #include <string>
    
    using namespace std;
    
    int main()
    {
    //key为string类型,value为size_类型
    map<string, size_t> word_count;
    string word;
    while (cin >> word) {
    ++word_count[word];
    }
    
    for (const auto &w : word_count) {
    cout <<w.first <<":"<<w.second<<((w.second>1)?"times":"time")<< endl;
    }
    return 0;
    }

    三、multimap

    格式

    • 与map相同:由“键值(key)与值(value)”两部分组成,这两者形成映射关系
    • 头文件:#include <map>

    特点

    • key可以重复(如果有相同的key,则相邻存储),默认也为升序排序
    • 其它特点与map相同
    • multimap不提供下标操作(下标运算符和at函数),因为key可以重复

    升序、降序操作

    • 与map相同,key默认是升序操作,我们也可以将key设置为显式地升序或者设置为降序排序
    • 下面两个标准库函数(less、greater)都在头文件#include <xfunctional>中
    • 演示升序操作
    [code]multimap<string, int, std::less<string>> word_count;//升序
    //multimap<string, int> word_count;//升序
    
    word_count.insert({ "a",1 });
    word_count.insert({ "c",1 });
    word_count.insert({ "b",1 });
    
    for (const auto &w : word_count) {
    cout << w.first << "  occurs  " << w.second <<
    ((w.second>1) ? "  times" : "  time") << endl;
    }

    • 演示降序操作
    [code]multimap<string, int,std::greater<string>> word_count;//降序
    
    word_count.insert({ "a",1 });
    word_count.insert({ "c",1 });
    word_count.insert({ "b",1 });
    
    for (const auto &w : word_count) {
    cout << w.first << "  occurs  " << w.second <<
    ((w.second>1) ? "  times" : "  time") << endl;
    }

    初始化

    • 如果没有给出初始化值,就默认为空容器

    迭代器与遍历

    • 与map相似,见map

    添加元素(insert、emplace)

    • 见map笔记处

    删除元素(erase)

    • 见map函数,原理相同

    其他操作

    • 详细介绍,见map容器处

    • 点赞
    • 收藏
    • 分享
    • 文章举报
    江南、董少 博客专家 发布了1602 篇原创文章 · 获赞 1216 · 访问量 58万+ 他的留言板 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: