首页 > 代码库 > C++ unordered Associative Containers(C++11)

C++ unordered Associative Containers(C++11)

C++11 引进了无序关联容器(unordered associative containers)的概念。 有unordered set or multiset, 以及unordered map or multimap。

顾名思义, unordered的意思就是元素没有固定的顺序, 并且元素的顺序可能会随着时间的变化而变化。

 

Internally, unordered container 是使用hash table(哈希表)实现的。 所谓的hash Table, 就是an array of linked list. 整个数组又被称为an array of buckets。

那些linked lsit 被称为entries。

每一个元素作为一个hash function的输入参数,  hash function 输出这个元素对应的hash value(哈希值)。 然后根据这个值将这个元素添加到对应的linked list 的合适的位置。

 

上述结构的优点就是, 当你有一个fast and effective function , finding an element in the hash table is very fast, 花费的时间是常数时间O(1). 这是所有的容器中最快的一种。  

 

例如下图, 创键了一个unordered set (无序集合)of string, 名字为myset(存储着red, green, blue);

然后使用set的成员函数find()去查找myset 中的green,  这一步仅仅需要花费常数时间(O(1))非常之快。 如果找到了这个元素, 该函数会返回指向这个元素的迭代器, 如果没有找到这个元素, 那么该函数会返回一个指向the end of the container 的迭代器, 所以我们需要Check 是否找到了。 所以如下验证。 如果返回的是一个未定义的, 说明指向容器末尾, 也就是没有找到;

然后我们想这个无序set 中插入一个yellow, 这个操作也仅仅需要花费O(1);

我们也可以实用另外一个容器作为insert 的参数, 例如向量容器vec, myset 调用insert成员函数的时候, 可以将这个容器的所有元素都插入到无序容器myset中, 非常有效。

 

无序集合也为我们提供了hash Table的API, 

load_factor() 是给出元素的总数与bucket的总数的比值。

函数bucket告诉我们我们的元素存储在那个bucket 之中。

bucket_count 告诉我们容器中有多少个bucket。

 

 

unordered multiset 允许我们的容器具有duplicated elements:

unodered map 非常想unordereded set, 区别就是unordered 的元素是有一组key 和value组成。

unordered multimap 允许duplicated keys。

我们需要记住的一点就是, 尽管hash Table 允许amortized constant time search, 但是这个性能会随着hash collision(哈希碰撞)而退化(降低)。

Q: 什么是hash collision:

A: 许多的元素被插入到同一个bucket中。

 

 

 

最坏的情况下, 当所有的元素被插入到一个bucket 的时候, 性能最差, 此时搜索所需要的时间变为了O(n):

 

 

无序容器的属性如下:

 

注意一点, 对于unordered set/multiset, 元素的值是不能够改变的, 因为改变值的时候, 将会改变其的data structures。也就是改变了hash table。

对于unordered map/multimap, 元素的key 也是不能随便改变的。 原因同上, 即会改变我们的hash table。

 

 

尽管STL中, 没有容器被称为关联数组(associative array), 但是我们仍然可以通过map 和unordered map 去实现它。

例如下面, 我们创建了一个unordereded map称为day, 存储的内容如下。

接下来, 存取相关的值就相当于存取数组的元素一样(通过索引, 只不过这里是key)。 注意用[] 存取无range Check, 用成员函数at 有range check。

 

需要注意的一点就是, 如果是向量, 如下, 使用vec[5]就会出错, 因为vec[5]并没有被创建(vec 只有三个元素)。

然而, 对于关联容器, 指令day[‘W ‘ ] = "Wednesday"会将{“W”, “Wednesday”}插入到关联容器中。 当然我们也可以调用insert函数。

然而, insert函数不能用于修改已经存在的元素值。 然而下标法却可以实现修改。 因为此时将其视为关联数组。

 

 

 

 

对于如下函数:

参数是pass by reference(const)。

在函数中, 我们修改了m, 所以下面的函数也不会通过编译。 因为m is not modifiable。

 

 

 

在看下面的函数, 假如我们只是打印出来, 那么会打印出来吗?

正确的答案是下面也不会通过编译, 出现的错误信息和上面的出现的错误信息是一样的。  原因是什么呢?

因为下标法[] 对我们的容器的操作是写操作。 所以当编译器看到m[‘S‘], 会assume you will write to the conatiners

 

所以, 为了实现读操作, 我们采用如下方式:

 

关于associative array, 我们有一下几点需要注意:

既然associative array 既可以用unordered map实现, 有可以用map 实现, 那么我们该使用哪一个呢?

不难看出, 对于搜索操作, unordered map 需要O(1)时间。 对于map, 需要花费O(log(n)), 表面上看, 似乎unordered map 占优。 但是我们应该知道map的时间是guaranteed, 而unordered map 当退化的时候, 花费的时间变成了线性的了。

 

还有一点就是, 我们不能使用multimap 和 unordered multimap去实现关联数组, 因为这两个容器的key 不是unique 的。而且二者还没有[]运算符。

 

 

STL也提供了三个容器适配器, 如下:

 

 

关于容器, 还有另外的一个分类方法:

上面的程序中, 输出的结果是未定义的。 每当插入或者删除基于数组的容器的元素, 以前指向某个元素的指针的可能无效了(invalidate)。