【C++从0到王者】第三十七站:模拟unordered_map和unordered_set

简介: 【C++从0到王者】第三十七站:模拟unordered_map和unordered_set


一、哈希表的修改

如下是我们一开始的哈希表

namespace hash_bucket
{
  template<class K,class V>
  struct HashNode
  {
    pair<K, V> _kv;
    HashNode<K, V>* _next = nullptr;
    HashNode(const pair<K, V>& kv)
      :_kv(kv)
    {}
  };
  template<class K>
  struct DefaultHashFunc
  {
    size_t operator()(const K& key)
    {
      return (size_t)key;
    }
  };
  template<>
  struct DefaultHashFunc<string>
  {
    size_t operator()(const string& str)
    {
      size_t hashi = 0;
      for (auto ch : str)
      {
        hashi = hashi * 131 + ch;
      }
      return hashi;
    }
  };
  template<class K, class V, class HashFunc = DefaultHashFunc<K>>
  class HashTable
  {
    typedef HashNode<K,V> Node;
  public:
    HashTable()
      :_n(0)
    {
      _table.resize(10, nullptr);
    }
    bool Insert(const pair<K, V>& kv)
    {
      if (Find(kv.first))
      {
        return false;
      }
      HashFunc hf;
      if (_n == _table.size())
      {
        size_t newSize = _table.size() * 2;
        vector<Node*> newTable(newSize, nullptr);
        for (int i = 0; i < _table.size(); i++)
        {
          Node* cur = _table[i];
          while (cur)
          {
            Node* next = cur->_next;
            size_t hashi = hf(cur->_kv.first) % newTable.size();
            cur->_next = newTable[hashi];
            newTable[hashi] = cur;
            cur = next;
          }
          _table[i] = nullptr;
        }
        _table.swap(newTable);
      }
      size_t hashi = hf(kv.first) % _table.size();
      Node* newnode = new Node(kv);
      newnode->_next = _table[hashi];
      _table[hashi] = newnode;
      ++_n;
      return true;
    }
    void Print()
    {
      for (int i = 0; i < _table.size(); i++)
      {
        Node* cur = _table[i];
        printf("[%d]->", i);
        while (cur)
        {
          cout << cur->_kv.first << ":" << cur->_kv.second << "->";
          cur = cur->_next;
        }
        cout << "NULL" << endl;
      }
    }
    Node* Find(const K& key)
    {
      HashFunc hf;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      while (cur)
      {
        if (cur->_kv.first == key)
        {
          return cur;
        }
        cur = cur->_next;
      }
      return nullptr;
    }
    bool Erase(const K& key)
    {
      HashFunc hf;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      Node* prev = nullptr;
      while (cur)
      {
        if (cur->_kv.first == key)
        {
          if (prev)
          {
            prev->_next = cur->_next;
          }
          else
          {
            _table[hashi] = cur->_next;
          }
          delete cur;
          cur = nullptr;
          _n--;
          return true;
        }
        else
        {
          prev = cur;
          cur = cur->_next;
        }
      }
      return false;
    }
    ~HashTable()
    {
      for (int i = 0; i < _table.size(); i++)
      {
        Node* cur = _table[i];
        while (cur)
        {
          Node* next = cur->_next;
          delete cur;
          cur = next;
        }
        _table[i] = nullptr;
      }
    }
  private:
    vector<Node*> _table;
    size_t _n;
  };
}

现在为了将他改装位unordered系列容器,我们还需要对这个哈希表做出一些修改

对于哈希表的修改与红黑树去封装map和set是非常类似的。

首先是将结点都改为T类型的,这个T类型对于set而言是K,对于map而言是pair<K,V>

然后接下来我们继续修改其他接口,我们先来修改插入接口,首先是该类型为T类型

但是这样的话,会导致,没有kv了,而Find接口需要一个K类型的,对于这个问题,我们与红黑树的解决办法一样

然后我们去修改Insert里面的问题,最终修改为如下所示

bool Insert(const T& data)
    {
      KeyOfT kot;
      if (Find(kot(data)))
      {
        return false;
      }
      HashFunc hf;
      if (_n == _table.size())
      {
        size_t newSize = _table.size() * 2;
        vector<Node*> newTable(newSize, nullptr);
        for (int i = 0; i < _table.size(); i++)
        {
          Node* cur = _table[i];
          while (cur)
          {
            Node* next = cur->_next;
            size_t hashi = hf(kot(cur->_data)) % newTable.size();
            cur->_next = newTable[hashi];
            newTable[hashi] = cur;
            cur = next;
          }
          _table[i] = nullptr;
        }
        _table.swap(newTable);
      }
      size_t hashi = hf(kot(data)) % _table.size();
      Node* newnode = new Node(data);
      newnode->_next = _table[hashi];
      _table[hashi] = newnode;
      ++_n;
      return true;
    }

接下来修改Find函数,加上kot的逻辑即可

Node* Find(const K& key)
    {
      HashFunc hf;
      KeyOfT kot;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          return cur;
        }
        cur = cur->_next;
      }
      return nullptr;
    }

然后是删除逻辑

bool Erase(const K& key)
    {
      HashFunc hf;
      KeyOfT kot;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      Node* prev = nullptr;
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          if (prev)
          {
            prev->_next = cur->_next;
          }
          else
          {
            _table[hashi] = cur->_next;
          }
          delete cur;
          cur = nullptr;
          _n--;
          return true;
        }
        else
        {
          prev = cur;
          cur = cur->_next;
        }
      }
      return false;
    }

有了上面的改造哈希表,我们就可以先暂时的使用一下unordered_map和set

void test1()
{
  Sim::unordered_map<int, int> um;
  um.insert(make_pair(1, 1));
  um.insert(make_pair(5, 5));
  um.insert(make_pair(2, 2));
  um.insert(make_pair(3, 3));
  Sim::unordered_set<int> us;
  us.insert(1);
  us.insert(5);
  us.insert(3);
  us.insert(2); 
}

二、迭代器

1.普通迭代器

对于哈希表的迭代器,我们需要注意的有以下几点

首先是最为核心的功能++,他该如何++是个麻烦,如果是再当前桶的下一个还有结点,那是最好的情况,可以直接向后指即可。但是如果当前桶已经完了,如何去找下一个桶呢?

我们的办法是这样的,再去定义一个哈希表的指针,因为有了哈希表的指针,我们就可以去访问哈希表中的容量,根据我们当前迭代器指向的数据,我们可以计算出当前在哪一个桶里面,然后我们在从下一个桶里开始,去依次寻找桶中有数据的元素即可。

如下就是迭代器的设计

template<class K, class T, class KeyOfT, class HashFunc>
  struct HTIterator
  {
    typedef HashNode<T> Node;
    typedef HTIterator<K, T, KeyOfT, HashFunc> Self;
    Node* _node;
    HashTable<K, T, KeyOfT, HashFunc>* _pht;
    HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
      :_node(node)
      ,_pht(pht)
    {}
    Self& operator++()
    {
      if (_node->_next)
      {
        _node = _node->_next;
      }
      else
      {
        KeyOfT kot;
        HashFunc hf;
        size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
        ++hashi;
        while (hashi < _pht->_table.size())
        {
          if (_pht->_table[hashi])
          {
            _node = _pht->_table[hashi];
            return *this;
          }
          ++hashi;
        }
        _node = nullptr;
      }
      return *this;
    }
    bool operator!=(const Self& ht)
    {
      return _node != ht._node;
    }
    bool operator==(const Self& ht)
    {
      return _node == ht._node;
    }
    T& operator*()
    {
      return _node->_data;
    }
    T* operator->()
    {
      return &_node->_data;
    }
  };

不过需要特别注意的是,我们必须得在哈希表中有一个友元声明,因为迭代器中需要去通过访问哈希表的底层数组的大小。而这个数组是一个私有成员变量,所以我们需要使用友元。

还需要注意的是,在迭代器中,我们使用了哈希表指针,在哈希表中,使用了迭代器。因为迭代器中只用到了哈希表的指针,所以需要在迭代器前面有一个哈希表的前置声明,让迭代器中的类型可以通过编译。

如下就是map和set中的迭代器函数了

使用如下的测试用例

void test1()
{
  Sim::unordered_map<int, int> um;
  um.insert(make_pair(1, 1));
  um.insert(make_pair(5, 5));
  um.insert(make_pair(2, 2));
  um.insert(make_pair(3, 3));
  Sim::unordered_map<int, int>::iterator umit = um.begin();
  while (umit != um.end())
  {
    cout << umit->first << ":" << umit->second << endl;
    ++umit;
  }
  cout << endl;
  Sim::unordered_map<string, string> ums;
  ums.insert(make_pair("sort", "排序"));
  ums.insert(make_pair("insert", "插入"));
  ums.insert(make_pair("delete", "删除"));
  ums.insert(make_pair("begin", "开始"));
  for (auto e : ums)
  {
    cout << e.first << ":" << e.second << endl;
  }
  cout << endl;
  Sim::unordered_set<int> us;
  us.insert(1);
  us.insert(5);
  us.insert(3);
  us.insert(2);
  Sim::unordered_set<int>::iterator usit = us.begin();
  while (usit != us.end())
  {
    cout << *usit << " ";
    ++usit;
  }
  cout << endl;
}

运行结果如下所示

2.const迭代器

看似上面的代码已经可以跑了,但是还是存在一些问题,因为我们还没有解决const迭代器的问题,这就导致了,在set中key可以被修改,在map中,key也可以被修改,这是一个很严重的问题。所以现在我们需要来解决他

我们先来处理const迭代器的问题

const的迭代器的处理思路还是和之前一样的,多传两个模板参数来解决

这样一来,我们就成功的将普通迭代器和const迭代器都处理好了

如下所示,我们再将set中的迭代器给处理好,让无论是const对象还是普通对象,都只能去访问const迭代器。无论是const迭代器还是普通迭代器其实都是const迭代器

不过在这里我们会会遇到一个新的问题

这里的问题其实报错报的不是很明显,下面这个更详细一些

其实这里是因为,this指针出现了权限放大的问题

因为我们set要使用的时候统统都把this指针当成了const修饰以后的this指针。而我们迭代器的构造函数中这个哈希表指针的类型是一个普通的类型,所以权限放大了

那么我们该如何解决呢?

有两种思路,一种是直接重载然后强制类型转换

这样的编译器也确实可以通过

还有一种方案就是直接将原来的内置成员类型改为const指针

为什么敢将这个指针改为const呢?

因为我们这个迭代器类中,并没有通过这个指针去修改哈希表中的任何事情,我们仅仅只是利用这个哈希表指针去读取哈希表的大小以及一些结点的指针。所以我们才敢将这个指针改为const

我们在这个迭代器中一切的修改都是使用node指针去进行修改的,只有通过它的解引用才可以实现

如此一来,哈希表中的迭代器已经可以跑起来了,那么问题又来了,可不可以不要第一个构造呢?

其实是可以的,如果没有第一个构造函数,因为第二个也是可以用的,因为仅仅这里就只是权限的缩小。即便普通迭代器也是可以传过来的

前面是对于set的处理,现在来处理以下map的,对于map的就很简单了,我们直接加上一个const即可,这样的话对于普通的map对象,由于底层第一个参数是const类型的,那么即便迭代器返回了,也是无法修改的。

3.插入返回值

解决了const迭代器的问题,接下来我们会发现我们的插入的返回值还是一个bool值,事实上应该是一个pair类型的,这一点也是为了支撑map的[]运算符重载

如下所示,我们先改变哈希表的insert

要改变哈希表的insert,我们还需要去改变find的返回值,它应该返回一个迭代器

不过在这里会出现跟使用红黑树封装set时一样的问题

这里是因为unordered_set中的iterator其实是const_iterator。所以是两个完全不一样的类型。当然不可以直接进行转化了。

不过在这里我们可能会有一些疑惑,为什么下面这段代码应该也涉及到了这个问题,但是在之前的测试中没有任何问题呢?

上面的这个问题可以等效为下面的问题

明明是两个不一样的类型,为什么不会报错呢?

其实这就是拷贝构造函数的妙用了

在pair<K,V>类中,其实类里面重载了两个参数都为const,或者其中一个为const的情况,这里的其实就是一个利用普通对象去构造const对象的妙用。

上面的问题还可以进一步在list的代码中找到

在这里,我们之前模拟实现过list,我们也知道,这里的迭代器其实也是一个对象,lt是一个普通的对象,它的bengin自然就是一个普通迭代器,那么第三行代码中,普通迭代器对象可以直接赋值给const迭代器对象。我们知道里面的这两个迭代器是完全不同的两个对象,至于他们为什么会支持,就是因为重载了一个拷贝构造函数,当这个迭代器为普通迭代器的时候,它其实没有什么用,就算不写它编译器也会默认生成一个拷贝构造函数。当这个迭代器为const迭代器的时候,那么这里就不是拷贝构造函数了,而是构造函数,这个构造函数是利用一个普通迭代器去构造一个const迭代器的。

类似的场景我们在前面使用红黑树的时候我们也使用过

我们当时也是通过添加一个拷贝构造函数来完成的

所以本来模板实例化后不同的类型是不可以相互转化的,但是由于拷贝构造函数的妙用,让这个拷贝构造函数有了双重意义以后,便行得通了。使得模板参数中带有const的类型可以使用普通的进行构造。

类似与下面的代码

好了现在回过头来,虽然举了四个例子,但是目标还是不能忘了,我们引发上面思考的原因就是因为类型不匹配问题所导致的,现在我们有了上面的如何用普通迭代器构造const迭代器的思路以后。我们能否将这个思路运用到我们的代码上呢?毕竟我们现在代码遇到的问题就是哈希表返回的是一个普通迭代器,而我们set需要返回一个const迭代器。

即现在,我们需要用这个普通迭代器构造一个const迭代器,那么我们就需要写一个具有双重意义的拷贝构造函数了

有了在这个拷贝构造函数以后,我们在insert中这样写就可以了

至此,我们的迭代器就终于实现了!!!

4.unordered_map的operator[]

这个有了insert的基础以后,就非常简单了

测试也是没有任何问题的

三、完整代码

HashTable.cpp文件

#pragma once
namespace hash_bucket
{
  template<class K>
  struct DefaultHashFunc
  {
    size_t operator()(const K& key)
    {
      return (size_t)key;
    }
  };
  template<>
  struct DefaultHashFunc<string>
  {
    size_t operator()(const string& str)
    {
      size_t hashi = 0;
      for (auto ch : str)
      {
        hashi = hashi * 131 + ch;
      }
      return hashi;
    }
  };
  template<class T>
  struct HashNode
  {
    T _data;
    HashNode<T>* _next = nullptr;
    HashNode(const T& data)
      :_data(data)
    {}
  };
  //前置声明
  template<class K, class T, class KeyOfT, class HashFunc>
  class HashTable;
  template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
  struct HTIterator
  {
    typedef HashNode<T> Node;
    typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;
    typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;
    Node* _node;
    const HashTable<K, T, KeyOfT, HashFunc>* _pht;
    //HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
    //  :_node(node)
    //  ,_pht(pht)
  //    {}
    HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
      :_node(node)
      , _pht(pht)
    {}
    HTIterator(const Iterator& it)
      :_node(it._node)
      ,_pht(it._pht)
    {}
    //HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
    //  :_node(node)
    //  //, _pht(pht)
    //{
    //  _pht = (HashTable<K, T, KeyOfT, HashFunc>*)pht;
    //}
    Self& operator++()
    {
      if (_node->_next)
      {
        _node = _node->_next;
      }
      else
      {
        KeyOfT kot;
        HashFunc hf;
        size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
        ++hashi;
        while (hashi < _pht->_table.size())
        {
          if (_pht->_table[hashi])
          {
            _node = _pht->_table[hashi];
            return *this;
          }
          ++hashi;
        }
        _node = nullptr;
      }
      return *this;
    }
    bool operator!=(const Self& ht)
    {
      return _node != ht._node;
    }
    bool operator==(const Self& ht)
    {
      return _node == ht._node;
    }
    Ref operator*()
    {
      return _node->_data;
    }
    Ptr operator->()
    {
      return &_node->_data;
    }
  };
  template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
  class HashTable
  {
    typedef HashNode<T> Node;
    template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
    friend struct HTIterator;
  public:
    typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
    typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;
    iterator begin()
    {
      Node* cur = nullptr;
      for (int i = 0; i < _table.size(); i++)
      {
        if (_table[i])
        {
          cur = _table[i];
          break;
        }
      }
      return iterator(cur, this);
    }
    iterator end()
    {
      return iterator(nullptr, this);
    }
    const_iterator begin() const
    {
      Node* cur = nullptr;
      for (int i = 0; i < _table.size(); i++)
      {
        if (_table[i])
        {
          cur = _table[i];
          break;
        }
      }
      return const_iterator(cur, this);
    }
    const_iterator end() const
    {
      return const_iterator(nullptr, this);
    }
    HashTable()
      :_n(0)
    {
      _table.resize(10, nullptr);
    }
    pair<iterator, bool> Insert(const T& data)
    {
      KeyOfT kot;
      iterator it = Find(kot(data));
      if (it != end())
      {
        return make_pair(it, false);
      }
      HashFunc hf;
      if (_n == _table.size())
      {
        size_t newSize = _table.size() * 2;
        vector<Node*> newTable(newSize, nullptr);
        for (int i = 0; i < _table.size(); i++)
        {
          Node* cur = _table[i];
          while (cur)
          {
            Node* next = cur->_next;
            size_t hashi = hf(kot(cur->_data)) % newTable.size();
            cur->_next = newTable[hashi];
            newTable[hashi] = cur;
            cur = next;
          }
          _table[i] = nullptr;
        }
        _table.swap(newTable);
      }
      size_t hashi = hf(kot(data)) % _table.size();
      Node* newnode = new Node(data);
      newnode->_next = _table[hashi];
      _table[hashi] = newnode;
      ++_n;
      return make_pair(iterator(newnode, this), true);
    }
    iterator Find(const K& key)
    {
      HashFunc hf;
      KeyOfT kot;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          return iterator(cur, this);
        }
        cur = cur->_next;
      }
      return iterator(nullptr, this);
    }
    bool Erase(const K& key)
    {
      HashFunc hf;
      KeyOfT kot;
      size_t hashi = hf(key) % _table.size();
      Node* cur = _table[hashi];
      Node* prev = nullptr;
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          if (prev)
          {
            prev->_next = cur->_next;
          }
          else
          {
            _table[hashi] = cur->_next;
          }
          delete cur;
          cur = nullptr;
          _n--;
          return true;
        }
        else
        {
          prev = cur;
          cur = cur->_next;
        }
      }
      return false;
    }
    ~HashTable()
    {
      for (int i = 0; i < _table.size(); i++)
      {
        Node* cur = _table[i];
        while (cur)
        {
          Node* next = cur->_next;
          delete cur;
          cur = next;
        }
        _table[i] = nullptr;
      }
    }
  private:
    vector<Node*> _table;
    size_t _n;
  };
}

unordered_map.h文件

#pragma once
namespace Sim
{
  template<class K,class V>
  class unordered_map
  {
    struct MapKeyOfT
    {
      const K& operator()(const pair<K, V>& kv)
      {
        return kv.first;
      }
    };
  public:
    typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
    typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
    iterator begin()
    {
      return _ht.begin();
    }
    iterator end()
    {
      return _ht.end();
    }
    const_iterator begin() const
    {
      return _ht.begin();
    }
    const_iterator end() const
    {
      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:
    hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
  };
}

unordered_set.h文件

#pragma once
namespace Sim
{
  template<class K>
  class unordered_set
  {
    struct SetKeyOfT
    {
      const K& operator()(const K& key)
      {
        return key;
      }
    };
  public:
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;
    const_iterator begin() const
    {
      return _ht.begin();
    }
    const_iterator end() const
    {
      return _ht.end();
    }
    pair<iterator, bool> insert(const K& key)
    {
      pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
      return pair<iterator, bool>(ret.first, ret.second);
    }
  private:
    hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
  };
}

test.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
#include <string>
#include <list>
using namespace std;
#include "HashTable.h"
#include "my_unordered_map.h"
#include "my_unordered_set.h"
void test1()
{
  Sim::unordered_map<int, int> um;
  um.insert(make_pair(1, 1));
  um.insert(make_pair(5, 5));
  um.insert(make_pair(2, 2));
  um.insert(make_pair(3, 3));
  Sim::unordered_map<int, int>::iterator umit = um.begin();
  while (umit != um.end())
  {
    cout << umit->first << ":" << umit->second << endl;
    ++umit;
  }
  cout << endl;
  Sim::unordered_map<string, string> ums;
  ums.insert(make_pair("sort", "排序"));
  ums.insert(make_pair("insert", "插入"));
  ums.insert(make_pair("delete", "删除"));
  ums.insert(make_pair("begin", "开始"));
  for (auto e : ums)
  {
    //e.first += 'x';
    //e.second += 'x';
    cout << e.first << ":" << e.second << endl;
  }
  cout << endl;
  ums["insert"] = "xxx";
  ums["proceess"] = "yyyy";
  for (auto e : ums)
  {
    cout << e.first << ":" << e.second << endl;
  }
  cout << endl;
  Sim::unordered_set<int> us;
  us.insert(1);
  us.insert(5);
  us.insert(3);
  us.insert(2);
  Sim::unordered_set<int>::iterator usit = us.begin();
  while (usit != us.end())
  {
    //*usit = 1;
    cout << *usit << " ";
    ++usit;
  }
  cout << endl;
}
//void test2()
//{
//  pair<int, int> p1(1, 1);
//  pair<const int, const int> p2(p1);
//  pair<int, int> p3(p2);
//}
//void test3()
//{
//  list<int> lt;
//  list<int>::iterator it1 = lt.begin();
//  list<int>::const_iterator it2 = lt.begin();
//
//}
//
//template<class T,class Ref>
//class A
//{
//public:
//  A(Ref a)
//    :_a(a)
//  {}
//  A(const A<T, T&>& a)
//    :_a(a._a)
//  {}
//
//  int _a;
//};
//void test4()
//{
//  int x = 1;
//  A<int, int&> a(x);
//  A<int, const int&> b(a);
//}
int main()
{
  test1();
  return 0;
}
相关文章
|
9天前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
14 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个高频单词等问题的解决方案。
41 6
【数据结构】map&set详解
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
39 1
|
3月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
38 5
|
3月前
|
存储 JavaScript 前端开发
js的map和set |21
js的map和set |21
|
3月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
|
4月前
|
存储 安全 Java
java集合框架复习----(4)Map、List、set
这篇文章是Java集合框架的复习总结,重点介绍了Map集合的特点和HashMap的使用,以及Collections工具类的使用示例,同时回顾了List、Set和Map集合的概念和特点,以及Collection工具类的作用。
java集合框架复习----(4)Map、List、set
|
4月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
4月前
|
存储 JavaScript 前端开发
ES6新特性(四): Set 和 Map
ES6新特性(四): Set 和 Map