侧边栏壁纸
博主头像
LittleAO的学习小站 博主等级

在知识的沙漠寻找绿洲

  • 累计撰写 125 篇文章
  • 累计创建 27 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

C++容器之关联容器

LittleAO
2024-07-15 / 0 评论 / 0 点赞 / 23 阅读 / 0 字
温馨提示:
本文最后更新于2024-07-21,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

C++之关联容器

介绍

关联容器支持高效的关键字查找和访问。C++提供了两个主要的关联容器类型:mapset。从这两个类型延伸出八个关联容器,分为两类:关键字带不带multi,带不带unordered。具体详情会在后面讲解。
map存储的是键值对,键又称为关键字起到索引的作用。值表示与索引相关联的数据。set则只是一些数据的集合,可以查询某个关键字是否存在于set中。
mapmultimap在头文件map中,setmultiset在头文件set中。unordered_mapunordered_set等无序容器在头文件unordered_mapunordered_set中。

定义关联容器

定义一个map,必须既指明关键字类型又指明值类型。定义一个set只需要指明关键字类型。每个关联容器都有默认构造函数,用来创建空容器。也可以用大括号来对关联容器进行值初始化。值初始化set可以向vector一样进行初始化。值初始化map则需要传入关键字和值并使用大括号包起来。

set<string> example_set({"apple", "banana", "orange"});
map<string, int> example_map({
    {"Alice" , 3},
    {"Bob", 5},
    {"John", 2}
});

setmap都只允许关键字出现一次,如果想要定义多个关键字,需要使用multimapmultiset

set<string> example_set({"apple", "banana", "orange", "orange"});
multiset<string> example_multiset({"apple", "banana", "orange", "orange"});
map<string, int> example_map({
    {"Alice" , 3},
    {"Bob", 5},
    {"John", 2},
    {"John", 4}
});
multimap<string, int> example_multimap({
    {"Alice" , 3},
    {"Bob", 5},
    {"John", 2},
    {"John", 4}
});

cout << example_map.size() << endl; // 3
cout << example_multimap.size() << endl;    // 4
cout << example_set.size() << endl; // 3
cout << example_multiset.size() << endl;    // 4

需要注意的是,我们所指定的关键字类型必须定义一个严格弱序。因此要想在关联容器中指定我们的类,需要在指定关键字类型后面提供一个指向比较函数的指针。构造函数传入比较函数。例如:

class MyClass
{
public:
    MyClass(int val): val(val) {}
    int val;

    static bool compare(const MyClass& c1, const MyClass& c2);
};

bool MyClass::compare(const MyClass &c1, const MyClass &c2)
{
    return c1.val < c2.val; // 严格弱序
}

int main()
{
    multiset<MyClass, decltype(MyClass::compare)*> correct_set(MyClass::compare); // 编译通过
    correct_set.insert(MyClass(2));
    correct_set.insert(MyClass(1));
    multimap<MyClass, size_t, decltype(MyClass::compare)*> correct_map(MyClass::compare);
    correct_map.insert(make_pair(MyClass(2), 5));
    correct_map.insert(make_pair(MyClass(9), 5));
}

pair类型

在讲述关联容器的操作前,需要了解pair标准库类型,在头文件utility中。pair数据类型十分简单,定义时需要指定两个数据类型,用于存储两个类型的数据,例如下面这样。

pair<string, size_t> word_count;

一般情况下,我们定义pair类型可以不用手动指定类型,可以使用make_pair来自动推导。

auto word_count2 = make_pair("Hello", 2); // 自动推导为 pair<const char *, int>)

可以使用数据成员firstsecond来访问其对应的数据。

    auto word_count2 = make_pair("Hello", 2); // 自动推导为 pair<const char *, int>)
    cout << word_count2.first << " " << word_count2.second << endl; // Hello 2

关联容器操作

类型别名

关联容器新增了三个类型别名:

  1. key_type:表示关联容器中键的类型。
  2. mapped_type:仅适用于std::map,表示映射到每个键的值的类型。
  3. value_type:对于std::map,它等同于std::pair<const Key, T>,其中Key是键的类型,T是值的类型;对于std::set,它等同于键的类型。

迭代器

解引用一个关联容器类型的迭代器,会得到一个容器为value_type的值的引用。set类型的迭代器是const的。

算法

关联容器使用泛型算法是一个坏主意。通常我们只需要搜索关联容器。例如find,使用泛型算法的find会顺序搜索关键字,这样做mapset的优势荡然无存。普通的map和set的数据结构是一棵红黑树,支持O(logN)的查找。

调用其成员函数find就能最大性能保证搜索。具体的操作会在后面详细讲解。

添加元素

使用insert成员为关联容器添加元素,如果是普通不包含重复关键字的mapset,插入一个以存在的元素没有任何影响。insert有两个版本:

  • c.insert(b, e):接受两个迭代器,插入迭代器中的内容;
  • c.insert({elem1, elem2}):接受初始化器列表,添加到容器中。

map插入的元素必须是一个pair类型。

vector<int> nums{2,5,3,1,7,5,0};
set<int> example_set;
example_set.insert(6);  // {6}
example_set.insert({4,8,9});    // {4, 6, 8, 9}
example_set.insert(nums.begin(), nums.end());   // {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

map<string, size_t> word_count;
word_count.insert({ "apple", 5 });
word_count.insert( make_pair("banana", 3));
word_count.insert(pair<string, size_t>("pineapple", 10));
word_count.insert(decltype(word_count)::value_type("orange", 8));

insertemplace的值依赖于容器类型和返回参数。只插入单个关键字,其返回值是一个pair类型。第一个元素是一个指向给定关键字元素的迭代器,第二个元素是一个bool值。若返回的boolfalse,则代表插入失败,第一个元素也为空。

删除元素

insert一样,关联容器提供erase来删除元素。其参数可以是一个迭代器,或是一对迭代器,此外还提供接受一个key_type参数的元素,用于删除单个元素,其返回值为删除元素的数量。

对于不保存重复关键字的容器,其erase的返回值始终为0或1,0代表没有找到元素。

map的下标操作

mapunordered_map提供了下标运算符和一个对应的at函数。set不支持下标。不能对一个multi类型的容器进行下标操作。

map下标支持一个关键字,如果这个关键字不存在,map会自动创建一个该关键字类型的元素,并对值进行初始化。

map<string, size_t> c;
c["hello"] = 1; // 不会报错

访问元素

  • find(k)会寻找关键字为参数的元素的迭代器,如果找不到,则会返回容器的尾迭代器。
  • count(k)会做的更多一些,它会返回一个指定元素在容器中的个数。如果不需要计数,则最好使用find
  • lower_bound(k)返回一个迭代器,指向第一个关键字不小于k的元素。
  • upper_bound(k)返回一个迭代器,指向第一个关键字大于k的元素。

lower_boundupper_bound不适用于无序容器。

  • c.equal_range(k)会返回一个迭代器pair,表示关键字等于k的元素范围。
multimap<string, size_t> word_count;
word_count.insert(make_pair("hello", 2));
word_count.insert(make_pair("hello", 5));
word_count.insert(make_pair("world", 9));

// multimap遍历全部关键字的方法
// 第一种:统计法
auto left = word_count.find("hello");
auto count = word_count.count("hello");
if (left != word_count.end())
{
    while (count--)
    {
        cout << left->first << ": " << left->second << endl;
        ++left;
    }
}

// 第二种:用lower_bound和upper_bound
string key = "hello";
for (auto i = word_count.lower_bound(key); i != word_count.upper_bound(key); ++i)
{
    cout << i->first << ": " << i->second << endl;
}

// 第三种:直球
auto range_pair = word_count.equal_range("hello");
for (auto i = range_pair.first; i != range_pair.second; ++i)
{
    cout << i->first << ": " << i->second << endl;
}

附件

源代码

0

评论区