• C++ 模拟实现 STL 中 unordered 系列的容器


    目录

    一,改造 hash table

    1,节点,数据结构,一些简单的辅助函数

    2,迭代器

    3,构造,析构,拷贝

    4,插入,查找,删除

    二,hash_set、hash_multiset 与 hash_map、hash_multimap

    1,hash_set 与 hash_multiset

    2,hash_map 与 hash_multimap

    三,简单的测试

    1,测试 hash_set 与 hash_multiset

    2,测试 hash_map 与 hash_multimap

    四,hash_table 完整代码


    在C++标准模板库(STL)中,unordered 系列的容器包括 unordered_map、unordered_set、unordered_multimap 和 unordered_multiset。这些容器基于哈希表实现,提供平均常数时间复杂度的查找、插入和删除操作。与基于红黑树的有序容器(例如 set、map、multiset、multimap)相比,unordered 容器不会按特定顺序存储元素,而是按哈希表的特性进行组织。

    如果不知道哈希表是什么或者是不清楚哈希表是怎么具体实现的可以看看我的另一篇博客~:

    C++ 散列表(hash table)-CSDN博客文章浏览阅读545次,点赞14次,收藏11次。散列表(也称为哈希表(hash table))是一种通过哈希函数来计算数据存储位置的数据结构,使得对数据的插入、删除和查找操作可以非常快速进行。哈希表可以说是一种专门用来查找的数据结构。在最理想的情况下,哈希表的这些操作的时间复杂度为O(1)。那么,它是怎么做到如此高效的呢?我们先来考虑一下这个:如果我们要查找的键都是小整数,我们可以用一个数组来实现无序的符号表,将键作为数组的索引而数组中下标 i 处储存的就是它对应的值。这样我们就可以快速访问任意键的值。https://blog.csdn.net/qq_41521068/article/details/137912859?spm=1001.2014.3001.5501

    (STL 中 unordered 系列的容器与 set, map 这类的容器在功能上几乎完全相同,感兴趣的话还可以先看看这个:C++ 模拟实现 STL 中的 set、map 与 multiset、multimap-CSDN博客

    接下来此文将基于上面卡片中的博客所实现的 hash table 来进行一点小小的改造,然后再基于改造后的 hash table 来模拟实现一下 STL 中 unordered 系列的容器。为了与 STL 区分开(也为了可以少些一点字母),这里不使用 unordered 来作为命名的前缀,而是使用 hash(例如:hash_set, hash_map, hash_multiset, hash_multimap)。

    一,改造 hash table

    1,节点,数据结构,一些简单的辅助函数

    节点:

    1. //哈希节点
    2. template<class T>
    3. struct hash_node {
    4. T data;
    5. hash_node* next;
    6. hash_node(const T& _data = T()) :data(_data), next(nullptr) {}
    7. };

    数据结构:

    1. template<class Key, class T, class KeyOfT, class HashFunc = hash_func>
    2. class hash_table {
    3. private:
    4. typedef hash_node Node;
    5. HashFunc _hf;
    6. KeyOfT _kot;
    7. private:
    8. // 主要的成员变量
    9. size_t _size = 0;
    10. std::vector _table;
    11. }

    这里来解释一下 KeyOfT:

    1. KeyOfT 的作用是从存储的元素中提取出键值。hash table 中存储的元素通常是键值对,它就是用来告诉 hash table 如何从这些元素中提取出键值,以便提供给哈希函数进行计算
    2. hash table 中存储的元素可能是键值对,也可能仅仅只是单键(例如 hash_set 中存的是单键, hash_map 中存的是键值对)。KeyOfT 就是为了统一单键与键值对这两种情况而设计的
    3. 在上面代码中的模板参数中,Key 表示键,T 表示储存的元素;如果是 hash_map,T 表示一个 pair键值对, 如果是 hash_set,T 表示 Key。而 KeyOfT 就是从 T 中取出 Key

    简单的辅助函数:

    1. public:
    2. // 一些简单的功能
    3. bool empty()const { return _size == 0; }
    4. size_t size()const { return _size; }
    5. size_t table_size()const { return _table.size(); }
    6. // 为了便于扩展所设计的函数
    7. bool key_equal(Node* node,const Key& key)const { return _kot(node->data) == key; }
    8. size_t next_size(size_t size)const { return prime(size); }
    9. // 为了方便找到索引而设计的函数
    10. size_t table_index(const Key& key)const { return _hf(key) % table_size(); }
    11. size_t table_index(const Key& key, size_t table_size)const { return _hf(key) % table_size; }

    2,迭代器

    如果不清楚迭代器是什么,或者是不知道要怎么为容器设计一个专属的迭代器可以看看我的另一篇博客:C++ 迭代器与反向迭代器-CSDN博客

    迭代器类:

    1. // Ref 为引用类型,Ptr 为指针类型
    2. template<class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
    3. struct Hash_Iterator{
    4. typedef hash_node Node;
    5. KeyOfT _kot;
    6. HashFunc _hf;
    7. // 我们在进行 ++ 时,迭代器可能会从 table[i] 跳转到 table[i+?]
    8. // 所以我们不仅需要节点 node ,还需要 node 所在的 table 对象
    9. Node* _node;
    10. const std::vector* _table;
    11. // 这里的 _table 一定要加 const 修饰,
    12. // 不然在使用 const_iterator 时类型会无法转换
    13. typedef T value_type;
    14. typedef Ref reference;
    15. typedef Ptr pointer;
    16. typedef Hash_Iterator iterator; //普通迭代器类型
    17. typedef Hash_Iterator self; //当前迭代器类型
    18. // 用哈希节点构造当前迭代器
    19. Hash_Iterator(Node* node, const std::vector* table) :_node(node), _table(table) {}
    20. // 用普通迭代器构造当前迭代器
    21. Hash_Iterator(const iterator& iter) :_node(iter._node), _table(iter._table) {}
    22. Ref operator*() { return _node->data; }
    23. Ptr operator->() { return &(operator*()); }
    24. bool operator==(const self& iter)const { return iter._node == _node; }
    25. bool operator!=(const self& iter)const { return iter._node != _node; }
    26. self operator++();
    27. self operator++(int);
    28. };

    这里要提一下:STL 中的 unordered 系列容器都没有 operator--() 操作,也就是说该系列容器并不支持反向迭代器。

    现在我们来看看 operator++() 的实现:

    1. self operator++() {
    2. if (_node->next) {
    3. //如果当前节点还有下一个节点
    4. _node = _node->next;
    5. return *this;
    6. }
    7. else {
    8. //从下一个桶开始往后找
    9. size_t idx = _hf(_kot(_node->data)) % _table->size();
    10. for (size_t i = idx + 1;i < _table->size();++i) {
    11. if ((*_table)[i]) {
    12. //取桶中的第一个元素
    13. _node = (*_table)[i];
    14. return *this;
    15. }
    16. }
    17. }
    18. _node = nullptr;
    19. _table = nullptr;
    20. return *this;
    21. }
    22. self operator++(int) {
    23. self res = *this;
    24. ++(*this);
    25. return res;
    26. }

    hash_table 中的迭代器:

    1. public:
    2. // 迭代器
    3. typedef Hash_Iterator iterator;
    4. typedef Hash_Iteratorconst T&, const T*, KeyOfT, HashFunc> const_iterator;
    5. iterator begin() {
    6. if (empty()) return end();
    7. for (Node* node : _table) {
    8. if (node) return iterator(node, &_table);
    9. }
    10. }
    11. iterator end() { return iterator(nullptr, nullptr); }
    12. const_iterator begin()const {
    13. if (empty()) return end();
    14. for (Node* node : _table) {
    15. if (node) return const_iterator(node, &_table);
    16. }
    17. }
    18. const_iterator end()const { return const_iterator(nullptr, nullptr); }

    3,构造,析构,拷贝

    这里实现的 hash_table 允许表中有重复的元素存在,为了更好的适配 hash_set、hash_map 以及 hash_multiset、hash_multimap,所以 hash_table 只提供空构造(表中没有有效元素)以及拷贝构造,其他的构造方式就由它的上层容器来扩展。

    首先来看辅助函数:

    1. public:
    2. // 辅助拷贝与析构的函数
    3. // 交换
    4. void swap(hash_table& ht) {
    5. std::swap(_size, ht._size);
    6. _table.swap(ht._table);
    7. }
    8. // 清空
    9. void clear() {
    10. for (int i = 0;i < _table.size();++i) {
    11. Node* node = _table[i];
    12. while (node) {
    13. Node* next = node->next;
    14. delete node;
    15. node = next;
    16. }
    17. _table[i] = nullptr;
    18. }
    19. _size = 0;
    20. }
    21. private:
    22. // 拷贝哈希表
    23. void copy(const hash_table& ht){
    24. _size = ht.size();
    25. _table.resize(ht.table_size());
    26. Node* dummy = new Node;
    27. for (int i = 0;i < _table.size();++i) {
    28. Node* pre = dummy;
    29. Node* node = ht._table[i];
    30. while (node != nullptr) {
    31. pre->next = new Node(node->data);
    32. node = node->next;
    33. pre = pre->next;
    34. }
    35. _table[i] = dummy->next;
    36. dummy->next = nullptr;
    37. }
    38. delete dummy;
    39. }

    接下来是真正的构造,析构与拷贝:

    1. public:
    2. // 构造与析构
    3. hash_table() :_size(0), _table(11, nullptr) {}
    4. ~hash_table() { if (not empty()) clear(); }
    5. public:
    6. // 拷贝
    7. hash_table(const hash_table& ht) { copy(ht); }
    8. hash_table(hash_table&& ht) { swap(ht); }
    9. hash_table& operator=(hash_table ht) {
    10. swap(ht);
    11. return *this;
    12. }

    4,插入,查找,删除

    这里实现的 hash_table 提供两种不同的插入方式:一种是 insert_unique(),表示插入的元素必须是树中不存在的元素;另一种是 insert_equal(),这个操作可以将重复的元素插入树中。这两种插入方式分别会被 hash_set、hash_map 与 hash_multiset、hash_multimap 调用。

    关于代码中的 resize (扩容逻辑)这里就不具体给出了,与文章开头中推荐的博客一样。

    首先是插入:

    1. public:
    2. pairbool> insert_unique(const T& data){
    3. if (_size == _table.size()) resize(_size * 2);
    4. size_t idx = table_index(_kot(data));
    5. for (Node* node = _table[idx]; node;node = node->next) {
    6. if (key_equal(node, _kot(data))) {
    7. // key 已经存在就直接返回
    8. return make_pair(iterator(node, &_table), false);
    9. }
    10. }
    11. iterator res = insert_assist(data, idx);
    12. return make_pair(res, true);
    13. }
    14. iterator insert_equal(const T& data){
    15. if (_size == _table.size()) resize(_size * 2);
    16. size_t idx = table_index(_kot(data));
    17. return insert_assist(data, idx);
    18. }
    19. private:
    20. iterator insert_assist(const T& data, size_t idx) {
    21. Node* node = new Node(data);
    22. // 头插新节点
    23. node->next = _table[idx];
    24. _table[idx] = node;
    25. ++_size;
    26. return iterator(node, &_table);
    27. }

    接下来是查找:

    hash_table 的查找十分高效,不过 hash_table 中存放的元素并不是有序的,如果我们想按照范围来查找数据,我们因该选择 AVL 或者 RB_tree 来当作我们的容器。

    1. private:
    2. template<class Iter_Type>
    3. Iter_Type find_impli(const Key& key)const{
    4. size_t idx = table_index(key);
    5. for (Node* node = _table[idx];node;node = node->next) {
    6. if (key_equal(node, key)) return Iter_Type(node, &_table);
    7. }
    8. return Iter_Type(nullptr, nullptr);
    9. }
    10. public:
    11. // 查找,插入,删除
    12. iterator find(const Key& key) { return find_impli(key); }
    13. const_iterator find(const Key& key)const { return find_impli(key); }
    14. size_t count(const Key& key)const {
    15. size_t idx = table_index(key);
    16. size_t cnt = 0;
    17. for (Node* node = _table[idx];node;node = node->next) {
    18. if (key_equal(node, key)) ++cnt;
    19. }
    20. return cnt;
    21. }

    最后是删除:

    1. public:
    2. size_t erase(const Key& key) {
    3. size_t idx = table_index(key);
    4. size_t count = 0;
    5. Node* dummy = new Node;
    6. dummy->next = _table[idx];
    7. Node* pre = dummy, * cur = _table[idx];
    8. while (cur) {
    9. if (key_equal(cur, key)) {
    10. pre->next = cur->next;
    11. delete cur;
    12. cur = pre->next;
    13. --_size;
    14. ++count;
    15. }
    16. else {
    17. pre = cur;
    18. cur = cur->next;
    19. }
    20. }
    21. _table[idx] = dummy->next;
    22. delete dummy;
    23. return count;
    24. }

    哈希表示意图(开链法):

    二,hash_set、hash_multiset 与 hash_map、hash_multimap

    我们实现了 hash_table 之后,接下来的工作就十分简单了,基本上就是对 hash_table 的封装。

    使用 set 或 map,我们的目的都是能够根据键值进行快速的查找。这一点不管是底层用 RB_tree 还是 hash_table 都能够很好的完成。但是我们需要注意的是:RB_tree 可以自动根据键值进行排序,而 hash_table 不行,所以 STL 中的 set,map 等容器都有自动排序的功能,而 unordered 系列的容器则没有。

    1,hash_set 与 hash_multiset

    hash_set 的使用方式与 set 完全相同

    1. namespace mySTL {
    2. template<class Key>
    3. class hash_set {
    4. private:
    5. struct KeyOfT {
    6. Key operator()(const Key& key)const {
    7. return key;
    8. }
    9. };
    10. typedef hash_table hash_table;
    11. hash_table _hash_tab;
    12. public:
    13. typedef typename hash_table::const_iterator iterator;
    14. typedef typename hash_table::const_iterator const_iterator;
    15. iterator begin()const { return _hash_tab.begin(); }
    16. iterator end()const { return _hash_tab.end(); }
    17. public:
    18. // 构造与析构
    19. hash_set(){}
    20. hash_set(const std::initializer_list& lt) {
    21. for (const Key& key : lt) {
    22. insert(key);
    23. }
    24. }
    25. ~hash_set() { if (not empty()) clear(); }
    26. // 拷贝
    27. hash_set(const hash_set& hs) { _hash_tab = hs._hash_tab; }
    28. hash_set(hash_set&& hs) { swap(hs); }
    29. hash_set& operator=(hash_set hs){
    30. swap(hs);
    31. return *this;
    32. }
    33. public:
    34. size_t size()const { return _hash_tab.size(); }
    35. bool empty()const { return _hash_tab.empty(); }
    36. void swap(hash_set& hs) { _hash_tab.swap(hs._hash_tab); }
    37. iterator find(const Key& key)const { return _hash_tab.find(key); }
    38. size_t count(const Key& key)const { return _hash_tab.count(key); }
    39. pairbool> insert(const Key& key) { return _hash_tab.insert_unique(key); }
    40. size_t erase(const Key& key) { return _hash_tab.erase(key); }
    41. void clear() { _hash_tab.clear(); }
    42. void print()const { _hash_tab.print(); }
    43. };
    44. }

    hash_multiset 的特性与 hash_set 完全相同,实现上的唯一区别就在于插入,前者使用 insert_equal(),后者使用 insert_unique()。

    1. namespace mySTL {
    2. template<class Key>
    3. class hash_multiset {
    4. private:
    5. struct KeyOfT {
    6. Key operator()(const Key& key)const {
    7. return key;
    8. }
    9. };
    10. typedef hash_table hash_table;
    11. hash_table _hash_tab;
    12. public:
    13. typedef typename hash_table::const_iterator iterator;
    14. typedef typename hash_table::const_iterator const_iterator;
    15. iterator begin()const { return _hash_tab.begin(); }
    16. iterator end()const { return _hash_tab.end(); }
    17. public:
    18. // 构造与析构
    19. hash_multiset() {}
    20. hash_multiset(const std::initializer_list& lt) {
    21. for (const Key& key : lt) {
    22. insert(key);
    23. }
    24. }
    25. ~hash_multiset() { if (not empty()) clear(); }
    26. // 拷贝
    27. hash_multiset(const hash_multiset& hs) { _hash_tab = hs._hash_tab; }
    28. hash_multiset(hash_multiset&& hs) { swap(hs); }
    29. hash_multiset& operator=(hash_multiset hs) {
    30. swap(hs);
    31. return *this;
    32. }
    33. public:
    34. size_t size()const { return _hash_tab.size(); }
    35. bool empty()const { return _hash_tab.empty(); }
    36. void swap(hash_multiset& hs) { _hash_tab.swap(hs._hash_tab); }
    37. iterator find(const Key& key)const { return _hash_tab.find(key); }
    38. size_t count(const Key& key)const { return _hash_tab.count(key); }
    39. iterator insert(const Key& key) { return _hash_tab.insert_equal(key); }
    40. size_t erase(const Key& key) { return _hash_tab.erase(key); }
    41. void clear() { _hash_tab.clear(); }
    42. void print()const { _hash_tab.print(); }
    43. };
    44. }

    2,hash_map 与 hash_multimap

    hash_map 的使用方式与 map 完全相同

    1. namespace mySTL {
    2. template<class Key, class Val>
    3. class hash_map {
    4. private:
    5. struct KeyOfT {
    6. Key operator()(const pair<const Key, Val>& p)const {
    7. return p.first;
    8. }
    9. };
    10. typedef hash_tableconst Key, Val>, KeyOfT> hash_table;
    11. hash_table _hash_tab;
    12. public:
    13. typedef typename hash_table::iterator iterator;
    14. typedef typename hash_table::const_iterator const_iterator;
    15. iterator begin() { return _hash_tab.begin(); }
    16. iterator end() { return _hash_tab.end(); }
    17. const_iterator begin()const { return _hash_tab.begin(); }
    18. const_iterator end()const { return _hash_tab.end(); }
    19. public:
    20. // 构造与析构
    21. hash_map(){}
    22. hash_map(const std::initializer_list>& lt) {
    23. for (const auto& data : lt) {
    24. insert(data);
    25. }
    26. }
    27. ~hash_map() { if (not empty()) clear(); }
    28. // 拷贝
    29. hash_map(const hash_map& hm) { _hash_tab = hm._hash_tab; }
    30. hash_map(hash_map&& hm) { swap(hm); }
    31. hash_map& operator=(hash_map hm) {
    32. swap(hm);
    33. return *this;
    34. }
    35. public:
    36. size_t size()const { return _hash_tab.size(); }
    37. bool empty()const { return _hash_tab.empty(); }
    38. void swap(hash_map& hm) { _hash_tab.swap(hm._hash_tab); }
    39. iterator find(const Key& key) { return _hash_tab.find(key); }
    40. const_iterator find(const Key& key)const { return _hash_tab.find(key); }
    41. Val& operator[](const Key& key) { return insert(make_pair(key, Val())).first->second; }
    42. size_t count(const Key& key)const { return _hash_tab.count(key); }
    43. pairbool> insert(const pair& kv) { return _hash_tab.insert_unique(kv); }
    44. void erase(const Key& key) { _hash_tab.erase(key); }
    45. void clear() { _hash_tab.clear(); }
    46. void print()const { _hash_tab.print(); }
    47. };
    48. }

    hash_multimap 的特性与 hash_map 完全相同,实现上的唯一区别就在于插入,前者使用 insert_equal(),后者使用 insert_unique()。还有一点需要注意:hash_multimap 中没有重载 operator[]。

    1. namespace mySTL {
    2. template<class Key, class Val>
    3. class hash_multimap {
    4. private:
    5. struct KeyOfT {
    6. Key operator()(const pair<const Key, Val>& p)const {
    7. return p.first;
    8. }
    9. };
    10. typedef hash_tableconst Key, Val>, KeyOfT> hash_table;
    11. hash_table _hash_tab;
    12. public:
    13. typedef typename hash_table::iterator iterator;
    14. typedef typename hash_table::const_iterator const_iterator;
    15. iterator begin() { return _hash_tab.begin(); }
    16. iterator end() { return _hash_tab.end(); }
    17. const_iterator begin()const { return _hash_tab.begin(); }
    18. const_iterator end()const { return _hash_tab.end(); }
    19. public:
    20. // 构造与析构
    21. hash_multimap() {}
    22. hash_multimap(const std::initializer_list>& lt) {
    23. for (const auto& data : lt) {
    24. insert(data);
    25. }
    26. }
    27. ~hash_multimap() { if (not empty()) clear(); }
    28. // 拷贝
    29. hash_multimap(const hash_multimap& hm) { _hash_tab = hm._hash_tab; }
    30. hash_multimap(hash_multimap&& hm) { swap(hm); }
    31. hash_multimap& operator=(hash_multimap hm) {
    32. swap(hm);
    33. return *this;
    34. }
    35. public:
    36. size_t size()const { return _hash_tab.size(); }
    37. bool empty()const { return _hash_tab.empty(); }
    38. void swap(hash_multimap& hm) { _hash_tab.swap(hm._hash_tab); }
    39. iterator find(const Key& key) { return _hash_tab.find(key); }
    40. const_iterator find(const Key& key)const { return _hash_tab.find(key); }
    41. size_t count(const Key& key)const { return _hash_tab.count(key); }
    42. iterator insert(const pair& kv) { return _hash_tab.insert_equal(kv); }
    43. void erase(const Key& key) { _hash_tab.erase(key); }
    44. void clear() { _hash_tab.clear(); }
    45. void print()const { _hash_tab.print(); }
    46. };
    47. }

    三,简单的测试

    为了方便测试,这里在 hash_table 中添加了一个函数:

    1. public:
    2. void print()const {
    3. size_t idx = 0;
    4. for (Node* node : _table) {
    5. cout << idx++ << ": ";
    6. while (node) {
    7. cout << _kot(node->data) << " -> ";
    8. node = node->next;
    9. }
    10. cout << "null" << endl;
    11. }
    12. cout << endl;
    13. }

    1,测试 hash_set 与 hash_multiset

    下面这份代码是对 hash_set 进行简单的测试,我们只需要用一下编辑器自带的替换功能将下面代码中的 set 改为 multiset 就能够对 hash_multiset 也进行一下简单的测试了

    1. class test_hash_set {
    2. private:
    3. // 测试插入,迭代器,删除功能
    4. void test_insert_iter_erase() {
    5. mySTL::hash_set<int> hash;
    6. // 测试插入
    7. for (int i = 0;i < 50;++i) {
    8. hash.insert(rand() % 100);
    9. }
    10. hash.print();
    11. // 测试迭代器
    12. for (int data : hash) {
    13. cout << data << " ";
    14. }
    15. cout << endl << endl;
    16. // 测试删除
    17. for (int i = 0;i < 50;++i) {
    18. hash.erase(rand() % 100);
    19. }
    20. hash.print();
    21. hash.clear();
    22. hash.print();
    23. }
    24. // 测试构造,拷贝,查找
    25. void test_construct_copy_find() {
    26. // 测试构造与拷贝
    27. mySTL::hash_set<int> hash = { 1,5,5,5,3,6,9,67,71 };
    28. mySTL::hash_set<int> hash2(hash);
    29. mySTL::hash_set<int> hash3;
    30. hash3 = hash2;
    31. hash3.print();
    32. mySTL::hash_set<int> hash4 = move(hash3);
    33. hash4.print();
    34. // 测试查找
    35. cout << *hash4.find(5) << " " << hash4.count(5) << endl;
    36. }
    37. public:
    38. void test() {
    39. test_insert_iter_erase();
    40. cout << endl;
    41. test_construct_copy_find();
    42. }
    43. };

    2,测试 hash_map 与 hash_multimap

    1. class test_hash_map {
    2. private:
    3. void test_insert_iter_erase() {
    4. //测试插入功能和迭代器
    5. mySTL::hash_map hash;
    6. hash["test"] = "测试";
    7. hash["test"] = "测试2";
    8. hash["test"] = "测试3";
    9. hash["data_struct"] = "数据结构";
    10. hash["algorithm"] = "算法";
    11. hash["hash_table"] = "哈希表";
    12. hash.print();
    13. for (const auto& [key, val] : hash) {
    14. cout << key << " : " << val << endl;
    15. }
    16. }
    17. void test_construct_copy() {
    18. mySTL::hash_multimap hash = {
    19. {"test","测试"},{"test","测试2"},{"test","测试3"},
    20. {"data_struct","数据结构"},{"algorithm","算法"},{"hash_table","哈希表"}
    21. };
    22. mySTL::hash_multimap hash2(hash);
    23. mySTL::hash_multimap hash3;
    24. hash3 = hash2;
    25. hash3.print();
    26. for (const auto& [key, val] : hash3) {
    27. cout << key << " : " << val << endl;
    28. }
    29. cout << endl;
    30. mySTL::hash_multimap hash4 = move(hash3);
    31. hash4.print();
    32. for (const auto& [key, val] : hash4) {
    33. cout << key << " : " << val << endl;
    34. }
    35. }
    36. public:
    37. void test() {
    38. test_insert_iter_erase();
    39. test_construct_copy();
    40. }
    41. };

    四,hash_table 完整代码

    1. namespace mySTL {
    2. //哈希节点
    3. template<class T>
    4. struct hash_node {
    5. T data;
    6. hash_node* next;
    7. hash_node(const T& _data = T()) :data(_data), next(nullptr) {}
    8. };
    9. // 哈希函数族
    10. template<class Key>
    11. struct hash_func {
    12. size_t operator()(const Key& key)const {
    13. return key;
    14. }
    15. };
    16. template<>
    17. struct hash_func {
    18. size_t operator()(const std::string& str)const {
    19. size_t res = 0;
    20. int seed = 131;
    21. for (char ch : str) {
    22. res *= seed;
    23. res += ch;
    24. }
    25. return res;
    26. }
    27. };
    28. // 寻找并返回不小于 n 的最小素数的函数
    29. size_t prime(int n) {
    30. // 检查一个数是否为素数的函数
    31. std::function<bool(int)> is_prime = [](int num) {
    32. if (num <= 1) return false; // 处理非正整数
    33. if (num <= 3) return true; // 2和3是素数
    34. if (num % 2 == 0 || num % 3 == 0) return false; // 排除能被2或3整除的数
    35. for (int i = 5; i * i <= num; i += 6) {
    36. if (num % i == 0 || num % (i + 2) == 0) return false;
    37. }
    38. return true;
    39. };
    40. if (n <= 2) return 2; // 特殊处理小于等于2的情况
    41. if ((n & 1) == 0) n++; // 如果n是偶数,从下一个奇数开始检查
    42. while (not is_prime(n)) n += 2; // 只检查奇数
    43. return n;
    44. }
    45. // Ref 为引用类型,Ptr 为指针类型
    46. template<class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
    47. struct Hash_Iterator{
    48. typedef hash_node Node;
    49. KeyOfT _kot;
    50. HashFunc _hf;
    51. Node* _node;
    52. const std::vector* _table; // 这里的 _table 一定要加 const 修饰,
    53. // 不然在使用 const_iterator 时类型会无法转换
    54. typedef T value_type;
    55. typedef Ref reference;
    56. typedef Ptr pointer;
    57. typedef Hash_Iterator iterator; //普通迭代器类型
    58. typedef Hash_Iterator self; //当前迭代器类型
    59. Hash_Iterator(Node* node, const std::vector* table) :_node(node), _table(table) {} //用哈希节点构造当前迭代器
    60. Hash_Iterator(const iterator& iter) :_node(iter._node), _table(iter._table) {} //用普通迭代器构造当前迭代器
    61. Ref operator*() { return _node->data; }
    62. Ptr operator->() { return &(operator*()); }
    63. bool operator==(const self& iter)const { return iter._node == _node; }
    64. bool operator!=(const self& iter)const { return not(iter == *this); }
    65. self operator++() {
    66. if (_node->next) {
    67. //如果当前节点还有下一个节点
    68. _node = _node->next;
    69. return *this;
    70. }
    71. else {
    72. //从下一个桶开始往后找
    73. size_t idx = _hf(_kot(_node->data)) % _table->size();
    74. for (size_t i = idx + 1;i < _table->size();++i) {
    75. if ((*_table)[i]) {
    76. //取桶中的第一个元素
    77. _node = (*_table)[i];
    78. return *this;
    79. }
    80. }
    81. }
    82. _node = nullptr;
    83. _table = nullptr;
    84. return *this;
    85. }
    86. self operator++(int) {
    87. self res = *this;
    88. ++(*this);
    89. return res;
    90. }
    91. };
    92. template<class Key, class T, class KeyOfT, class HashFunc = hash_func>
    93. class hash_table {
    94. private:
    95. typedef hash_node Node;
    96. HashFunc _hf;
    97. KeyOfT _kot;
    98. private:
    99. // 主要的成员变量
    100. size_t _size = 0;
    101. std::vector _table;
    102. public:
    103. // 迭代器
    104. typedef Hash_Iterator iterator;
    105. typedef Hash_Iteratorconst T&, const T*, KeyOfT, HashFunc> const_iterator;
    106. iterator begin() {
    107. if (empty()) return end();
    108. for (Node* node : _table) {
    109. if (node) return iterator(node, &_table);
    110. }
    111. }
    112. iterator end() { return iterator(nullptr, nullptr); }
    113. const_iterator begin()const {
    114. if (empty()) return end();
    115. for (Node* node : _table) {
    116. if (node) return const_iterator(node, &_table);
    117. }
    118. }
    119. const_iterator end()const { return const_iterator(nullptr, nullptr); }
    120. public:
    121. // 一些简单的功能
    122. bool empty()const { return _size == 0; }
    123. size_t size()const { return _size; }
    124. size_t table_size()const { return _table.size(); }
    125. // 为了便于扩展所设计的函数
    126. bool key_equal(Node* node,const Key& key)const { return _kot(node->data) == key; }
    127. size_t next_size(size_t size)const { return prime(size); }
    128. // 为了方便找到索引而设计的函数
    129. size_t table_index(const Key& key)const { return _hf(key) % table_size(); }
    130. size_t table_index(const Key& key, size_t table_size)const { return _hf(key) % table_size; }
    131. public:
    132. // 辅助拷贝与析构的函数
    133. // 交换
    134. void swap(hash_table& ht) {
    135. std::swap(_size, ht._size);
    136. _table.swap(ht._table);
    137. }
    138. // 拷贝哈希表
    139. void copy(const hash_table& ht){
    140. _size = ht.size();
    141. _table.resize(ht.table_size());
    142. Node* dummy = new Node;
    143. for (int i = 0;i < _table.size();++i) {
    144. Node* pre = dummy;
    145. Node* node = ht._table[i];
    146. while (node != nullptr) {
    147. pre->next = new Node(node->data);
    148. node = node->next;
    149. pre = pre->next;
    150. }
    151. _table[i] = dummy->next;
    152. dummy->next = nullptr;
    153. }
    154. delete dummy;
    155. }
    156. // 清空
    157. void clear() {
    158. for (int i = 0;i < _table.size();++i) {
    159. Node* node = _table[i];
    160. while (node) {
    161. Node* next = node->next;
    162. delete node;
    163. node = next;
    164. }
    165. _table[i] = nullptr;
    166. }
    167. _size = 0;
    168. }
    169. public:
    170. // 构造与析构
    171. hash_table() :_size(0), _table(11, nullptr) {}
    172. ~hash_table() { if (not empty()) clear(); }
    173. public:
    174. // 拷贝
    175. hash_table(const hash_table& ht) { copy(ht); }
    176. hash_table(hash_table&& ht) { swap(ht); }
    177. hash_table& operator=(hash_table ht) {
    178. swap(ht);
    179. return *this;
    180. }
    181. public:
    182. // 扩容
    183. void resize(size_t size) {
    184. size = next_size(size);
    185. if (size <= _size) return;
    186. std::vector newTable(size, nullptr);
    187. for (Node* node : _table) {
    188. while (node) {
    189. size_t idx = table_index(_kot(node->data), newTable.size());
    190. Node* next = node->next;
    191. node->next = newTable[idx];
    192. newTable[idx] = node;
    193. node = next;
    194. }
    195. }
    196. // _table = std::move(newTable);
    197. _table.swap(newTable);
    198. }
    199. private:
    200. template<class Iter_Type>
    201. Iter_Type find_impli(const Key& key)const{
    202. size_t idx = table_index(key);
    203. for (Node* node = _table[idx];node;node = node->next) {
    204. if (key_equal(node, key)) return Iter_Type(node, &_table);
    205. }
    206. return Iter_Type(nullptr, nullptr);
    207. }
    208. public:
    209. // 查找,插入,删除
    210. iterator find(const Key& key) { return find_impli(key); }
    211. const_iterator find(const Key& key)const { return find_impli(key); }
    212. size_t count(const Key& key)const {
    213. size_t idx = table_index(key);
    214. size_t cnt = 0;
    215. for (Node* node = _table[idx];node;node = node->next) {
    216. if (key_equal(node, key)) ++cnt;
    217. }
    218. return cnt;
    219. }
    220. pairbool> insert_unique(const T& data){
    221. size_t idx = table_index(_kot(data));
    222. for (Node* node = _table[idx]; node;node = node->next) {
    223. if (key_equal(node, _kot(data))) {
    224. // key 已经存在就直接返回
    225. return make_pair(iterator(node, &_table), false);
    226. }
    227. }
    228. iterator res = insert_equal(data);
    229. return make_pair(res, true);
    230. }
    231. iterator insert_equal(const T& data){
    232. if (_size == _table.size()) {
    233. // 扩容 table
    234. resize(_size * 2);
    235. }
    236. size_t idx = table_index(_kot(data));
    237. Node* node = new Node(data);
    238. // 头插新节点
    239. node->next = _table[idx];
    240. _table[idx] = node;
    241. ++_size;
    242. return iterator(node, &_table);
    243. }
    244. public:
    245. size_t erase(const Key& key) {
    246. size_t idx = table_index(key);
    247. size_t count = 0;
    248. Node* dummy = new Node;
    249. dummy->next = _table[idx];
    250. Node* pre = dummy, * cur = _table[idx];
    251. while (cur) {
    252. if (key_equal(cur, key)) {
    253. pre->next = cur->next;
    254. delete cur;
    255. cur = pre->next;
    256. --_size;
    257. ++count;
    258. }
    259. else {
    260. pre = cur;
    261. cur = cur->next;
    262. }
    263. }
    264. _table[idx] = dummy->next;
    265. delete dummy;
    266. return count;
    267. }
    268. public:
    269. void print()const {
    270. size_t idx = 0;
    271. for (Node* node : _table) {
    272. cout << idx++ << ": ";
    273. while (node) {
    274. cout << _kot(node->data) << " -> ";
    275. node = node->next;
    276. }
    277. cout << "null" << endl;
    278. }
    279. cout << endl;
    280. }
    281. };
    282. }

  • 相关阅读:
    【Linux】项目日志——输出重定向
    react hooks useMemo
    Vue中this.$set()解决页面不更新问题
    打造基于ILRuntime热更新的组件化开发
    如何修复-谷歌浏览器-打开任何一个网页都显示崩溃
    微信自动化推送天气预报信息教程【Python版源代码】
    分布式版本控制工具——git
    Vue3像Vue2一样在prototype(原型)上挂载数据
    2分能出线,6分却不能出线?世界杯小组赛的出线规则这次真被我整明白了
    一文了解SpringBoot的配置文件
  • 原文地址:https://blog.csdn.net/qq_41521068/article/details/137930168