【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;
}
相关文章
|
3月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
297 1
|
6月前
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
506 1
|
3月前
|
存储 算法 容器
set_map的实现+set/map加持秒杀高频算法题锻炼算法思维
`set`基于红黑树实现,支持有序存储、自动去重,增删查效率为O(logN)。通过仿函数可自定义排序规则,配合空间配置器灵活管理内存。不支持修改元素值,迭代器失效需注意。`multiset`允许重复元素。常用于去重、排序及查找场景。
|
7月前
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
369 121
|
10月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
289 2
|
10月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
579 73
|
7月前
|
存储 C++ 容器
unordered_set、unordered_multiset、unordered_map、unordered_multimap的介绍及使用
unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。在unordered_set中,元素的值同时也是唯一地标识它的key。在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。它的迭代器至少是前向迭代器。前向迭代器的特性。
337 0
|
7月前
|
编译器 C++ 容器
用一棵红黑树同时封装出map和set
再完成上面的代码后,我们的底层代码已经完成了,这时候已经是一个底层STL的红黑树了,已经已符合库里面的要求了,这时候我们是需要给他穿上对应的“衣服”,比如穿上set的“衣服”,那么这个穿上set的“衣服”,那么他就符合库里面set的要求了,同样map一样,这时候我们就需要实现set与map了。因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。我们就可以使用仿函数了。
106 0
|
7月前
|
存储 编译器 容器
set、map、multiset、multimap的介绍及使用以及区别,注意事项
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。set默认是升序的,但是其内部默认不是按照大于比较,而是按照小于比较。set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
284 0