关联容器

关联容器
题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。
作者: tyc611, 2007-01-25
    本文主要讨论C++标准库中的关联容器(associative container),内容主要涉及map, set, multimap和multiset四类容器。
    如果文中有错误或遗漏之处,敬请指出,谢谢!
概述
    关联容器(Associative Container)与顺序容器(Sequential Container)的本质区别在于:关联容器是通过键(key)存储和读取元素的,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。
    关联容器支持通过键来高效地查找和读取元素,两个基本的关联容器是map和set。map的元素是“键-值”对的二元组形式:键用作元素在map中的索引,而值则表示所存储和读取的数据。set仅包含一个键,并有效地支持关于某个键是否存在的查询。set和map类型的对象所包含的元素都具有不同的键。如果需要一个键对应多个实例,则需要使用multimap或multiset类型。这两种类型允许多个元素拥有相同的键。
map关联数组:元素通过键来存储和读取
set大小可变的集合,支持通过键实现的快速读取
multimap支持同一个键多次出现的map类型
multiset支持同一个键多次出现的set类型
pair类型
    pair模板类用来绑定两个对象为一个新的对象,该类型在<utility>头文件中定义。pair类型提供的操作如下表:
pair<T1, T2> p1;创建一个空的pair对象,它的两个元素分别是T1和T2类型,采用值初始化
pair<T1, T2> p1(v1, v2);

创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2

make_pair(v1, v2)以v1和v2值创建一个新的pair对象,其元素类型分别是v1和v2的类型
p1 < p2字典次序:如果p1.first<p2.first或者!(p2.first < p1.first)&& p1.second<p2.second,则返回true
p1 == p2如果两个pair对象的first和second成员依次相等,则这两个对象相等。
p.first返回p中名为first的(公有)数据成员
p.second返回p中名为second的(公有)数据成员
关联容器
    关联容器共享大部分顺序容器的操作,但不提供front, push_front, back, push_back以及pop_back操作。
    具体而言,有顺序容器中的:前三种构造函数;关系运算;begin, end, rbegin和rend操作;类型别名;swap和赋值操作,但关联容器不提供assign函数;clear和erase函数,但erase函数返回 void类型;关于容器大小的操作,但resize函数不能用于关联容器。

map类型
    map类型定义在头文件<map>中。map是键-值对的集合,通常看作关联数组:可使用键作为下标来获取一个值。map类定义内部定义的类型有key_type, mapped_type, value_type,如下表所示:
map<K, V>::key_type在map容器内,用做索引的键的类型
map<K, V>::mapped_type在map容器中,键所关联的值的类型
map<K, V>::value_typemap的值类型:一个pair类型,它的first元素具有
const map<K, V>::key_type类型,而second元素
则为map<K, V>::mapped_type类型
注意:map的元素类型为pair类型,且键成员不可修改。其它类型别名与顺序容器一样。
map对象的定义
map<K, V> m;创建一个名为m的空map对象,其键和值的类型分别为K和V
map<K, V> m(m2);创建m2的副本m,m与m2必须有相同的键类型和值类型
map<k, V> m(b, e);创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本。元素的类型必须能转换为pair<const k, v>
键类型的约束
    在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。默认情况下,标准库使用键类型定义的 < 操作符来实现键的比较。这个比较函数必须满足:当一个键和自身比较时,结果必定是false;当两个键之间都不存在“小于”关系时,则容器将之视为相同的键。也就是说,map内的元素按键值升序排列。
operator[]
A::reference operator[](const Key& key);
[]操作符返回键key所关联的值的引用;如果该键key不存在,则向map对象添加一个新的元素,元素的键为key,所关联的值采用值初始化。(要特别留意这个副作用)
注:map下标操作符返回的类型(mapped_type&)与对map迭代器进行解引用获得的类型(value_type)不相同。

    例如:
       map <string, int> wordCount;    // empty map
       word_count["Hello"] = 1;
    上面的代码首先创建一个空的map对象,然后执行下列步骤:
    1)在wordCount中查找键为“Hello”的元素,没有找到;
    2)将一个新的键-值对插入到wordCount中,其中,键为“Hello”,值为0
    3)读取新插入的键-值对的值,并将它的值赋为1。
应用实例,下面的程序用来统计一篇英文文章中单词出现的频率:

#include <iostream>
#include <map>
using namespace std;

int main()
{
    map<string, int> wordCount;
    string word;
    while (cin >> word)
        ++wordCount[word];
    
    for (map<string, int>::iterator it = wordCount.begin(); it != wordCount.end(); ++it)
        cout<<"Word: "<<(*it).first<<" /tCount: "<<(*it).second<<endl;
    
    return 0;
}

map::insert
m.insert(e)    e是一个用在m上的value_type类型的值,如果键(e.first)不在m中,则插入e到m中;如果键已经在m中存在,则保持m不变。
    该函数返回一个pair类型对象,如果发生了插入动作,则返回pair(it, true);否则返回pair(it, false)。其中,it是指向键为e.first那个元素的迭代器。
m.insert(beg, end)

beg和end是标记元素范围的迭代器,其中的元素必须为value_type类型的键-值对。对于该范围内的所有元素,如果它的键在m中不存在,则将该键及其关联的值插入到m。返回void类型。

m.insert(iter, e)insert(e),并以iter为起点搜索新元素的位置。返回一个迭代器,指向m中键为e.first的元素。
注:当需要插入一个map元素时,一是可以用map::value_type来构造一个pair对象,另外,也可以用make_pair来构造这个对象。
查找元素
m.count(k)返回m中k的出现次数(0或1)
m.find(k)如果容器中存在键为k的元素,则返回指向该元素的迭代器。
如果不存在,则返回end()值。
删除元素
m.erase(k)删除m中键为k的元素,返回size_type类型的值,表示删除的元素个数(0或1)
m.erase(p)从m中删除迭代器p所指向的元素。p必须指向m中确实存在的元素,而且不能等于e.end()。返回void类型
m.erase(b, e)从m中删除[b, e)范围内的元素,返回void类型

set类型
    set类型定义于<set>头文件中。set容器支持大部分map容器的操作,如:构造函数;insert操作;count和find操作; erase操作。两个例外情况是:set不支持下标操作符,而且没有定义mapped_type类型。与map一样,set容器存储的键也必须是唯一的,而且不能修改。

multimap和multiset类型
    map和set容器中,一个键只能对应一个实例。而multiset和multimap类型则允许一个键对应多个实例。
    multimap和multiset所支持的操作分别与map和set的操作相同,只有一个例外:multimap不支持下标运算。为了顺序一个键可以对应多个值这一特性,map和mulitmap,或set和multiset中相同的操作都以不同的方式做出了一定的修改。
元素的添加和删除
    map和set容器中的insert和erase操作同样适用于multimap和multiset容器,实现元素的添加和删除。
    由于键不要求是唯一的,因此每次调用insert总会添加一个元素。
    而带有一个键参数的erase将删除拥有该键的所有元素,并返回删除元素的个数;而带有一个或一对迭代器参数的erase版本只删除指定的元素,并返回void类型。
查找元素
    在map和set容器中,元素是有序存储的(升序),同样multimap和multiset也一样。因此,在multimap和multiset容器中,如果某个键对应多个实例,则这些实例在容器中将相邻存放,即迭代遍历时,可保证依次返回特定键所关联的所有元素。
    要查找特定键所有相关联的值,可以有下面三种方法:
    1)配合使用find和count来查找:count函数求出某键出现的次数,而find操作返回指向第一个键的实例的迭代器。
    2)使用lower_bound和upper_bound函数:这两个函数常用于multimap和multiset,但也可以用于map和set容器。所有这些操作都需要传递一个键,并返回一个迭代器。
m.lower_bound(k)返回一个迭代器,指向键不小于k的第一个元素
m.upper_bound(k)返回一个迭代器,指向键大于k的第一个元素
m.equal_range(k)返回一个迭代器的pair对象;它的first成员等价于
m.lower_bound(k),而second成员则等价于
m.upper_bound(k)
注意:形成的有效区间是[lower_bound(k), upper_bound(i)),是个半开半闭区间。
      lower_bound返回的迭代器不一定指向拥有特定键的元素。如果该键不在容器中,则lower_bound返回在保持容器元素顺序的前提下该键应被插入的第一个位置。
      若键不存在,返回的迭代器相同。
    3)使用equal_range,其实质跟法2)相同。

    如果文中有错误或遗漏之处,敬请指出,谢谢!
 
<think>我们被要求解释C++关联容器的定义和种类。根据引用[2]和引用[3],关联容器C++标准库中的一类容器,它们通过关键字(key)来存储和访问元素。关联容器分为有序关联容器和无序关联容器两大类。###有序关联容器有序关联容器使用比较运算符(默认是<)来组织元素,内部通常以平衡二叉搜索树(如红黑树)实现,因此元素总是按照关键字升序排列(除非自定义比较函)。包括: -`set`:只存储关键字,关键字不可重复- `map`:存储键值对(key-value),每个key唯一- `multiset`:关键字可重复出现的set- `multimap`:关键字可重复出现的map###无序关联容器无序关联容器(引用[2])使用哈希函和关键字类型的==运算符来组织元素。在关键字类型的元素没有明显序关系或维护序的代价高昂时,无序容器非常有用。包括: -`unordered_set`:哈希集合,关键字不可重复-`unordered_map`:哈希映射,每个key唯一(最常用,如引用[2]中的单词计例子) -`unordered_multiset`:关键字可重复的unordered_set-`unordered_multimap`:关键字可重复的unordered_map###关联容器的关键特性1.**关键字要求**:有序容器要求关键字类型必须定义比较运算符(或提供自定义比较函);无序容器要求关键字类型必须支持哈希函和==运算符。2.**元素访问**:map和unordered_map支持下标操作(通过key访问value),但注意:下标操作可能会插入新元素(如果key不存在)。可以使用`at()`成员函(引用[4])来访问元素,如果key不存在会抛出异常。 3. **元素查找**:使用`find()`成员函查找元素,返回迭代器;`count()`返回关键字出现的次(引用[4])。4.**范围查找**:有序关联容器支持`lower_bound()`、`upper_bound()`和`equal_range()`(引用[4]),用于在有序序列中定位元素范围。###示例代码引用[2]中的无序映射示例:```cppvoiduse_unorderd_map(){unordered_map<string,size_t> word_count;stringword;while(cin>> word) {word_count[word]++; //计}for(constauto &word :word_count) {cout<< "key is" <<word.first<< "value is" <<word.second<< endl;} }``` 引用[3]中的嵌套关联容器示例(map的value是vector):```cppmap<string,vector<pair<string,string>>>peo; //姓:名字和生日// ...输入据//遍历输出```###总结关联容器提供基于关键字的快速查找(对时间复杂度或平均常时间复杂度)。选择有序还是无序容器取决于是否需要对元素排序以及性能要求。---§§相关问题§§1.如何为自定义类型实现有序关联容器(如set)所需的比较函2.无序关联容器的哈希函如何自定义?3.关联容器的插入操作有哪些方法?它们有什么区别? 4.如何高效地遍历关联容器?</think>### C++ 关联容器详解 #### 1. 关联容器定义 关联容器C++ 标准库中的一类特殊容器,**通过关键字(key)而非位置索引来存储和访问元素**。与顺序容器(如 vector/list)不同,关联容器使用**高效的据结构(通常是红黑树或哈希表)** 组织元素,实现快速查找(平均 $O(log n)$ 或 $O(1)$ 时间复杂度)[^2][^3]。 核心特征: - 元素以键值对(key-value)形式存储 - 自动维护元素有序性(有序容器)或哈希组织(无序容器) - 提供基于关键字的快速查找、插入和删除操作 #### 2. 关联容器分类 **有序关联容器**(基于红黑树实现): | 容器类型 | 特性描述 | 头文件 | |----------------|-----------------------------|-------------| | `std::set` | 唯一关键字集合 | `<set>` | | `std::map` | 唯一键值对映射 | `<map>` | | `std::multiset`| 允许重复关键字集合 | `<set>` | | `std::multimap`| 允许重复键值对映射 | `<map>` | **无序关联容器**(基于哈希表实现): | 容器类型 | 特性描述 | 头文件 | |------------------------|-----------------------------|-----------------| | `std::unordered_set` | 唯一关键字哈希集合 | `<unordered_set>` | | `std::unordered_map` | 唯一键值对哈希映射 | `<unordered_map>` | | `std::unordered_multiset`| 允许重复关键字哈希集合 | `<unordered_set>` | | `std::unordered_multimap`| 允许重复键值对哈希映射 | `<unordered_map>` | #### 3. 关键特性对比 | 特性 | 有序容器 | 无序容器 | |----------------------|-----------------------------|-----------------------------| | **底层结构** | 红黑树 | 哈希表 | | **元素顺序** | 按关键字严格排序 | 无特定顺序 | | **查找复杂度** | $O(log n)$ | 平均 $O(1)$,最坏 $O(n)$ | | **自定义排序** | 支持比较函(如引用[1]) | 需自定义哈希函和相等比较 | | **典型应用** | 需要有序访问的场景 | 快速查找且无需排序的场景 | #### 4. 基本操作示例 **有序 map 使用**(引用[3]): ```cpp std::map<std::string, std::vector<std::pair<std::string, std::string>>> people; // 插入元素 people["Smith"].emplace_back("John", "1990-01-01"); people["Smith"].emplace_back("Anna", "1992-05-15"); // 遍历输出 for (const auto& family : people) { std::cout << family.first << ":\n"; for (const auto& member : family.second) { std::cout << " " << member.first << ", born: " << member.second << "\n"; } } ``` **无序 map 使用**(引用[2]): ```cpp std::unordered_map<std::string, size_t> word_count; // 计操作 word_count["apple"]++; word_count["banana"] = 5; // 范围查找(引用[4]) auto range = word_count.equal_range("apple"); if (range.first != range.second) { std::cout << "Count: " << range.first->second << "\n"; } ``` #### 5. 选择指南 - 需要 **元素有序遍历** 或 **范围查询**(如 `lower_bound()`)→ 选择有序容器[^4] - 追求 **最高查找性能** 且不关心顺序 → 选择无序容器[^2] - 处理 **重复关键字** → 选择 `multi` 版本容器 - 仅需 **检查存在性** → 选择 `set` 而非 `map` --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值