Unverified Commit c107d4a1 authored by Xeonacid's avatar Xeonacid Committed by GitHub
Browse files

Merge pull request #2604 from OI-wiki/1843-fix

refactor associative-container.md
parents ee148c1b 2398501c
Loading
Loading
Loading
Loading
+51 −81
Original line number Diff line number Diff line
@@ -2,12 +2,12 @@

 `set` 是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。 `set` 内部通常采用红黑树实现。

和数学中的集合相似, `set` 中不会出现值相同的元素。
和数学中的集合相似, `set` 中不会出现值相同的元素。如果需要有相同元素的集合,需要使用 `multiset``multiset` 的使用方法与 `set` 的使用方法基本相同。

### 插入与删除操作

-  `insert(x)` 当容器中没有等价元素的时候,将元素 x 插入到 `set` 中。
-  `erase(x)` 删除值为 x 的元素,返回删除元素的个数。
-  `erase(x)` 删除值为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 清空 `set`
@@ -15,7 +15,7 @@
???+note "insert 函数的返回值"
    insert 函数的返回值类型为 `pair<iterator, bool>` ,其中 iterator 是一个指向所插入元素(或者是指向等于所插入值的原本就在容器中的元素)的迭代器,而 bool 则代表元素是否插入成功,由于 `set` 中的元素具有唯一性质,所以如果在 `set` 中已有等值元素,则插入会失败,返回 false,否则插入成功,返回 true; `map` 中的 insert 也是如此。

### 迭代器
### 迭代器<span id="set-iterator"></span>

 `set` 提供了以下几种迭代器:

@@ -30,7 +30,7 @@

以上列出的迭代器中,含有字符 `c` 的为只读迭代器,你不能通过只读迭代器去修改 `set` 中的元素的值。如果一个 `set` 本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。

### 查找操作
### 查找操作<span id="set-find"></span>

-  `count(x)` 返回 `set` 内键为 x 的元素数量。
-  `find(x)``set` 内存在键为 x 的元素时会返回该元素的迭代器,否则返回 `end()`
@@ -44,34 +44,11 @@
    
    但使用 `algorithm` 库中的 `lower_bound``upper_bound` 函数对 `set` 中的元素进行查询,时间复杂度为 $O(n)$ 。

##  `multiset` 

 `multiset` 是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。

与 `set` 不同的是, `multiset` 允许不同元素间拥有相同的值。

### 插入与删除操作

-  `insert(x)` 将元素 x 插入到 `multiset` 中。
-  `erase(x)` 删除值为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 清空 `multiset` 。

### 迭代器

 `multiset` 的迭代器和 `set` 的 [迭代器](#_2) 类似,这里不再赘述。

### 查找操作

 `multiset` 的查找操作和 `set`[查找操作](#_3) 类似,这里不再赘述。

##  `map` 

 `map` 是有序键值对(Attribute–value pair)容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。 `map` 通常实现为红黑树。
 `map` 是有序键值对容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。 `map` 通常实现为红黑树。

你可能需要存储一些键值对,例如存储学生姓名对应的分数: `Tom 0``Bob 100``Alan 100`
但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 `map` 了!
你可能需要存储一些键值对,例如存储学生姓名对应的分数: `Tom 0``Bob 100``Alan 100` 。但是由于数组下标只能为非负整数,所以无法用姓名作为下标来存储,这个时候最简单的办法就是使用 STL 中的 `map` 了!

 `map` 重载了 `operator[]` ,可以用任意定义了 `operator <` 的类型作为下标(在 `map` 中叫做 `key` ,也就是索引):

@@ -85,77 +62,70 @@ map<Key, T> yourMap;
map<string, int> mp;
```

### 添加元素
 `map` 中不会存在键相同的元素, `multimap` 中允许多个元素拥有同一键。 `multimap` 的使用方法与 `map` 的使用方法基本相同。

1. 直接赋值,例如 `mp["Tom"]=0` 
2. 通过插入一个类型为 `pair<Key, T>` 的值,例如 `mp.insert(pair<string,int>("Alan",100));` 
3. 使用 `initializer_list`
!!! warning
    正是因为 `multimap` 允许多个元素拥有同一键的特点, `multimap` 并没有提供给出键访问其对应值的方法。

```cpp
map<string, int> mp = {{"Tom", 0}, {"Bob", "100"}, {"Alan", 100}};
```
### 插入与删除操作<span id="map-insert"></span>

### 查找、修改元素
- 可以直接通过下标访问来进行查询或插入操作。例如 `mp["Alan"]=100`
- 通过向 `map` 中插入一个类型为 `pair<Key, T>` 的值可以达到插入元素的目的,例如 `mp.insert(pair<string,int>("Alan",100));`
-  `erase(key)` 函数会删除键为 `key`**所有** 元素。返回值为删除元素的数量。
-  `erase(pos)` : 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` : 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` 函数会清空整个容器。

1. 使用赋值语法: `int grade=mp["Tom"]`
2. 使用成员函数 `iterator find( const Key& key );` 来确定一个索引是否在 `map` 中。它会返回指向该元素的迭代器;如果索引不在 `map` 中,则会返回尾后迭代器 `mp.end()`
3. 如果你想获得 `map` 里全部的元素,请使用迭代器,解引用迭代器会得到一个类型为 `pair<Key, T>` 的值:
???+note "下标访问中的注意事项"
    在利用下标访问 `map` 中的某个元素时,如果 `map` 中不存在相应键的元素,会自动在 `map` 中插入一个新元素,并将其值设置为默认值(对于整数,值为零;对于有默认构造函数的类型,会调用默认构造函数进行初始化)。
    
```cpp
for (iter = mp.begin(); iter != mp.end(); ++iter)
  cout << iter->first << " " << iter->second << endl;
```
    当下标访问操作过于频繁时,容器中会出现大量无意义元素,影响 `map` 的效率。因此一般情况下推荐使用 `find()` 函数来寻找特定键的元素。

其中使用 `mp.begin()` 可以得到指向 `map` 首元素的迭代器。
如果使用 C++11(及以上),还可以使用 C++11 的范围 for 循环
### 查询操作

```cpp
for (auto &i : mp) {
  printf("Key : %d, Value : %d\n", i.first, i.second);
}
```

使用迭代器遍历大小为 $n$ 的 `map` 的时间复杂度是 $O(n)$ 。
-  `count(x)` : 返回容器内键为 x 的元素数量。复杂度为 $O(\log(size)+ans)$ (关于容器大小对数复杂度,加上匹配个数)。
-  `find(x)` : 若容器内存在键为 x 的元素,会返回该元素的迭代器;否则返回 `end()` 。
-  `lower_bound(x)` : 返回指向首个不小于给定键的元素的迭代器。
-  `upper_bound(x)` : 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回 `end()` 。
-  `empty()` : 返回容器是否为空。
-  `size()` : 返回容器内元素个数。

### 删除元素
## 遍历容器

如果你想删除 `Tom` 这个元素,则可以利用 `find` 函数找到 `Tom` ,然后再 `erase` 如下
可以利用迭代器来遍历关联式容器的所有元素。

```cpp
map<string, int>::iterator it;
it = mp.find("Tom");
mp.erase(it)
set<int> s;
typedef set<int>::iterator si;
for (si it = s.begin(); it != s.end(); it++) cout << *it << endl;
```

如果你想清空所有的元素,可以直接 `mp.clear()` 
需要注意的是,对 `map` 的迭代器解引用后,得到的是类型为 `pair<Key, T>` 的键值对。

### 其他函数
在 C++11 中,使用范围 for 循环会让代码简洁很多:

-  `count` 返回匹配特定键的元素出现的次数,例如 `mp.count("Tom")`
-  `swap` 可以交换两个 `map` ,例如 `swap(m1,m2)`
-  `size` 返回 `map` 中元素的个数。
-  `empty` 如果 `map` 为空则返回 `true` ,例如 `mp.empty()`
```cpp
set<int> s;
for (auto x : s) cout << x << endl;
```

##  `multimap` 
对于任意关联式容器,使用迭代器遍历容器的时间复杂度均为 $O(n)$ 。

`map` 类似, `multimap` 是有序键值对容器,但允许多个元素拥有同一键。其搜索、插入操作拥有对数复杂度。删除单个元素在最坏情况下具有对数复杂度,均摊为常数复杂度。
## 自定义比较方式

!!! warning
    正是因为 multimap 允许多个元素拥有同一键的特点,multimap 并没有提供给出键访问其对应值的方法。
 `set` 在默认情况下的比较函数为 `<` (如果是非内置类型需要 [重载 `<` 运算符](../op-overload.md#compare) )。然而在某些特殊情况下,我们希望能自定义 `set` 内部的比较方式。

### 插入与删除操作
这时候可以通过传入自定义比较器来解决问题。

- 通过向 `multimap` 中插入一个类型为 `pair<Key, T>` 的值可以达到插入元素的目的,例如 `mp.insert(pair<string,int>("Alan",100));`
-  `erase(x)` : 删除键为 x 的 **所有** 元素,返回删除元素的个数。
-  `erase(pos)` : 删除迭代器为 pos 的元素,要求迭代器必须合法。
-  `erase(first,last)` : 删除迭代器在 $[first,last)$ 范围内的所有元素。
-  `clear()` : 清空容器。
具体来说,我们需要定义一个类,并在这个类中 [重载 `()` 运算符](../op-overload.md#function)

### 查找操作
例如,我们想要维护一个存储整数,且较大值靠前的 `set` ,可以这样实现:

-  `count(x)` : 返回容器内键为 x 的元素数量。复杂度为 $O(\log(size)+ans)$ (关于容器大小对数复杂度,加上匹配个数)。
-  `find(x)` : 若容器内存在键为 x 的元素,会返回该元素的迭代器(如果有多个键为 x 的元素会返回任意一个);否则返回 `end()`
-  `lower_bound(x)` : 返回指向首个不小于给定键的元素的迭代器。
-  `upper_bound(x)` : 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回 `end()`
-  `empty()` : 返回容器是否为空。
-  `size()` : 返回容器内元素个数。
```cpp
struct cmp {
  bool operator()(int a, int b) { return a > b; }
};
set<int, cmp> s;
```

对于其他关联式容器,可以用类似的方式实现自定义比较,这里不再赘述。