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迭代器什么区别)。

相关文章
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
223 2
|
8月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
438 73
|
9月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
8月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
343 3
|
9月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
507 1
|
10月前
|
C++ 容器
【c++丨STL】stack和queue的使用及模拟实现
本文介绍了STL中的两个重要容器适配器:栈(stack)和队列(queue)。容器适配器是在已有容器基础上添加新特性或功能的结构,如栈基于顺序表或链表限制操作实现。文章详细讲解了stack和queue的主要成员函数(empty、size、top/front/back、push/pop、swap),并提供了使用示例和模拟实现代码。通过这些内容,读者可以更好地理解这两种数据结构的工作原理及其实现方法。最后,作者鼓励读者点赞支持。 总结:本文深入浅出地讲解了STL中stack和queue的使用方法及其模拟实现,帮助读者掌握这两种容器适配器的特性和应用场景。
249 21
|
9月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
11月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
243 1
|
11月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
365 7