1 白话hash_set/unordered_set
  这一章节,我们来了解两个新的结构体hash_set和unorderd_set。我将这两者放在一个博文中介绍是因为它们都属于基于哈希表(hash table)构建的数据结构,并且是关键字与键值相等的关联容器。后面我们还会介绍到hash_map与unordered_map两种数据结构,这就好比是set与map的区别了,后面我们再说。
  说到这那到底hash_set与unordered_set哪个更好呢?实际上unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,一个是民间流传的。在编译器中,Visual Studio(当然需要支持C++11的版本)库中两个数据结构都有定义,而在gcc/g++中并不支持hash_set。总之,如果想使用这种基于哈希表的关联容器,那么就使用unordered_set就好了。下面我们也将会围绕无序集合容器(unordered_set)讲解,hash_set有对应的公共接口不再细说。
  此外我们来看看unordered_set/hash_set与set有什么区别。首先从内部构建来看,虽然都属于关键字与键值相等的关联容器,但是内部结构大大的不同。set的内部结构是基于红黑树来实现的,所以保证了一个稳定的动态操作时间,查询、插入、删除都是O(logN),最坏和平均都是。而unordered_map如前所述,是哈希表。顺便提一下,哈希表的查询时间虽然是O(1),但是并不是unordered_map查询时间一定比map短,因为实际情况中还要考虑到数据量,而且unordered_map的hash函数的构造速度也没那么快,所以不能一概而论,应该具体情况具体分析。
  第二点从存储方式来看,unordered_set也是一个存储唯一(unique,即无重复)的关联容器(Associative container),但是容器中的元素无特别的秩序关系,该容器允许基于值的快速元素检索,同时也支持正向迭代。在一个unordered_set内部,元素不会按任何顺序排序,而是通过元素值的hash值将元素分组放置到各个桶中,这样就能通过元素值快速访问各个对应的元素(均摊耗时为O(1))。
2 小谈哈希表
  hash_set/unordered_set是哈希表构建的,所以我们在介绍其方法接口前还是有进一步了解一下哈希表的原理。
  哈希表是根据关键码值而进行直接访问的数据结构,通过相应的哈希函数(也称散列函数)处理关键字得到相应的关键码值,关键码值对应着一个特定位置,用该位置来存取相应的信息,这样就能以较快的速度获取关键字的信息。
  比如:现有公司员工的个人信息(包括年龄),需要查询某个年龄的员工个数。由于人的年龄范围大约在[0,200],所以可以开一个200大小的数组,然后通过哈希函数得到key对应的key-value,这样就能完成统计某个年龄的员工个数。而在这个例子中,也存在这样一个问题,两个员工的年龄相同,但其他信息(如:名字、身份证)不同,通过前面说的哈希函数,会发现其都位于数组的相同位置,这里,就涉及到“冲突”。准确来说,冲突是不可避免的,而解决冲突的方法常见的有:开发地址法、再散列法、链地址法(也称拉链法)。而unordered_set内部解决冲突采用的是链地址法,当用冲突发生时把具有同一关键码的数据组成一个链表。下图展示了链地址法的使用:

这里写图片描述

3 unordered_set实战
 3.1 头文件

#include<unordered_set> // hash_set则是#iunclude<hash_set>

using namespace std;

 3.2 其他操作
  由于其常用方法接口和set几乎一样,我不在过多描述,下面只贴出程序样例,一些说明请阅读博文例说数据结构&STL(八)——set.

    unordered_set<int> set_fir; // 默认构造对象

    unordered_set<int> set_sed = { 2, 3, 10, 5, 9 }; //初始化构造

    set_sed.insert(7);          // 插入7,放置在set中位置跟hash构建有关,并不是在尾部

    unordered_set<int>::iterator iter1 = set_sed.lower_bound(2); //返回set中>=2的索引(迭代器),切记非小于2

    unordered_set<int>::iterator iter2 = set_sed.upper_bound(2); //返回set中>2的索引

    set_sed.erase(2); //删除set中元素2

    set_sed.erase(set_sed.begin(), set_sed.end()); //清空整个set

    if (set_sed.find(5) != set_sed.end()) // 查找键值为5的元素
        cout << "exsit" << endl;

    // 正向访问
    unordered_set<int>::iterator iter4;
    for (iter4 = set_sed.begin(); iter4 != set_sed.end(); iter4++)
        cout << *iter4 << endl;

    unordered_set<int>::reverse_iterator iter5; //对应反向迭代器对象
    // 反向访问
    for (iter5 = set_sed.rbegin(); iter5 != set_sed.rend(); iter5++)
        cout << *iter5 << endl;

    set_sed.count(12);     // 返回set中元素的个数,由于set的特殊性,所以结果只有0或者1

    set_sed.swap(set_fir); // 交换所有数据,需要确保set中元素类型相同

    set_sed.clear();       // 清空集合set_sed

    set_sed.size();        // 统计set_sed中元素个数

    set_sed.empty();       // 判断set中是否为空,空则返回1

4 小结
  上面介绍了无序集合容器数据结构特点以及公用的接口。由于集合是基于哈希表构建的数据结构,所以其查询的时间复杂度只有O(1),n为集合中元素的个数。
  以上是个人学习记录,由于能力和时间有限,如果有错误望读者纠正,谢谢!
  转载请注明出处:http://blog.csdn.net/FX677588/article/details/76400389


  参考文献:
  http://www.cnblogs.com/davidgu/p/4998083.html
  http://blog.csdn.net/vevenlcf/article/details/51743058
  http://blog.csdn.net/sdnu111111111/article/details/38658929

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐