unordered_map 和 unordered_set

简介: unordered_map 和 unordered_set

unordered —— 无序的,从表面上来看,与 map 和 set 不同之处就在于,unordered_map 和 unordered_set 无法保证插入数据是有序的;

尽管如此,由于这两种容器内部封装了“哈希桶”,可以实现快速查找数据 —— 这一优点与 map 和 set 相同。

其实,除了内部结构的不同外,其余与 map 和 set 没什么不同,一样的 insert、find、erase … … 在我们模拟过 map set 的基础上,再学习封装 无序map 和 无序set 实在简单。

因此,本文的重点在于迭代器的运行逻辑 —— operator++() ,和理解模板、仿函数等

最后,补充一个概念:

哈希 是一种思想,将传入的数据映射成一个或多个整型;哈希表 / 哈希桶 则是一种实现哈希思想的结构。

一、改造哈希桶

1.1 初步搭建 HashTable
// 改造 HashNode
  template<class T> 
    struct HashNode
    {
        HashNode<T>* _next; 
        T _data;
        
        HashNode(const T& data)
            :_next(nullptr)
            ,_data(data)
        {}
    };
  // 改造 HashTable
    // 此处 HashFunc 与《开散列哈希桶》中提供的无异
  // KeyOfT 与 map set 中的无异,都是用于从 T 中取到键值 
  template<class K, class T, class KeyOfT, class Hash = HashFunc<K>> 
    class HashTable
    {
    public:
        typedef HashNode<T> Node;
    
    private:
        vector<Node*> _tables;
        size_t _n = 0;
    };
1.2 数据插入 数据查找
  • 数据插入 —— Insert()
pair<iterator, bool> Insert(const T& data)
    {
        KeyOfT kot;
        iterator ret = Find(kot(data)); // 未实现的 Find,返回值为 iterator
        if (ret != end())// 找到了
        {
            return make_pair(ret, false);
        }
        
        // 扩容 
        // ... 
        
        // 插入
        Hash hs;
        size_t hashi = hs(kot(data)) % _tables.size(); // kot(data) -- 取键值;hs(键值) -- 计算映射值
        Node* newNode = new Node(data);
        newNode->_next = _tables[hashi];
        _tables[hashi] = newNode;
        _n++;
        
        return make_pair(new_iterator, true); // 此处为伪代码!
  }

与普通哈希桶不同的地方在于此: size_t hashi = hs(kot(data)) % _tables.size(); // kot(data) -- 取键值;hs(键值) -- 计算映射值 ,多套了一层仿函数。

PS:

  1. 扩容逻辑与哈希桶完全一致。
  2. return make_pair(new_iterator, true); 为伪代码,用 new_iterator 代表插入节点的迭代器 —— 后面介绍迭代器时,会将这里的坑填上!
  • 数据查找 —— Find()

很多新手不理解为什么在封装 map set 要这样构造 —— 好像传入了两个 Key :

map: RBTree<K, pair<const K, V>, ... > _t;

set: RBTree<K, const K, ...> _t;

我们通常是 t.find(key1); t,erase(key2); 这种方式使用 find() 和 erase() ,无论 t 是 map 还是 set

意思就是,第一个模板参数 K 是为了解决 map 的查找和删除等的问题

iterator Find(const K& key)
    {
        Hash hs;
        KeyOfT kot;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        while (cur)
        {
            if (kot(cur->_data) == key)
            {
                return iterator_cur; // 这里同样是伪代码!
            }
            cur = cur->_next;
        }
        return iterator_nullptr;// 伪代码
    }

iterator_cur 代表 cur 位置的迭代器; iterator_nullptr 代表 空迭代器,这两个迭代器的空白会在后面填补。

二、迭代器封装 __Hash_Iterator

__Hash_Iterator 内部应该传入什么呢?节点的指针吗?哈希桶的指针吗?

我们希望可以通过迭代器遍历整个哈希桶,同时要能取到当前迭代器所在节点的数据,因此,迭代器内部应有节点的指针和哈希桶的指针。

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>> 
    struct __Hash_Iterator
    {
        typedef HashNode<T> Node;
        typedef HashTable<K, T, KeyOfT, Hash> HashTable;
        typedef __Hash_Iterator<K, T, KeyOfT, Hash> Self;
        
        Node* _node;
        HashTable* _ht;
        
        __Hash_Iterator(Node* node, HashTable* ht)
          :_node(node)
            ,_ht(ht)
        {}
    };
2.1 operator++()

operator++() 需要考虑两种情形:

  1. _node->_next 不为空,++ 后,_node = _node->_next
  2. _node->_next 为空,则往后遍历 HashTable,直到找到下一个不为空的位置,或者遍历完整个 HashTable 。
Self& operator++()
    {
        if (_node->_next)
        {
            _node = _node->_next;
        }
        else 
        {
            Hash hs;
            KeyOfT kot;
            size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
            hashi++; // 从当前位置的后一个位置开始查找
            while (hashi < _ht->_tables.size())
            {
                if (_ht->_tables[hashi])
                {
                    // 找到下一个位置,跳出循环
                    _node = _ht->_tables[hashi]; 
                    break;
                }
                ++hashi;
            }
            if (hashi == _ht->_tables.size()) // 遍历结束,没有下一个节点
            {
                _node = nullptr;
            }
        }
        return *this;
    }
2.2 operator!=() operator*() operator->()
bool operator!=(const Self& s)
    {
        return _node != s._node;
    }
  T& operator*()
    {
        return _node->_data;
  }
  T* operator->()
    {
        return &_node->_data;
  }
2.3 完善 HashTable

针对第一部分中迭代器遗留问题,在这里将其完善。

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>> 
    class HashTable
    {
    public:
        typedef HashNode<T> Node;
        typedef __Hash_Iterator<K, T, KeyOfT, Hash> iterator;
        
        pair<iterator, bool> Insert(const T& data)
        {
            KeyOfT kot;
            Hash hs;
            iterator ret = Find(kot(data)); // 未实现的 Find,返回值为 iterator
            if (ret != end())// 找到了
            {
                return make_pair(ret, false);
            }
            // 扩容 
            if (_n == _tables.size())
      {
        vector<Node*> newTables(_tables.size() * 2, nullptr);
        
        for (size_t i = 0; i < _tables.size(); ++i)
        {
          
          Node* cur = _tables[i];
          while (cur)
          {
            Node* next = cur->_next;
            size_t hashi = hs(kot(cur->_data)) % newTables.size();
            cur->_next = newTables[hashi];
            newTables[hashi] = cur;
            cur = next;
          }
          _tables[i] = nullptr;
          
        }
        _tables.swap(newTables);
      }
            // 插入
            
            size_t hashi = hs(kot(data)) % _tables.size(); // kot(data) -- 取键值;hs(键值) -- 计算映射值
            Node* newNode = new Node(data);
            newNode->_next = _tables[hashi];
            _tables[hashi] = newNode;
            _n++;
            return make_pair(iterator(newNode, this), true); // 
        }
        
        iterator Find(const K& key)
        {
            Hash hs;
            KeyOfT kot;
            size_t hashi = hs(key) % _tables.size();
            Node* cur = _tables[hashi];
            while (cur)
            {
                if (kot(cur->_data) == key)
                {
                    return iterator(cur, this); 
                }
                cur = cur->_next;
            }
            return iterator(nullptr, this);
        }
        
        bool Erase(const K& key)
        {
            Hash hs;
            KeyOfT kot;
            size_t hashi = hs(key) % _tables.size();
            Node* cur = _tables[hashi];
            Node* prev = nullptr;
            while (cur)
            {
                if (kot(cur->_data) == key)
                {
                    if (prev == nullptr) // cur == _tables[hashi]
                    {
                        _tables[hashi]->_next = cur->_next;
                    }
                    else
                    {
                        prev->_next = cur->_next;
                    }
                    delete cur;
                    --_n;
                    return true;
                }
                prev = cur;
                cur = cur->_next;
            }
            return false;
        }
    
    private:
        vector<Node*> _tables;
        size_t _n = 0;
    };
2.4 begin() end()
iterator begin()
    {
        for (size_t i = 0; i < _tables.size(); i++)
        {
            if (_tables[i])
            {
                return iterator(_tables[i], this);
      }
        }
        return iterator(nullptr, this);
    }
  iterator end()
    {
        return iterator(nullptr, this);
    }

三、unordered_map unordered_set 封装

unordered_map

template<class K, class V, class Hash = HashFunc<K>>
  class unordered_map
  {
  public:
    struct MapKeyOfT
    {
      K operator()(const pair<K, V>& kv)
      {
        return kv.first;
      }
    };
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
      return _ht.Insert(kv);
    }
    iterator find(const K& key)
    {
      return _ht.Find(key);
    }
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    V& operator[](const K& key)
    {
      pair<iterator, bool> ret = insert(make_pair(key, V()));
      return ret.first->second;
    }
  private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
  };

unordered_set

template<class K, class Hash = HashFunc<K>>
  class unordered_set
  {
  public:
    struct SetKeyOfT
    {
      K operator()(const K& key)
      {
        return key;
      }
    };
    typedef typename HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    pair<iterator, bool> insert(const K& key)
    {
      return _ht.Insert(key);
    }
    iterator find(const K& key)
    {
      return _ht.Find(key);
    }
  private:
    HashTable<K, const K, SetKeyOfT, Hash> _ht;
  };
注意:

如果直接将以上代码在 VS 中运行,会出现以下几个错误:


该问题的原因是,__Hash_Iterator 之前并未声明 HashTable

template<class K, class T, class KeyOfT, class Hash> // 不能加缺省值
  class HashTable; // 声明
  template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
  struct __Hash_Iterator
  {
        // ...
    };

该问题在于,__Hash_Iterator 无法访问 HashTableprivate 成员变量,解决办法是将 __Hash_Iterator 写成 HashTable 的友元类

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
  class HashTable
  {
  public:
    template<class K, class T, class KeyOfT, class Hash> // 不能加缺省值
    friend struct __Hash_Iterator; // 友元
        // ...
    };

四、完整代码

My_Unordered_Map.h
#include "HashTable.h"
namespace MY_Test
{
  template<class K, class V, class Hash = HashFunc<K>>
  class unordered_map
  {
  public:
    struct MapKeyOfT
    {
      K operator()(const pair<K, V>& kv)
      {
        return kv.first;
      }
    };
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
      return _ht.Insert(kv);
    }
    iterator find(const K& key)
    {
      return _ht.Find(key);
    }
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    V& operator[](const K& key)
    {
      pair<iterator, bool> ret = insert(make_pair(key, V()));
      return ret.first->second;
    }
  private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
  };
  void test_map1()
  {
    unordered_map<string, string> dict;
    dict.insert(make_pair("sort", "排序"));
    dict.insert(make_pair("left", "左边"));
    dict.insert(make_pair("right", "右边"));
    for (auto& kv : dict)
    {
      //kv.first += 'x';
      kv.second += 'y';
      cout << kv.first << ":" << kv.second << endl;
    }
  }
  void test_map2()
  {
    string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
  "苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" };
    unordered_map<string, int> countMap;
    for (auto& e : arr)
    {
      /*if (e == "ݮ")
      {
        int i = 0;
      }*/
      countMap[e]++;
    }
    for (auto& kv : countMap)
    {
      cout << kv.first << ":" << kv.second << endl;
    }
    cout << endl;
  }
}
My_Unordered_Set.h
#include "HashTable.h"
namespace MY_Test
{
  template<class K, class Hash = HashFunc<K>>
  class unordered_set
  {
  public:
    struct SetKeyOfT
    {
      K operator()(const K& key)
      {
        return key;
      }
    };
    typedef typename HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    pair<iterator, bool> insert(const K& key)
    {
      return _ht.Insert(key);
    }
    iterator find(const K& key)
    {
      return _ht.Find(key);
    }
  private:
    HashTable<K, const K, SetKeyOfT, Hash> _ht;
  };
  void test_set1()
  {
    unordered_set<int> us;
    us.insert(3);
    us.insert(1);
    us.insert(5);
    us.insert(15);
    us.insert(45);
    us.insert(7);
    unordered_set<int>::iterator it = us.begin();
    while (it != us.end())
    {
      //*it += 100;
      cout << *it << " ";
      ++it;
    }
    cout << endl;
     for (auto e : us)
    {
      cout << e << " ";
    }
    cout << endl;
  }
}
HashTable.h
#include <vector>
template<class K>
struct HashFunc
{
    size_t operator()(const K& key)
    {
        size_t hash = key;
        return hash;
    }
};
template<>
struct HashFunc<string>
{
    size_t operator()(const string& s)
    {
        size_t hash = 0;
        for (auto e : s)
        {
            hash = hash * 131 + e;
        }
        return hash;
    }
};
template<class T>
struct HashNode
{
    HashNode<T>* _next;
    T _data;
    HashNode(const T& data)
        :_next(nullptr)
        , _data(data)
    {}
};
template<class K, class T, class KeyOfT, class Hash> // 不能加缺省值
class HashTable; // 声明
template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
struct __Hash_Iterator
{
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOfT, Hash> HashTable;
    typedef __Hash_Iterator<K, T, KeyOfT, Hash> Self;
    Node* _node;
    HashTable* _ht;
    __Hash_Iterator(Node* node, HashTable* ht)
        :_node(node)
        , _ht(ht)
    {}
    Self& operator++()
    {
        if (_node->_next)
        {
            _node = _node->_next;
        }
        else
        {
            Hash hs;
            KeyOfT kot;
            size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();
            hashi++; // 从当前位置的后一个位置开始查找
            while (hashi < _ht->_tables.size())
            {
                if (_ht->_tables[hashi])
                {
                    // 找到下一个位置,跳出循环
                    _node = _ht->_tables[hashi];
                    break;
                }
                ++hashi;
            }
            if (hashi == _ht->_tables.size()) // 遍历结束,没有下一个节点
            {
                _node = nullptr;
            }
        }
        return *this;
    }
    bool operator!=(const Self& s)
    {
        return _node != s._node;
    }
    T& operator*()
    {
        return _node->_data;
    }
    T* operator->()
    {
        return &_node->_data;
    }
};
template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable
{
public:
    typedef HashNode<T> Node;
    typedef __Hash_Iterator<K, T, KeyOfT, Hash> iterator;
    template<class K, class T, class KeyOfT, class Hash> // 不能加缺省值
    friend struct __Hash_Iterator; // 友元
    HashTable()
    {
        _tables.resize(10);
    }
    pair<iterator, bool> Insert(const T& data)
    {
        KeyOfT kot;
        Hash hs;
        iterator ret = Find(kot(data)); // 未实现的 Find,返回值为 iterator
        if (ret != end())// 找到了
        {
            return make_pair(ret, false);
        }
        // 扩容 
        if (_n == _tables.size())
        {
            vector<Node*> newTables(_tables.size() * 2, nullptr);
            for (size_t i = 0; i < _tables.size(); ++i)
            {
                Node* cur = _tables[i];
                while (cur)
                {
                    Node* next = cur->_next;
                    size_t hashi = hs(kot(cur->_data)) % newTables.size();
                    cur->_next = newTables[hashi];
                    newTables[hashi] = cur;
                    cur = next;
                }
                _tables[i] = nullptr;
            }
            _tables.swap(newTables);
        }
        // 插入
        
        size_t hashi = hs(kot(data)) % _tables.size(); // kot(data) -- 取键值;hs(键值) -- 计算映射值
        Node* newNode = new Node(data);
        newNode->_next = _tables[hashi];
        _tables[hashi] = newNode;
        _n++;
        return make_pair(iterator(newNode, this), true); // 
    }
    iterator Find(const K& key)
    {
        Hash hs;
        KeyOfT kot;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        while (cur)
        {
            if (kot(cur->_data) == key)
            {
                return iterator(cur, this);
            }
            cur = cur->_next;
        }
        return iterator(nullptr, this);
    }
    bool Erase(const K& key)
    {
        Hash hs;
        KeyOfT kot;
        size_t hashi = hs(key) % _tables.size();
        Node* cur = _tables[hashi];
        Node* prev = nullptr;
        while (cur)
        {
            if (kot(cur->_data) == key)
            {
                if (prev == nullptr) // cur == _tables[hashi]
                {
                    _tables[hashi]->_next = cur->_next;
                }
                else
                {
                    prev->_next = cur->_next;
                }
                delete cur;
                --_n;
                return true;
            }
            prev = cur;
            cur = cur->_next;
        }
        return false;
    }
    iterator begin()
    {
        for (size_t i = 0; i < _tables.size(); i++)
        {
            if (_tables[i])
            {
                return iterator(_tables[i], this);
            }
        }
        return iterator(nullptr, this);
    }
    iterator end()
    {
        return iterator(nullptr, this);
    }
private:
    vector<Node*> _tables;
    size_t _n = 0;
};
相关文章
|
11天前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
46 18
你对Collection中Set、List、Map理解?
|
4天前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
40 20
|
21天前
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
27 3
【C++】map、set基本用法
|
21天前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
18 5
|
2月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
3月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
44 6
【数据结构】map&set详解
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
41 1
|
3月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
40 5
|
3月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
3月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用