C++容器之基于哈希表实现的unordered_set

1. 基本概念

unordered_set是无序数据集,也就是没有以特别的顺序存储数据。这个是基于哈希表实现的数据容器。哈希表本质上是一个数组,与常见的数组不同的是,哈希表中存放的值是键值对。键值对就是可以根据一个键值获取对应的一个值。而对于键值,百度百科的解释是“键值(key)是windows中注册表中的概念。键值位于注册表结构链末端,和文件系统的文件类似,包含当前计算机及应用程序执行时使用的实际配置信息和数据。键值包含几种数据类型,以适应不同环境的使用需求。”通过一个键值获取对应的一个值,这个有点类似高等数学中的映射。一个简单的例子在这里说明这个概念。

假设:有一本中文词典,里面包含了所有的汉字,但是这些汉字是按任意顺序随意排版的,那么想要在其中找到某一个汉字,你就需要从头至尾一个一个核查,如果运气差,这个汉字正好在词典的末尾,那你需要遍历整本词典才能找到你要查的汉字。

优化:因为汉字和拼音之间存在着一种确定的关系,为了提高查找速度,现在将所有汉字按照拼音(key)进行排序(拼音可以根据首字母,第二个字母依次进一步排序),并且每个拼音都有一个对应页码(index),从该页开始,存放拼音对应的汉字(value)。所以找到拼音,也就能在对应的页码找到对应的汉字。其中,拼音和页码之间,有着某种固定的映射关系,可以通过某种方式计算出来(hash function)。
从上面的例子可以看出这个容器在数据查找、容器遍历方面具有相当优势,所以对于查找问题可以考虑使用该容器。另外,这个容器存储的数据是唯一的,这个特性可以用于快速检查某段数据序列是否存在重复值

2. 用法

  1. 定义和初始化
// constructing unordered_sets
#include <iostream>
#include <string>
#include <unordered_set>

template<class T>
T cmerge (T a, T b) {
    
     T t(a); t.insert(b.begin(),b.end()); return t; }

int main ()
{
    
    
  std::unordered_set<std::string> first;                                // empty
  std::unordered_set<std::string> second ( {
    
    "red","green","blue"} );    // init list
  std::unordered_set<std::string> third ( {
    
    "orange","pink","yellow"} ); // init list
  std::unordered_set<std::string> fourth ( second );                    // copy
  std::unordered_set<std::string> fifth ( cmerge(third,fourth) );       // move
  std::unordered_set<std::string> sixth ( fifth.begin(), fifth.end() ); // range

  std::cout << "sixth contains:";
  for (const std::string& x: sixth) std::cout << " " << x;
  std::cout << std::endl;

  return 0;

输出:

sixth contains: pink yellow red green orange blue
  1. 成员方法
    (1)begin():返回指向第一元素的迭代器(迭代器(iterable)是一个超级接口! 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦)。
    原型:
<1> container iterator (1)	
      iterator begin() noexcept;
      const_iterator begin() const noexcept;
<2> bucket iterator (2)	
      local_iterator begin ( size_type n );
      const_local_iterator begin ( size_type n ) const;

根据函数原型,可以知道begin可以返回两种迭代器类型,其中iterator可以改变所指向元素的值,而const_iterator不可改,只可更改其指向其他元素。也就是const_iterator可以修改自身的指向,但不可以修改所指向的位置的值。
(2)end():返回指向最后一个元素的迭代器。
原型:

<1> container iterator (1)	
      iterator end() noexcept;
      const_iterator end() const noexcept;
<2> bucket iterator (2)	
      local_iterator end (size_type n);
      const_local_iterator end (size_type n) const;

begin和end的使用例子

// unordered_set::begin/end example
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset =
  {
    
    "Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune"};

  std::cout << "myset contains:";
  for ( auto it = myset.begin(); it != myset.end(); ++it )
    std::cout << " " << *it;
  std::cout << std::endl;

  std::cout << "myset's buckets contain:\n";
  for ( unsigned i = 0; i < myset.bucket_count(); ++i) {
    
    
    std::cout << "bucket #" << i << " contains:";
    for ( auto local_it = myset.begin(i); local_it!= myset.end(i); ++local_it )
      std::cout << " " << *local_it;
    std::cout << std::endl;
  }

  return 0;
}

输出

myset contains: Venus Jupiter Neptune Mercury Earth Uranus Saturn Mars
myset's buckets contain:
bucket #0 contains:
bucket #1 contains: Venus
bucket #2 contains: Jupiter
bucket #3 contains: 
bucket #4 contains: Neptune Mercury
bucket #5 contains: 
bucket #6 contains: Earth
bucket #7 contains: Uranus Saturn
bucket #8 contains: Mars
bucket #9 contains: 
bucket #10 contains: 

(3) bucket(const key_type& k):返回元素值为k的桶号(在一个unordered_set内部,元素不会按任何顺序排序,而是通过元素值的hash值将元素分组放置到各个槽(Bucker,也可以译为“桶”),这样就能通过元素值快速访问各个对应的元素(均摊耗时为O(1))。类似通过字典的拼音查找某个汉字
原型:

size_type bucket ( const key_type& k ) const;

使用例子

// unordered_set::bucket
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset = {
    
    "water","sand","ice","foam"};

  for (const std::string& x: myset) {
    
    
    std::cout << x << " is in bucket #" << myset.bucket(x) << std::endl;
  }

  return 0;
}

输出:

ice is in bucket #0
foam is in bucket #2
sand is in bucket #2
water is in bucket #4

(4) bucket_count(): 返回容器中桶的数量
方法原型:

扫描二维码关注公众号,回复: 15212747 查看本文章
size_type bucket_count() const noexcept;

使用例子

// unordered_set::bucket_count
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset =
  {
    
    "Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune"};

  unsigned n = myset.bucket_count();

  std::cout << "myset has " << n << " buckets.\n";

  for (unsigned i=0; i<n; ++i) {
    
    
    std::cout << "bucket #" << i << " contains:";
    for (auto it = myset.begin(i); it!=myset.end(i); ++it)
      std::cout << " " << *it;
    std::cout << "\n";
  }

  return 0;
}

输出

myset has 11 buckets.
bucket #0 contains: 
bucket #1 contains: Venus
bucket #2 contains: Jupiter
bucket #3 contains: 
bucket #4 contains: Neptune Mercury
bucket #5 contains: 
bucket #6 contains: Earth
bucket #7 contains: Uranus Saturn
bucket #8 contains: Mars
bucket #9 contains: 
bucket #10 contains: 

(5)cbegin()和cend(),这两者的功能和begin()、cend()相同,只不过返回类型不一样,cbegin和cend返回的都是const_iterator。
方法原型:

container iterator (1)	
          const_iterator cbegin() const noexcept;
bucket iterator (2)	
          const_local_iterator cbegin ( size_type n ) const;
container iterator (1)	
          const_iterator cend() const noexcept;
bucket iterator (2)	
          const_local_iterator cend ( size_type n ) const;

这里有两种返回类型,一种是常见的迭代器类型(const_iterator),而另一种是const_local_iterator类型。顾名思义,局部的迭代器,也就是回去当前这个桶的迭代器。使用的例子如下:

// unordered_set::cbegin/cend example
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset =
  {
    
    "Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune"};

  std::cout << "myset contains:";
  for ( auto it = myset.cbegin(); it != myset.cend(); ++it )//这里的it是const_iterator类型
    std::cout << " " << *it;    // cannot modify *it
  std::cout << std::endl;

  std::cout << "myset's buckets contain:\n";
  for ( unsigned i = 0; i < myset.bucket_count(); ++i) {
    
    
    std::cout << "bucket #" << i << " contains:";
    for ( auto local_it = myset.cbegin(i); local_it!= myset.cend(i); ++local_it )//这里的local_it就是const_local_iterator类型
      std::cout << " " << *local_it;
    std::cout << std::endl;
  }

  return 0;
}

输出

myset contains: Venus Jupiter Neptune Mercury Earth Uranus Saturn Mars
myset's buckets contain:
bucket #0 contains:
bucket #1 contains: Venus
bucket #2 contains: Jupiter
bucket #3 contains: 
bucket #4 contains: Neptune Mercury
bucket #5 contains: 
bucket #6 contains: Earth
bucket #7 contains: Uranus Saturn
bucket #8 contains: Mars
bucket #9 contains: 
bucket #10 contains: 

(6) clear() :清除容器中数据。这个方法会调用容器的析构方法~unorder_set。需要注意的是使用clear( )并不会清空内存,只是把容器中存储的数据清除掉,也就是在使用clear()后,容器内的元素个数为0,所申请的内存并没有释放掉。那如何释放该内存呢?BOOM朝朝朝博主总结了三种容器释放内存的方法,这里直接贴出来:
1.方法一:直接声明同一个匿名容器类型与原有容器交换,匿名容器会自动销毁;

vector( ).swap(num);

2.方法二:先声明一个临时对象,然后与目标容器交换数据:

vector temp; 
(temp).swap(num); 

临时对象未被初始化,其缓冲区大小为0,没有数据,与目标对象交换数据,则容器num中的缓冲区就没有了;

3.方法三:先将目标容器的内存清空,再利用swap函数与原有容器进行交换,即:

num.clear( ); vector(num).swap(num);

方法原型:

void clear() noexcept;

noexcept,它有两类作用:noexcept 指定符和noexcept 运算符。其中指定符是指定函数是否抛出异常,而运算符是进行编译时检查,若表达式声明为不抛出任何异常则返回true。
使用例子

// clearing unordered_set
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset =
    {
    
     "chair", "table", "lamp", "sofa" };

  std::cout << "myset contains:";
  for (const std::string& x: myset) std::cout << " " << x;
  std::cout << std::endl;

  myset.clear();
  myset.insert("bed");
  myset.insert("wardrobe");
  myset.insert("nightstand");

  std::cout << "myset contains:";
  for (const std::string& x: myset) std::cout << " " << x;
  std::cout << std::endl;

  return 0;
}

输出

myset contains: sofa lamp table chair
myset contains: nightstand wardrobe bed

(7)count(const key_type& k): 统计容器中元素值为k的数量。由于unordered_set存储的元素是唯一的,所以这个方法只会返回0或1。
方法原型:

size_type count ( const key_type& k ) const;

使用例子

// unordered_set::count
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset = {
    
     "hat", "umbrella", "suit" };

  for (auto& x: {
    
    "hat","sunglasses","suit","t-shirt"}) {
    
    
    if (myset.count(x)>0)
      std::cout << "myset has " << x << std::endl;
    else
      std::cout << "myset has no " << x << std::endl;
  }

  return 0;
}

输出

myset has hat
myset has no sunglasses
myset has suit
myset has no t-shirt

(8)emplance(Args&&… args):当容器中没有args元素时,向容器中插入数据args并且返回元素的迭代器和一个True变量。如果容器中已有该元素将返回该元素的迭代器和一个False变量。
方法原型:

template <class... Args>
pair <iterator,bool> emplace ( Args&&... args );

使用例子

// unordered_set::emplace
#include <iostream>
#include <string>
#include <unordered_set>

int main ()
{
    
    
  std::unordered_set<std::string> myset;

  myset.emplace ("potatoes");
  myset.emplace ("milk");
  myset.emplace ("flour");

  std::cout << "myset contains:";
  for (const std::string& x: myset) std::cout << " " << x;

  std::cout << std::endl;
  return 0;
}

输出

myset contains: potatoes flour milk

猜你喜欢

转载自blog.csdn.net/yyl80/article/details/123860099