unordered_set和unordered_map的封装

简介: unordered_set和unordered_map的封装



一、前言

在C++11中,STL提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,但是效率却提高了。

unordered系列的关联式容器之所以效率比较高,是因为unordered系列的关联式容器的底层使用了我们前面所讲到的哈希结构。那么今天我们就来看看 unordered_set和unordered_map这两个容器的使用及其实现。


二、容器的使用

1、unordered_map

1、 unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。

2、 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。

3、 在内部unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。

4、 unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。

5、unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。

int main()
{
  unordered_map<string, int> countMap;
  string arr[] = { "苹果","香蕉","苹果","葡萄","西瓜" };
  for (auto& e : arr)
  {
    auto it = countMap.find(e);
    /*if (it == countMap.end())
    {
      countMap.insert(make_pair(e, 1));
    }
    else
    {
      it->second++;
    }*/
    countMap[e]++;
  }
  for (auto& kv : countMap)
  {
    cout << kv.first << ":" << kv.second << endl;
  }
  return 0;
}

从运行结果我们可以看出 unordered_map里面的数据是无序且数据是可以有重复的。

2、unordered_set

1、不再以键值对的形式存储数据,而是直接存储数据的值。

2、容器内部存储的各个元素的值都互不相等,且不能被修改。

3、不会对内部存储的数据进行排序。

int main()
{
  unordered_set<int> us;
  us.insert(10);
  us.insert(1);
  us.insert(10);
  us.insert(3);
  us.insert(4);
  us.insert(4);
  auto it = us.begin();
  while (it != us.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  return 0;
}

从运行结果我们可以看出 unordered_set里面的数据是无序且没有重复的。


三、哈希表的改造

从标准库中我们知道,unordered_set和unordered_map这两个容器的底层结构都是哈希表。但是两个容器所存储的数据类型不同,前者存储的是 key类型的数据,后者存储的是 key/value类型的数据。所以为了方便,我们需要像实现 map和set的底层红黑树那样,去实现一个通用的哈希表。所以我们需要对哈希表改造一下,来满足两个容器都可以使用。

1、结点

首先,我们需要对节点改造,使其能够满足两种存储方式。

template<class T>
struct HashNode
{
  T _data;
  HashNode<T>* _next;
  HashNode(const T& data)
    :_data(data)
    , _next(nullptr)
  {}
};

2、哈希表的迭代器

我们需要对哈希表指针和结点指针进行封装。

template<class K, class T, class Hash, class KeyOfT>
class HashTable;
template<class K, class T, class Hash, class KeyOfT>
struct _HashIterator
{
  typedef HashNode<T> Node;
  typedef HashTable<K, T, Hash, KeyOfT> HT;
  typedef _HashIterator<K, T, Hash, KeyOfT> Self;
  Node* _node;
  HT* _ht;
};

* 构造函数

_HashIterator(Node* node, HT* ht)
  :_node(node)
  , _ht(ht)
{}

* 重载 *

T& operator*()
{
  return _node->_data;
}

* 重载 ->

T* operator->()
{
  return &_node->_data;
}

* 重载 ++

思路:如果当前的哈希桶还有节点(即下一个结点不为空),那么++就是当前桶的当前节点的下一个节点,如果当前元素是所在桶的最后一个元素,那么++就是找下一个非空的哈希桶。

Self& operator++()
  {
    if (_node->_next)
    {
      //当前桶迭代
      _node = _node->_next;
    }
    else
    {
      // 找下一个桶
      Hash hash;
      KeyOfT kot;
      size_t i = hash(kot(_node->_data)) % _ht->_tables.size();
      i++;
      for (; i < _ht->_tables.size(); i++)
      {
        if (_ht->_tables[i])
        {
          _node = _ht->_tables[i];
          break;
        }
      }
      //说明后面没有有数据的桶了
      if (i == _ht->_tables.size())
      {
        _node = nullptr;
      }
    }
    return *this;
  }

* 重载 != 和  ==

bool operator!=(const Self& s)const
{
  return _node != s._node;
}
bool operator==(const Self& s)const
{
  return _node == s._node;
}

3、哈希表的析构

~HashTable()
{
  for (size_t i = 0; i < _tables.size(); i++)
  {
    Node* cur = _tables[i];
    while (cur)
    {
      Node* next = cur->_next;
      delete cur;
      cur = next;
    }
    _tables[i] = nullptr;
  }
}

4、unordered_map的 [] 实现

我们知道,unordered_map的操作中可以通过[]来访问数据,并修改value值,那么这是怎么做到的呢?

其实要想实现[],我们需要先把insert的返回值修改成pair<iterator,bool>,最后的返回值也要一起修改。如果有重复的元素就返回这个位置的迭代器,没有重复的就返回新节点newnode的迭代器。

pair<iterator, bool> insert(const T& data)
  {
    Hash hash;
    KeyOfT kot;
    //去重
    iterator ret = Find(kot(data));
    if (ret != end())
    {
      return make_pair(ret, false);
    }
    //扩容
    if (_size == _tables.size())
    {
      size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 10;
      vector<Node*> newTables;
      newTables.resize(newsize, nullptr);
      //将旧表中结点移动映射到新表
      for (size_t i = 0; i < _tables.size(); i++)
      {
        Node* cur = _tables[i];
        while (cur)
        {
          Node* next = cur->_next;
          size_t hashi = hash(kot(cur->_data)) % newTables.size();
          cur->_next = newTables[hashi];
          newTables[hashi] = cur;
          cur = next;
        }
        _tables[i] = nullptr;
      }
      _tables.swap(newTables);
    }
    size_t hashi = hash(kot(data)) % _tables.size();
    Node* newnode = new Node(data);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
    _size++;
    return make_pair(iterator(newnode, this), true);
  }

有了上面的插入,我们就可以在unordered_map中实现[]的操作了。

5、修改后的哈希表

#pragma once
#include<iostream>
#include<string>
#include<vector>
using namespace std;
template<class T>
struct HashNode
{
  T _data;
  HashNode<T>* _next;
  HashNode(const T& data)
    :_data(data)
    , _next(nullptr)
  {}
};
//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
template<class K, class T, class Hash, class KeyOfT>
struct _HashIterator
{
  typedef HashNode<T> Node;
  typedef HashTable<K, T, Hash, KeyOfT> HT;
  typedef _HashIterator<K, T, Hash, KeyOfT> Self;
  Node* _node;
  HT* _ht;
  _HashIterator(Node* node, HT* ht)
    :_node(node)
    , _ht(ht)
  {}
  T& operator*()
  {
    return _node->_data;
  }
  T* operator->()
  {
    return &_node->_data;
  }
  Self& operator++()
  {
    if (_node->_next)
    {
      //当前桶迭代
      _node = _node->_next;
    }
    else
    {
      // 找下一个桶
      Hash hash;
      KeyOfT kot;
      size_t i = hash(kot(_node->_data)) % _ht->_tables.size();
      i++;
      for (; i < _ht->_tables.size(); i++)
      {
        if (_ht->_tables[i])
        {
          _node = _ht->_tables[i];
          break;
        }
      }
      //说明后面没有有数据的桶了
      if (i == _ht->_tables.size())
      {
        _node = nullptr;
      }
    }
    return *this;
  }
  bool operator!=(const Self& s)const
  {
    return _node != s._node;
  }
  bool operator==(const Self& s)const
  {
    return _node == s._node;
  }
};
template<class K>
struct HashFunc
{
  size_t operator()(const K& key)
  {
    return (size_t)key;
  }
};
//特化
template<>
struct HashFunc<string>
{
  size_t operator()(const string& key)
  {
    size_t val = 0;
    for (auto ch : key)
    {
      val *= 131;
      val += ch;
    }
    return val;
  }
};
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
  typedef HashNode<T> Node;
  template<class K, class T, class Hash, class KeyOfT>
  friend struct _HashIterator;
public:
  typedef _HashIterator<K, T, Hash, KeyOfT> iterator;
  iterator begin()
  {
    for (size_t i = 0; i < _tables.size(); i++)
    {
      if (_tables[i])
      {
        return iterator(_tables[i], this);
      }
    }
    return end();
  }
  iterator end()
  {
    return iterator(nullptr, this);
  }
  ~HashTable()
  {
    for (size_t i = 0; i < _tables.size(); i++)
    {
      Node* cur = _tables[i];
      while (cur)
      {
        Node* next = cur->_next;
        free(cur);
        cur = next;
      }
      _tables[i] = nullptr;
    }
  }
  pair<iterator, bool> insert(const T& data)
  {
    Hash hash;
    KeyOfT kot;
    //去重
    iterator ret = Find(kot(data));
    if (ret != end())
    {
      return make_pair(ret, false);
    }
    //扩容
    if (_size == _tables.size())
    {
      size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 10;
      vector<Node*> newTables;
      newTables.resize(newsize, nullptr);
      //将旧表中结点移动映射到新表
      for (size_t i = 0; i < _tables.size(); i++)
      {
        Node* cur = _tables[i];
        while (cur)
        {
          Node* next = cur->_next;
          size_t hashi = hash(kot(cur->_data)) % newTables.size();
          cur->_next = newTables[hashi];
          newTables[hashi] = cur;
          cur = next;
        }
        _tables[i] = nullptr;
      }
      _tables.swap(newTables);
    }
    size_t hashi = hash(kot(data)) % _tables.size();
    Node* newnode = new Node(data);
    newnode->_next = _tables[hashi];
    _tables[hashi] = newnode;
    _size++;
    return make_pair(iterator(newnode, this), true);
  }
  iterator Find(const K& key)
  {
    if (_tables.size() == 0)
      return end();
    Hash hash;
    KeyOfT kot;
    size_t hashi = hash(key) % _tables.size();
    Node* cur = _tables[hashi];
    while (cur)
    {
      if (kot(cur->_data) == key)
        return iterator(cur, this);
      else
        cur = cur->_next;
    }
    return end();
  }
  bool erase(const K& key)
  {
    if (_tables.size() == 0)
    {
      return false;
    }
    Hash hash;
    KeyOfT kot;
    size_t hashi = hash(key) % _tables.size();
    Node* cur = _tables[hashi];
    Node* prev = nullptr;
    while (cur)
    {
      if (kot(cur->_data) == key)
      {
        // 1、头删
        // 2、中间删
        if (prev == nullptr)
        {
          _tables[hashi] = cur->_next;
        }
        else
        {
          prev->_next = cur->_next;
        }
        delete cur;
        _size--;
        return true;
      }
      prev = cur;
      cur = cur->_next;
    }
    return false;
  }
private:
  vector<Node*> _tables;
  size_t _size = 0;
};

四、 unordered_set的实现

#pragma once
#include"HashTable.h"
namespace zdl
{
  template<class K, class Hash = HashFunc<K>>
  class unordered_set
  {
    struct SetKeyOfT
    {
      const K& operator()(const K& key)
      {
        return key;
      }
    };
  public:
    typedef typename HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    pair<iterator, bool> insert(const K& key)
    {
      return _ht.insert(key);
    }
  private:
    HashTable<K, K, Hash, SetKeyOfT> _ht;
  };

五、unordered_map的实现

#pragma once
#include"HashTable.h"
namespace zdl
{
  template<class K, class V, class Hash = HashFunc<K>>
  class unordered_map
  {
    struct MapKeyOfT
    {
      const K& operator()(const pair<K, V>& kv)
      {
        return kv.first;
      }
    };
  public:
    typedef typename HashTable<K, pair<K, V>, Hash, MapKeyOfT>::iterator iterator;
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
      return _ht.insert(kv);
    }
    V& operator[](const K& key)
    {
      pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
      return ret.first->second;
    }
  private:
    HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
  };
}
目录
相关文章
|
1月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 Java API
【数据结构】map&set详解
本文详细介绍了Java集合框架中的Set系列和Map系列集合。Set系列包括HashSet(哈希表实现,无序且元素唯一)、LinkedHashSet(保持插入顺序的HashSet)、TreeSet(红黑树实现,自动排序)。Map系列为双列集合,键值一一对应,键不可重复,值可重复。文章还介绍了HashMap、LinkedHashMap、TreeMap的具体实现与应用场景,并提供了面试题示例,如随机链表复制、宝石与石头、前K个高频单词等问题的解决方案。
38 6
【数据结构】map&set详解
|
1月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
38 1
|
2月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
37 5
|
2月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
2月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
3月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
3月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
3月前
|
存储 JavaScript 前端开发
ES6新特性(四): Set 和 Map
ES6新特性(四): Set 和 Map
|
3月前
|
存储 Java 索引