C++STL——哈希(中)

简介: C++STL——哈希(中)

开散列——哈希桶

这里就是数组是要给指针数组,数组里面存放单链表的地址,将冲突的值放在单链表里面就可以了。

但是如果某一个位置冲突很多,挂了很长,那么效率也会下降。

不过这里下面不一定能非要挂单链表,也可以挂红黑树等等。

哈希桶的实现

首先表中类型需要更改,并且负载因子等于1才会进行扩容。

如果当前位置没有任何值就是空,如果有就挂链表。

每一次链表插入的时候需要进行头插,这样更方便,不需要进行排序,因为不知道要先找谁。

#include<iostream>
#include<cassert>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc
{
  size_t operator()(const K& key)
  {
    return (size_t)key;
  }
};
template<>
struct HashFunc<string>//HashFunc特化,如果是string类型的就走这里
{
  size_t operator()(const string& key)
  {
    size_t sum = 0;
    size_t count = 0;
    for (auto e : key)
    {
      ++count;
      sum += e;
    }
    return sum;
  }
};
template<class K, class V>
struct HashNode
{
  pair<K, V> _kv;
  HashNode<K ,V>* next;
  HashNode(const pair<K, V>& kv)
    :_kv(kv)
    ,next(nullptr)
  { }
};
template<class K, class V, class Hashc = HashFunc<K>>
class Hashbucket
{
  typedef HashNode<K, V> data;
public:
  Hashbucket()
  {
    _hash.resize(10, nullptr);
  }
  ~Hashbucket()
  {
    for (int i = 0; i < _hash.size(); i++)
    {
      data* old = _hash[i];
      while (old)
      {
        data* p = old -> next;
        delete old;
        old = p;
      }
      _hash[i] = nullptr;
    }
  }
  bool Insert(const pair<K, V>& kv)
  {
    if (find(kv.first))
      return false;
    Hashc hs;
    if (_hash.size() == _n)//扩容
    {
      vector<data*> new_hash;//这里必须创建Hashbucket中的vector<data*>,如果直接用Hashbucket类型会导致析构出问题
      new_hash.resize(2 * _hash.size(), nullptr);
      for (size_t i = 0; i < _hash.size(); i++)
      {
        data* cur = _hash[i];
        while (cur)
        {
          data* old = cur->next;
          size_t hashi = hs(cur->_kv.first) % new_hash.size();//计算重新插入的位置
          cur->next = new_hash[hashi];//头插
          new_hash[hashi] = cur;
          cur = old;
        }
        _hash[i] = nullptr;
      }
      _hash.swap(new_hash);
    }
    size_t hashi = hs(kv.first) % _hash.size();
    data* newnode = new data(kv);
    newnode->next = _hash[hashi];
    _hash[hashi] = newnode;
    ++_n;
    return true;
  }
  data* find(const K& key)
  {
    Hashc hs;
    size_t hashi = hs(key) % _hash.size();
    data* p = _hash[hashi];
    while (p)
    {
      if (p->_kv.first == key)
      {
        return p;
      }
      else
      {
        p = p -> next;
      }
    }
    return nullptr;
  }
  bool Erase(const K& key)
  {
    Hashc hs;
    size_t hashi = hs(key) % _hash.size();
    data* cur = _hash[hashi];
    data* old = nullptr;//指向cur的前一个结点
    while (cur)
    {
      if (cur->_kv.first == key)
      {
        if (cur == _hash[hashi])//这里说明删除的是第一个结点
        {
          _hash[hashi] = cur->next;
        }
        else
        {
          old->next = cur->next;
        }
        _n--;
        delete cur;
        return true;
      }
      else
      {
        old = cur;
        cur = cur->next;
      }
    }
    return false;
  }
private:
  vector<data*> _hash;
  size_t _n = 0;
};

模拟实现unordered_set与unordered_map

这里有一种说法,说哈希表的大小保持一个素数的大小更好。

那么看源码是怎么处理的:

这里给了一个素数表。

将素数表放进上面实现哈希桶用的代码里面,我们只需要封装成一个函数就可以了。

参数选择传当前哈希表的大小。

然后通过这个参数来比较是否需要扩容即可。

返回值也是返回容量大小,如果是表中最后一个值还要继续扩容,那么这里就返回最后一个值,不进行扩容,因为内存已经很大了。

unsigned long Prime_list(unsigned long n)//素数表
  {
    static const int __stl_num_primes = 28;
    static const unsigned long __stl_prime_list[__stl_num_primes] =
    {
      53,         97,         193,       389,       769,
      1543,       3079,       6151,      12289,     24593,
      49157,      98317,      196613,    393241,    786433,
      1572869,    3145739,    6291469,   12582917,  25165843,
      50331653,   100663319,  201326611, 402653189, 805306457,
      1610612741, 3221225473, 4294967291
    };
    for (int i = 0; i < __stl_num_primes; i++)
    {
      if (n < __stl_prime_list[i])
      {
        return __stl_prime_list[i];
      }
    }
    return __stl_prime_list[__stl_num_primes - 1];
  }

然后接下来进行对哈希桶改造与迭代器的封装:

首先考虑以下迭代器如何遍历整个哈希桶。

从第一个有结点的地方开始遍历,走到空之后去找表中的下一个有结点的地方再从头开始走,以此类推,遍历完整个表就可以了。

#include<iostream>
#include<cassert>
#include<vector>
#include<string>
using namespace std;
template<class K>
struct HashFunc
{
  size_t operator()(const K& key)
  {
    return (size_t)key;
  }
};
template<>
struct HashFunc<string>//HashFunc特化,如果是string类型的就走这里
{
  size_t operator()(const string& key)
  {
    size_t hash = 0;
    for (auto ch : key)
    {
      hash *= 131;
      hash += ch;
    }
    return hash;
  }
};
//前置声明,不然迭代器不知道有哈希桶这个类
template<class K, class T, class KeyOFT, class Hashc>
class Hashbucket;
template<class T>
struct HashNode
{
  T _data;
  HashNode<T>* next;
  HashNode(const T& data)
    :_data(data)
    , next(nullptr)
  { }
};
template<class K, class T, class KeyOFT, class Hashc>
struct HIterator
{
  typedef HashNode<T> Node;
  typedef HIterator<K, T, KeyOFT, Hashc> Self;
  typedef Hashbucket<K, T, KeyOFT, Hashc> Hashbucket;
  Node* _node;
  Hashbucket* _hs;
  HIterator(Node* node, Hashbucket* hs)
    :_node(node)
    ,_hs(hs)
  {}
  T& operator*()
  {
    return _node->_data;
  }
  T* operator->()
  {
    return &_node->_data;
  }
  bool operator!=(const Self& s)const
  {
    return _node != s._node;
  }
  Self operator++()
  {
    if (_node->next)
    {
      _node = _node->next;
    }
    else
    {
      KeyOFT kot;
      Hashc ht;
      size_t hashi = ht(kot(_node->_data)) % _hs->_hash.size();
      hashi++;
      while (hashi < _hs->_hash.size())//找表中下一个不为空的结点
      {
        if (_hs->_hash[hashi])
        {
          _node = _hs->_hash[hashi];
          break;
        }
        else
        {
          hashi++;
        }
      }
      if (hashi == _hs->_hash.size())//如果走到末尾了就说明结束了
        _node = nullptr;
    }
    return *this;
  }
};
template<class K, class T, class KeyOFT, class Hashc>//这里的取整数仿函数不在这里放缺省值
class Hashbucket
{
  template<class K, class T, class KeyOFT, class Hashc>
  friend struct HIterator;//这里将迭代器设置友元,因为迭代器内部需要调用该类的私有成员
  typedef HashNode<T> Data;
public:
  typedef HIterator<K, T, KeyOFT, Hashc> Iterator;
  Iterator begin()
  {
    for (size_t i = 0; i < _hash.size(); i++)
    {
      if (_hash[i])
      {
        return Iterator(_hash[i], this);
      }
    }
    return Iterator(nullptr, this);
  }
  Iterator end()
  {
    return Iterator(nullptr, this);
  }
  Hashbucket()
  {
    _hash.resize(10, nullptr);
  }
  ~Hashbucket()
  {
    for (int i = 0; i < _hash.size(); i++)
    {
      Data* old = _hash[i];
      while (old)
      {
        Data* p = old->next;
        delete old;
        old = p;
      }
      _hash[i] = nullptr;
    }
  }
  pair<Iterator, bool> Insert(const T& data)
  {
    KeyOFT kot;
    Iterator it = find(kot(data));
    if (it != end())
      return make_pair(it, false);
    Hashc hs;
    if (_hash.size() == _n)//扩容
    {
      vector<Data*> new_hash;//这里必须创建Hashbucket中的vector<data*>,如果直接用Hashbucket类型会导致析构出问题
      new_hash.resize(2 * _hash.size(), nullptr);
      for (size_t i = 0; i < _hash.size(); i++)
      {
        Data* cur = _hash[i];
        while (cur)
        {
          Data* old = cur->next;
          size_t hashi = hs(kot(cur->_data)) % new_hash.size();//计算重新插入的位置
          cur->next = new_hash[hashi];//头插
          new_hash[hashi] = cur;
          cur = old;
        }
        _hash[i] = nullptr;
      }
      _hash.swap(new_hash);
    }
    size_t hashi = hs(kot(data)) % _hash.size();
    Data* newnode = new Data(data);
    newnode->next = _hash[hashi];
    _hash[hashi] = newnode;
    ++_n;
    return make_pair(Iterator(newnode, this), true);
  }
  Iterator find(const K& key)
  {
    KeyOFT kot;
    Hashc hs;
    size_t hashi = hs(key) % _hash.size();
    Data* p = _hash[hashi];
    while (p)
    {
      if (kot(p->_data) == key)
      {
        return Iterator(p, this);
      }
      else
      {
        p = p->next;
      }
    }
    return Iterator(nullptr, this);
  }
  bool Erase(const K& key)
  {
    Hashc hs;
    KeyOFT kot;
    size_t hashi = hs(key) % _hash.size();
    Data* cur = _hash[hashi];
    Data* old = nullptr;//指向cur的前一个结点
    while (cur)
    {
      if (kot(cur->_data) == key)
      {
        if (cur == _hash[hashi])//这里说明删除的是第一个结点
        {
          _hash[hashi] = cur->next;
        }
        else
        {
          old->next = cur->next;
        }
        _n--;
        delete cur;
        return true;
      }
      else
      {
        old = cur;
        cur = cur->next;
      }
    }
    return false;
  }
  unsigned long Prime_list(unsigned long n)//素数表
  {
    static const int __stl_num_primes = 28;
    static const unsigned long __stl_prime_list[__stl_num_primes] =
    {
      53,         97,         193,       389,       769,
      1543,       3079,       6151,      12289,     24593,
      49157,      98317,      196613,    393241,    786433,
      1572869,    3145739,    6291469,   12582917,  25165843,
      50331653,   100663319,  201326611, 402653189, 805306457,
      1610612741, 3221225473, 4294967291
    };
    for (int i = 0; i < __stl_num_primes; i++)
    {
      if (n < __stl_prime_list[i])
      {
        return __stl_prime_list[i];
      }
    }
    return __stl_prime_list[__stl_num_primes - 1];
  }
private:
  vector<Data*> _hash;
  size_t _n = 0;
};
#include "uset与umap.h"
template<class K, class Hash = HashFunc<K>>
class uset
{
  struct Setkot
  {
    const K& operator()(const K& key)
    {
      return key;
    }
  };
public:
  typedef typename Hashbucket<K, K, Setkot, Hash>::Iterator Iterator;
  Iterator begin()
  {
    return _set.begin();
  }
  Iterator end()
  {
    return _set.end();
  }
  pair<Iterator, bool> Insert(const K& key)
  {
    return _set.Insert(key);
  }
  Iterator Find(const K& key)
  {
    return _set.find(key);
  }
  bool Erase(const K& key)
  {
    return _set.Erase(key);
  }
private:
  Hashbucket<K, K, Setkot, Hash> _set;
};
#include "uset与umap.h"
template<class K, class V, class Hash = HashFunc<K>>
class umap
{
  struct Mapkot
  {
    const K& operator()(const pair<const K, V>& kv)
    {
      return kv.first;
    }
  };
public:
  typedef typename Hashbucket<K, pair<K, V>, Mapkot, Hash>::Iterator Iterator;
  Iterator begin()
  {
    return _map.begin();
  }
  Iterator end()
  {
    return _map.end();
  }
  pair<Iterator, bool> Insert(const pair<K, V>& kv)
  {
    return _map.Insert(kv);
  }
  V& operator[](const K& key)
  {
    pair<Iterator, bool> it = Insert(make_pair(key, V()));
    return it.first->second;
  }
  Iterator Find(const K& key)
  {
    return _map.find(key);
  }
  bool Erase(const K& key)
  {
    return _map.Erase(key);
  }
private:
  Hashbucket<K, pair<K, V>, Mapkot, Hash> _map;
};

但是const版本的迭代器和非const迭代器是不能像以前一样套两个模板就能分别返回不同的const与非const类型,因为我们调用const迭代器的时候this指针也是const,那么成员:

这两个也是const类型,按照以前写begin()const,end()const:

然后取用const类型去构造,这里的迭代器构造函数是非const类型,没办法进行构造,这里属于权限放大。(严格意义上来说,hs也是一样的,传过来的是const,不能构造非const _hs)

所以我们这里就绪就要重新写一个const版本的迭代器(这里跟const迭代器什么区别)。

相关文章
|
10天前
|
存储 算法 C++
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
文章详细探讨了C++中的泛型编程与STL技术,重点讲解了如何使用模板来创建通用的函数和类,以及模板在提高代码复用性和灵活性方面的作用。
27 2
C++提高篇:泛型编程和STL技术详解,探讨C++更深层的使用
|
2月前
|
存储 算法 编译器
[C++] STL简介
[C++] STL简介
23 1
|
2月前
|
存储 算法 C++
C++ STL应用宝典:高效处理数据的艺术与实战技巧大揭秘!
【8月更文挑战第22天】C++ STL(标准模板库)是一组高效的数据结构与算法集合,极大提升编程效率与代码可读性。它包括容器、迭代器、算法等组件。例如,统计文本中单词频率可用`std::map`和`std::ifstream`实现;对数据排序及找极值则可通过`std::vector`结合`std::sort`、`std::min/max_element`完成;而快速查找字符串则适合使用`std::set`配合其内置的`find`方法。这些示例展示了STL的强大功能,有助于编写简洁高效的代码。
37 2
|
2月前
|
存储 搜索推荐 Serverless
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
【C++航海王:追寻罗杰的编程之路】哈希的应用——位图 | 布隆过滤器
30 1
|
2月前
|
安全 编译器 容器
C++STL容器和智能指针
C++STL容器和智能指针
|
2月前
|
存储 缓存 NoSQL
【C++】哈希容器
【C++】哈希容器
|
2月前
|
算法 安全 Linux
|
2月前
|
存储 Serverless C++
【C++航海王:追寻罗杰的编程之路】一篇文章带你认识哈希
【C++航海王:追寻罗杰的编程之路】一篇文章带你认识哈希
14 0
|
3月前
|
设计模式 算法 Java
【c++】STL之stack和queue详解
【c++】STL之stack和queue详解
37 1
|
3月前
|
存储 算法 C++
【C++高阶】探索STL的瑰宝 map与set:高效数据结构的奥秘与技巧
【C++高阶】探索STL的瑰宝 map与set:高效数据结构的奥秘与技巧
52 0
下一篇
无影云桌面