【C++】开散列实现unordered_map与unordered_set的封装

简介: 【C++】开散列实现unordered_map与unordered_set的封装

> 作者:დ旧言~

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:手撕哈希表的闭散列和开散列

> 毒鸡汤:谁不是一边受伤,一边学会坚强。

> 专栏选自:C嘎嘎进阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕



🌟前言

我们已经模拟实现了哈希表,当然需要用哈希表来封装相关容器,最初的map和set封装是用红黑树,所以我们就不再封装了map和set,来封装它们的同胞兄弟unordered_map与unordered_set。对比前面map和set的封装还是比较简单的,咱们走起吧!!!


⭐主体

学习开散列实现unordered_map与unordered_set的封装咱们按照下面的图解:



🌙unordered_map讲解

概念:


unordered_map其实就是与map相对应的一个容器。学过map就知道,map的底层是一个红黑树,通过中序遍历的方式可以以有序的方式遍历整棵树。而unordered_map,正如它的名字一样,它的数据存储其实是无序的,这也和它底层所使用的哈希结构有关。而在其他功能上,unordered_map和map基本上就是一致的。


特点:


  • unordered_map是用于存储<key, value>键值对的关联式容器,它允许通过key快速的索引到对应的value。
  • 在内部,unorder_map没有对<key, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的<key, value>键值对放在相同的桶中。
  • unordered_map容器的搜索效率比map快,但它在遍历元素自己的范围迭代方面效率就比较低。
  • 它的迭代器只能向前迭代,不支持反向迭代。


拓展:


从大结构上看,unordered_map和map的模板其实没有太大差距。学习了map和set我们就应该知道,map是通过T来告诉红黑树要构造的树的存储数据类型的,unordered_map也是一样的,但是它的参数中多了Hash和Pred两个参数,这两个参数都传了仿函数,主要和哈希结构有关。


使用:

统计水果个数:

int main()
{
  string arr[] = { "西瓜","西瓜", "苹果", "梨", "苹果",
    "香蕉", "苹果", "梨", "香蕉", "苹果", "西瓜" };
  unordered_map<string, int> count;
 
  for (auto& e : arr)
  {
    count[e]++;
  }
  for (auto e : count)
  {
    cout << e.first << ":" << e.second << endl;
  }
 
  return 0;
}



🌙unordered_set讲解

概念:

unordered_set其实也是一样的,从功能上来看和set并没有什么区别,只是由于地层数据结构的不同,导致unordered_set的数据是无序的,但是查找效率非常高。


特点:


  • 无序性:unordered_set中的元素没有特定的顺序,不会根据插入的顺序或者元素的值进行排序。
  • 唯一性:unordered_set中的元素是唯一的,不允许重复的元素。
  • 快速查找:unordered_set使用哈希表实现,可以在平均常数时间内进行查找操作,即使在大型数据集上也能保持高效。
  • 插入和删除效率高:unordered_set的插入和删除操作的平均时间复杂度为常数时间,即O(1)。
  • 高效的空间利用:unordered_set使用哈希表来存储元素,不会浪费额外的空间。
  • 不支持修改操作:由于unordered_set中的元素是唯一的,不支持直接修改元素的值,需要先删除旧值,再插入新值。
  • 迭代器失效:在进行插入和删除


拓展:

unordered_set相较于set,多了Hash和Pred两个参数。这两个参数都是传了仿函数,和unordered_map与map之间的关系都是一样的。

使用:

存储整数:

int main()
{
  unordered_set<int> v;
  v.insert(7);
  v.insert(2);
  v.insert(3);
  v.insert(5);
  v.insert(4);
  v.insert(8);
  v.insert(7);
  unordered_set<int>::iterator it = v.begin();
  while (it != v.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
 
  return 0;
}



🌙哈希桶的改造

可以我们原先的哈希桶的写法,这里我们只改造开散列:【C++】手撕哈希表的闭散列和开散列

💫结构修改

步骤:

首先,这里实现的哈希桶需要能够同时支持unordered_map和unordered_set的调用。因此,在传入的数据中就需要有一个T,来标识传入的数据类型。同时,还需要有Hash函数和KeyOfT来分别对传入的数据转换为整形和获取传入数据的key值,主要是提供给使用了KV模型的数据。


其次,哈希桶其实是保存在一个顺序表中的,每个下标对应的位置上都是桶的头节点,每个桶中的数据以单链表的方式链接起来。因此,我们就需要一个vector来存储结构体指针,这个结构体中包含了当前节点存储的数据和下一个节点的位置。当然,还有有一个_n来记录顺序表中数据的个数。


代码:

//开散列
namespace hash_bucket
{
  // 定义结点
  template<class T>
  struct HashNode
  {
    T _data;
    HashNode<T>* _next;
    HashNode(const T& data)
      :_data(data)
      , _next(nullptr)
    {}
  };
 
    // 哈希表
  template<class K, class T, class KeyOfT, class Hash>
  class HashTable
  {
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >
    
    //友元 使 __HashIterator中可以调用HashTable的私有
    friend struct   __HashIterator;
    
    typedef HashNode<T> Node;
  public:
    typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
    typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
 
 
  private:
    vector<Node*> _tables;  //指针数组
    size_t _n = 0;          //存储数据有效个数
  };
}


注意:

哈希桶hash_bucket中

需要将其内部的 HashNode 的参数进行修改

将原来的模板参数 K,V 改为 T

同样由于不知道传入数据的是K还是K V类型的 ,所以 使用 T 类型的data代替


之前实现的模板参数 K ,V分别代表 key 与value

修改后 , 第一个参数 拿到单独的K类型,是为了 使用 Find erase接口函数的参数为K

第二个参数 T 决定了 拿到的是K类型 还是 K V 类型


上面的代码中有一个迭代器的重命名和迭代器结构体的友元声明。重命名是为了方便后续的使用。友元声明则和迭代器的实现有关,这里先不过多讲解。


💫构造函数

代码如下:

// 析构函数
~HashTable()//析构
{
  for (auto& cur : _tables)
  {
    while (cur)
    {
      Node* next = cur->_next;
      delete cur;
      cur = next;
    }
    cur = nullptr;
  }
}


💫插入函数

步骤:

在插入时,首先要先查看哈希桶中是否存在相同键值,存在就直接返回当前位置。第二步就是要查看哈希桶中的元素个数与哈希桶的容量之间的负载因子,如果等于1,就需要进行扩容。第三步则是开始插入节点。先找到映射位置,然后新建一个节点连接到对应的数组下标的空间中即可。

注意:

insert()函数返回的是pair<iterator, bool>,这是为了后续实现 [ ] 重载做准备。

代码:

// 插入元素
pair<iterator, bool> insert(const T& data)//插入
{
  KeyOfT kot;
  //若对应的数据已经存在,则插入失败
  iterator it = Find(kot(data));
  if (it != end())
  {
    return make_pair(it, false);
  }
 
  Hash  hash;
 
  //负载因子==1时扩容
  if (_n == _tables.size())
  {
    size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
        
    // 创建 newsize个数据,每个数据都为空
    vector<Node*>newtables(newsize, nullptr);
 
    for (auto& cur : _tables)
    {
      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.swap(newtables);
  }
 
 
  size_t hashi = hash(kot(data)) % _tables.size();
 
  //头插
  Node* newnode = new Node(data);//新建节点
  newnode->_next = _tables[hashi];
  _tables[hashi] = newnode;
  ++_n;
  return make_pair(iterator(newnode, this), true);
 
}


💫查找函数

步骤:

查找数据很简单。先通过hash函数计算出要查找的数据键值所对应的位置,如果对应的位置上存储的是空,说明不存在,直接返回。如果不为空,则比对键值,相同返回,不相同向下找直到为空。

代码:

// 查找函数
iterator Find(const K& key)
{
  Hash hash;
  KeyOfT kot;
 
  //若表刚开始为空
  if (_tables.size() == 0)
  {
    return end();
  }
  size_t hashi = hash(key) % _tables.size();
  Node* cur = _tables[hashi];
  while (cur)
  {
    if (kot(cur->_data) == key)
    {
      return iterator(cur, this);
    }
    cur = cur->_next;
  }
  return end();
}


💫删除函数

步骤:

在删除时,首先要调用hash函数获取关键码,根据对应位置上找。如果该位置存的数据为空,则表示不存在,返回;如果不为空,就比较键值,相同为找到,删除,不同就继续向下找直到为空。

注意:

哈希桶与哈希表不同,要删除数据时不能使用find()函数搜索。因为哈希桶中的数据是用单链表链接起来的,用find()就无法知道节点的父节点,进而无法链接链表。

代码:

// 删除函数
bool Erase(const K& key)
{
  Hash hash;
  KeyOfT kot;
  size_t hashi = hash(key) % _tables.size();
  Node* prev = nullptr;//用于记录前一个节点
  Node* cur = _tables[hashi];
  while (cur)
  {
    if (kot(cur->_data) == key)
    {
      if (prev == nullptr)//要删除节点为头节点时
      {
        _tables[hashi] = cur->_next;
      }
      else
      {
        prev->_next = cur->_next;
      }
      delete cur;
      return true;
    }
    else
    {
      prev = cur;
      cur = cur->_next;
    }
  }
  return false;
}


💫迭代器实现

步骤:

迭代器的结构体中首先要有哈希桶的指针。当然,迭代器的结构体中还需要有一个数据的指针,用于初始化迭代器,获取对应位置上的内容。


判断当前节点是否为空,不为空则返回;为空就说明当前下标对应的位置上已经没有数据了,就调用hash函数获取该节点的关键码。++关键码走向下一个下标,不为空则返回;为空则继续走,到找到不为空的位置或结束。

图解:


注意:

哈希桶的begin()要返回的是第一个不为空的桶,而不是第一个节点

代码:

//迭代器
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >
struct __HashIterator
{
  typedef HashNode<T> Node;
  typedef HashTable<K, T, KeyOfT, Hash> HT;
  typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
 
  //定义一个普通迭代器
  typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
 
  Node* _node; //节点的指针
  HT* _ht;     //哈希表
 
  // 初始化列表
  __HashIterator(Node* node, HT* ht)
    :_node(node), _ht(ht)
  {}
 
  // 初始化列表const
  __HashIterator(const Iterator& it)
    :_node(it._node), _ht(it._ht)
  {}
 
  Ref operator*()   // 取值
  {
    return _node->_data;
  }
 
  Ptr operator->()  // 指向
  {
    return &_node->_data;
  }
 
  bool operator!=(const Self& s) //使用节点的指针进行比较
  {
    return _node != s._node;
  }
 
  //前置++
  Self& operator++()
  {
    //若不处于当前桶的尾位置,则寻找桶的下一个位置
    if (_node->_next != nullptr)
    {
      _node = _node->_next;
    }
    else
    {
      //若为空,则说明处于当前桶的尾位置,则需寻找下一个不为空的桶
      KeyOfT kot;
      Hash hash;
      //计算当前桶位置
      size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
      hashi++;//下一个位置
 
      //寻找下一个不为空的桶
      while (hashi < _ht->_tables.size())
      {
        if (_ht->_tables[hashi])
        {
          _node = _ht->_tables[hashi];
          break;
        }
        else
        {
          //桶为空,需要继续寻找
          hashi++;
        }
      }
 
      //没有找到不为空的桶
      if (hashi == _ht->_tables.size())
      {
        _node = nullptr;
      }
    }
    return  *this;
  }
 
};


💫友元声明

解析:

在++迭代器的时候,会使用到哈希表指针,哈希表指针又会使用到HashTable中的_tables。HashTable中的_tables是私有成员,在类外是不能访问的。解决这个问题可以在HashTable中写一个公有的访问函数,也可以采用友元。


图解:



🌙unordered_map封装

代码如下:

#include"HashTable.h"
 
namespace lyk
{
  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 hash_bucket::HashTable<K, pair<const K, V>, 
      MapKeyOfT, Hash>::iterator iterator;
 
    // 开始位置
    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;
    }
 
    // 插入函数
    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
      return _ht.insert(kv);
    }
 
    // 查找函数
    iterator find(const K& key)
    {
      return _ht.Find();
    }
 
    // 删除函数
    bool erase(const K& key)
    {
      return _ht.Erase(key);
    }
    
  private:
    hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
  };
 
  // 测试一
  void test_map1()
  {
    unordered_map<int, int> v;
    v.insert(make_pair(1, 1));
    v.insert(make_pair(3, 3));
    v.insert(make_pair(10, 10));
    v.insert(make_pair(2, 2));
    v.insert(make_pair(8, 8));
    unordered_map<int, int>::iterator it = v.begin();
    while (it != v.end())
    {
      cout << it->first << ":" << it->second << " ";
      ++it;
    }
    cout << endl;
 
  }
  // 测试二
  void test_map2()
  {
    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.second += 'y';
 
      cout << kv.first << ":" << kv.second << endl;
    }
  }
 
};


🌙unordered_set封装

代码如下:

#include"HashTable.h"
 
namespace lyk
{
  template<class K, class Hash = HashFunc<K>>
  class unordered_set
  {
    // 仿函数
    struct SetKeyOfT
    {
      const K& operator()(const K& key)
      {
        return key;
      }
    };
  public:
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator  iterator;
    typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator  const_iterator;
 
    // 开始位置
    iterator begin()
    {
      return _ht.begin();
    }
    // 结束位置
    iterator end()
    {
      return _ht.end();
    }
 
    // 开始位置const修饰
    const_iterator begin() const
    {
      return _ht.begin();
    }
    // 结束位置const修饰
    const_iterator end() const
    {
      return _ht.end();
    }
 
    // 插入
    pair<iterator, bool> insert(const K& key)
    {
      return _ht.insert(key);
    }
 
    // 查找
    iterator find(const K& key)
    {
      return _ht.Find();
    }
 
    // 删除
    bool erase(const K& key)
    {
      return _ht.Erase(key);
    }
    
 
  private:
    hash_bucket::HashTable<K, 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())
    {
      cout << *it << " ";
      ++it;
    }
    cout << endl;
 
    for (auto e : us)
    {
      cout << e << " ";
    }
    cout << endl;
  }
 
}


🌙哈希表完整代码

#include <iostream>
#include <string>
#include <vector>
using namespace std;
 
// 仿函数
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 hash = 0;
    for (auto ch : key)
    {
      hash *= 131;
      hash += ch;
    }
    return hash;
  }
};
 
//开散列
namespace hash_bucket
{
  // 定义结点
  template<class T>
  struct HashNode
  {
    T _data;
    HashNode<T>* _next;
    HashNode(const T& data)
      :_data(data)
      , _next(nullptr)
    {}
  };
 
  // 前置声明 (像函数声明一样)
  template<class K, class T, class KeyOfT, class Hash>
  class HashTable;
 
  //迭代器
  template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >
  struct __HashIterator
  {
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOfT, Hash> HT;
    typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
 
    //定义一个普通迭代器
    typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
 
    Node* _node; //节点的指针
    HT* _ht;     //哈希表
 
    // 初始化列表
    __HashIterator(Node* node, HT* ht)
      :_node(node), _ht(ht)
    {}
 
    // 初始化列表const
    __HashIterator(const Iterator& it)
      :_node(it._node), _ht(it._ht)
    {}
 
    Ref operator*()   // 取值
    {
      return _node->_data;
    }
 
    Ptr operator->()  // 指向
    {
      return &_node->_data;
    }
 
    bool operator!=(const Self& s) //使用节点的指针进行比较
    {
      return _node != s._node;
    }
 
    //前置++
    Self& operator++()
    {
      //若不处于当前桶的尾位置,则寻找桶的下一个位置
      if (_node->_next != nullptr)
      {
        _node = _node->_next;
      }
      else
      {
        //若为空,则说明处于当前桶的尾位置,则需寻找下一个不为空的桶
        KeyOfT kot;
        Hash hash;
        //计算当前桶位置
        size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
        hashi++;//下一个位置
 
        //寻找下一个不为空的桶
        while (hashi < _ht->_tables.size())
        {
          if (_ht->_tables[hashi])
          {
            _node = _ht->_tables[hashi];
            break;
          }
          else
          {
            //桶为空,需要继续寻找
            hashi++;
          }
        }
 
        //没有找到不为空的桶
        if (hashi == _ht->_tables.size())
        {
          _node = nullptr;
        }
      }
      return  *this;
    }
 
  };
 
  // 哈希表
  template<class K, class T, class KeyOfT, class Hash>
  class HashTable
  {
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash >
    
    //友元 使 __HashIterator中可以调用HashTable的私有
    friend struct   __HashIterator;
    
    typedef HashNode<T> Node;
  public:
    typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
    typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
 
    // 开始位置
    iterator begin()
    {
      Node* cur = nullptr;
      size_t i = 0;
      //找到第一个不为空的桶
      for (i = 0; i < _tables.size(); i++)
      {
        cur = _tables[i];
        if (cur)
        {
          break;
        }
      }
      //迭代器返回 第一个桶 和哈希表
      //this 作为哈希表本身
      return iterator(cur, this);
    }
 
    // 最后位置
    iterator end()
    {
      return iterator(nullptr, this);
    }
 
    // const修饰的开始位置
    const_iterator begin()const
    {
      Node* cur = nullptr;
      size_t i = 0;
      //找到第一个不为空的桶
      for (i = 0; i < _tables.size(); i++)
      {
        cur = _tables[i];
        if (cur)
        {
          break;
        }
      }
      //迭代器返回 第一个桶 和哈希表
      //this 作为哈希表本身
      return const_iterator(cur, this);
    }
 
    // const修饰的最后位置
    const_iterator end() const
    {
      return const_iterator(nullptr, this);
    }
    
    // 析构函数
    ~HashTable()//析构
    {
      for (auto& cur : _tables)
      {
        while (cur)
        {
          Node* next = cur->_next;
          delete cur;
          cur = next;
        }
        cur = nullptr;
      }
    }
 
    // 查找函数
    iterator Find(const K& key)
    {
      Hash hash;
      KeyOfT kot;
 
      //若表刚开始为空
      if (_tables.size() == 0)
      {
        return end();
      }
      size_t hashi = hash(key) % _tables.size();
      Node* cur = _tables[hashi];
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          return iterator(cur, this);
        }
        cur = cur->_next;
      }
      return end();
    }
 
    // 删除函数
    bool Erase(const K& key)
    {
      Hash hash;
      KeyOfT kot;
      size_t hashi = hash(key) % _tables.size();
      Node* prev = nullptr;//用于记录前一个节点
      Node* cur = _tables[hashi];
      while (cur)
      {
        if (kot(cur->_data) == key)
        {
          if (prev == nullptr)//要删除节点为头节点时
          {
            _tables[hashi] = cur->_next;
          }
          else
          {
            prev->_next = cur->_next;
          }
          delete cur;
          return true;
        }
        else
        {
          prev = cur;
          cur = cur->_next;
        }
      }
      return false;
    }
 
    // 插入元素
    pair<iterator, bool> insert(const T& data)//插入
    {
      KeyOfT kot;
      //若对应的数据已经存在,则插入失败
      iterator it = Find(kot(data));
      if (it != end())
      {
        return make_pair(it, false);
      }
 
      Hash  hash;
 
      //负载因子==1时扩容
      if (_n == _tables.size())
      {
        size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
        
        // 创建 newsize个数据,每个数据都为空
        vector<Node*>newtables(newsize, nullptr);
 
        for (auto& cur : _tables)
        {
          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.swap(newtables);
      }
 
 
      size_t hashi = hash(kot(data)) % _tables.size();
 
      //头插
      Node* newnode = new Node(data);//新建节点
      newnode->_next = _tables[hashi];
      _tables[hashi] = newnode;
      ++_n;
      return make_pair(iterator(newnode, this), true);
 
    }
  
  private:
    vector<Node*> _tables;  //指针数组
    size_t _n = 0;          //存储数据有效个数
  };
  
}


🌟结束语

      今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

目录
相关文章
|
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你都知道吗?一文了解集合和字典在前端中的应用
|
3月前
|
数据安全/隐私保护 C语言 C++
C++(七)封装
本文档详细介绍了C++封装的概念及其应用。封装通过权限控制对外提供接口并隐藏内部数据,增强代码的安全性和可维护性。文档首先解释了`class`中的权限修饰符(`public`、`private`、`protected`)的作用,并通过示例展示了如何使用封装实现栈结构。接着介绍了构造器和析构器的使用方法,包括初始化列表的引入以及它们在内存管理和对象生命周期中的重要性。最后,通过分文件编程的方式展示了如何将类定义和实现分离,提高代码的模块化和复用性。
|
4月前
|
存储 Java 索引
|
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是一个不允许重复元素的集合。