SGISTL源码探究-关联式容器:hash_multimap

本文深入解析hash_multimap的源码实现,重点介绍了其与hash_map的区别,并详细分析了hash_multimap的数据结构、构造函数及常用操作等核心内容。

前言

本小节将介绍hash_multimap的源码实现,它与hash_map的不同与mapmultimap的不同类似,所以我们可以一边分析它们的不同之处,一边顺便复习hash_map

hash_multimap的实现

定义及数据结构
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class Key, class T, class HashFcn = hash<Key>,
          class EqualKey = equal_to<Key>,
          class Alloc = alloc>
#else
template <class Key, class T, class HashFcn, class EqualKey,
          class Alloc = alloc>
#endif
class hash_multimap
{
private:
  //这里不要忽视了pair的第一个元素是const型的,不允许修改
  typedef hashtable<pair<const Key, T>, Key, HashFcn,
                    select1st<pair<const Key, T> >, EqualKey, Alloc> ht;
  ht rep;

public:
  //一些别名
  typedef typename ht::key_type key_type;
  typedef T data_type;
  typedef T mapped_type;
  typedef typename ht::value_type value_type;
  typedef typename ht::hasher hasher;
  typedef typename ht::key_equal key_equal;

  typedef typename ht::size_type size_type;
  typedef typename ht::difference_type difference_type;
  typedef typename ht::pointer pointer;
  typedef typename ht::const_pointer const_pointer;
  typedef typename ht::reference reference;
  typedef typename ht::const_reference const_reference;

  typedef typename ht::iterator iterator;
  typedef typename ht::const_iterator const_iterator;

  hasher hash_funct() const { return rep.hash_funct(); }
  key_equal key_eq() const { return rep.key_eq(); }
构造函数
public:
  /* 构造函数 指定大小、哈希函数、比较key大小的函数 */
  hash_multimap() : rep(100, hasher(), key_equal()) {}
  explicit hash_multimap(size_type n) : rep(n, hasher(), key_equal()) {}
  hash_multimap(size_type n, const hasher& hf) : rep(n, hf, key_equal()) {}
  hash_multimap(size_type n, const hasher& hf, const key_equal& eql)
    : rep(n, hf, eql) {}

  //插入元素时使用的insert_equal
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  hash_multimap(InputIterator f, InputIterator l)
    : rep(100, hasher(), key_equal()) { rep.insert_equal(f, l); }
  template <class InputIterator>
  hash_multimap(InputIterator f, InputIterator l, size_type n)
    : rep(n, hasher(), key_equal()) { rep.insert_equal(f, l); }
  template <class InputIterator>
  hash_multimap(InputIterator f, InputIterator l, size_type n,
                const hasher& hf)
    : rep(n, hf, key_equal()) { rep.insert_equal(f, l); }
  template <class InputIterator>
  hash_multimap(InputIterator f, InputIterator l, size_type n,
                const hasher& hf, const key_equal& eql)
    : rep(n, hf, eql) { rep.insert_equal(f, l); }

#else
  hash_multimap(const value_type* f, const value_type* l)
    : rep(100, hasher(), key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const value_type* f, const value_type* l, size_type n)
    : rep(n, hasher(), key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const value_type* f, const value_type* l, size_type n,
                const hasher& hf)
    : rep(n, hf, key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const value_type* f, const value_type* l, size_type n,
                const hasher& hf, const key_equal& eql)
    : rep(n, hf, eql) { rep.insert_equal(f, l); }

  hash_multimap(const_iterator f, const_iterator l)
    : rep(100, hasher(), key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const_iterator f, const_iterator l, size_type n)
    : rep(n, hasher(), key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const_iterator f, const_iterator l, size_type n,
                const hasher& hf)
    : rep(n, hf, key_equal()) { rep.insert_equal(f, l); }
  hash_multimap(const_iterator f, const_iterator l, size_type n,
                const hasher& hf, const key_equal& eql)
    : rep(n, hf, eql) { rep.insert_equal(f, l); }
#endif /*__STL_MEMBER_TEMPLATES */
常用操作
public:
  //这部分都与hash_map相同
  size_type size() const { return rep.size(); }
  size_type max_size() const { return rep.max_size(); }
  bool empty() const { return rep.empty(); }
  void swap(hash_multimap& hs) { rep.swap(hs.rep); }
  friend bool
  operator== __STL_NULL_TMPL_ARGS (const hash_multimap&, const hash_multimap&);

  //返回首/尾迭代器,尾迭代器为指向null的节点的指针,首迭代器为指向第一个bucket不为空的头节点的指针
  iterator begin() { return rep.begin(); }
  iterator end() { return rep.end(); }
  const_iterator begin() const { return rep.begin(); }
  const_iterator end() const { return rep.end(); }

public:
  //插入操作,调用的是insert_equal
  iterator insert(const value_type& obj) { return rep.insert_equal(obj); }
#ifdef __STL_MEMBER_TEMPLATES
  template <class InputIterator>
  void insert(InputIterator f, InputIterator l) { rep.insert_equal(f,l); }
#else
  void insert(const value_type* f, const value_type* l) {
    rep.insert_equal(f,l);
  }
  void insert(const_iterator f, const_iterator l) { rep.insert_equal(f, l); }
#endif /*__STL_MEMBER_TEMPLATES */
  iterator insert_noresize(const value_type& obj)
    { return rep.insert_equal_noresize(obj); }    

  iterator find(const key_type& key) { return rep.find(key); }
  const_iterator find(const key_type& key) const { return rep.find(key); }
  /* 在hash_map中,这里实现了对[]操作符的重载
   * 但是在hash_multimap中没有对其重载
   * 是因为key可以重复,那么下标访问时可能就对应了多个元素,此时该返回哪个就成了难题
   */
  size_type count(const key_type& key) const { return rep.count(key); }

  pair<iterator, iterator> equal_range(const key_type& key)
    { return rep.equal_range(key); }
  pair<const_iterator, const_iterator> equal_range(const key_type& key) const
    { return rep.equal_range(key); }
  //删除操作
  size_type erase(const key_type& key) {return rep.erase(key); }
  void erase(iterator it) { rep.erase(it); }
  void erase(iterator f, iterator l) { rep.erase(f, l); }
  void clear() { rep.clear(); }

public:
  /* 剩下的部分都与hash_map相同了 */
  void resize(size_type hint) { rep.resize(hint); }
  size_type bucket_count() const { return rep.bucket_count(); }
  size_type max_bucket_count() const { return rep.max_bucket_count(); }
  size_type elems_in_bucket(size_type n) const
    { return rep.elems_in_bucket(n); }
};

template <class Key, class T, class HF, class EqKey, class Alloc>
inline bool operator==(const hash_multimap<Key, T, HF, EqKey, Alloc>& hm1,
                       const hash_multimap<Key, T, HF, EqKey, Alloc>& hm2)
{
  return hm1.rep == hm2.rep;
}

#ifdef __STL_FUNCTION_TMPL_PARTIAL_ORDER

template <class Key, class T, class HashFcn, class EqualKey, class Alloc>
inline void swap(hash_multimap<Key, T, HashFcn, EqualKey, Alloc>& hm1,
                 hash_multimap<Key, T, HashFcn, EqualKey, Alloc>& hm2)
{
  hm1.swap(hm2);
}

#endif /* __STL_FUNCTION_TMPL_PARTIAL_ORDER */

小结

本小节对hash_multimap进行了分析。它和hash_map的不同之处除了插入元素时允许重复之外,hash_multimap还不支持下标运算。
关联式容器部分就到此为止了,其实只要把hashtable的实现搞明白了,对hash_sethash_map之类的理解就轻松多了。
在下一小节中,我们将对STL中的泛型算法部分开始分析,在具体的源码分析中,我不会列出所有的算法,而是选择其中比较经典和常用的算法进行分析。

<think>我们被要求提供关于C++中`std::unordered_multimap`的使用教程和示例。根据之前的引用,我们知道`std::unordered_multimap`是一个无序关联容器,它允许存储多个具有相同键的键值对。与`std::unordered_map`不同,它不要求键的唯一性。 主要特点: 1. 内部使用哈希表实现,因此平均情况下插入、删除和查找操作的时间复杂度为常数时间。 2. 元素在内部不以特定顺序排序,而是根据键的哈希值组织到桶中。 3. 支持多个相同键值。 我们将通过以下步骤来介绍: 1. 包含头文件 2. 创建`std::unordered_multimap` 3. 插入元素 4. 查找元素 5. 删除元素 6. 遍历元素 7. 其他常用操作 同时,我们将结合引用中的示例代码,并给出新的示例。 注意:引用[5]中有一个示例,我们可以参考并扩展。 步骤: 1. 包含头文件: ```cpp #include <unordered_multimap> #include <iostream> #include <string> ``` 2. 创建`std::unordered_multimap`: ```cpp std::unordered_multimap<std::string, int> myMap; ``` 3. 插入元素:可以使用`insert`成员函数,也可以使用`emplace`(直接构造元素)。 ```cpp myMap.insert(std::make_pair("apple", 10)); myMap.insert(std::make_pair("banana", 20)); myMap.insert(std::make_pair("apple", 30)); // 允许插入相同的键 ``` 4. 查找元素:由于键不唯一,我们可以使用`equal_range`来获取一个键对应的所有元素的范围(返回一对迭代器)。 ```cpp auto range = myMap.equal_range("apple"); for (auto it = range.first; it != range.second; ++it) { std::cout << it->first << ": " << it->second << std::endl; } ``` 5. 删除元素:可以使用`erase`,可以删除指定键的所有元素,也可以删除指定位置的元素。 ```cpp // 删除键为"apple"的所有元素 myMap.erase("apple"); // 删除指定位置的元素 auto it = myMap.find("banana"); if (it != myMap.end()) { myMap.erase(it); } ``` 6. 遍历元素:使用迭代器遍历整个容器。注意:遍历顺序是无序的(取决于哈希函数和桶的分布)。 ```cpp for (auto it = myMap.begin(); it != myMap.end(); ++it) { std::cout << it->first << " -> " << it->second << std::endl; } // 或者使用范围for循环 for (const auto& pair : myMap) { std::cout << pair.first << " -> " << pair.second << std::endl; } ``` 7. 其他常用操作: - `size()`:返回元素数量。 - `empty()`:判断是否为空。 - `clear()`:清空容器- `count(key)`:返回键等于key的元素数量。 下面我们将结合引用[5]中的示例,并编写一个完整的示例程序: 引用[5]中的示例代码: ```cpp #include <iostream> #include <unordered_map> #include <string> auto Print(const std::string &msg, const std::unordered_multimap<int, int> &lst) { std::cout << msg << " : "; for (const auto &pa : lst) { std::cout << pa.first << " -> " << pa.second << "\t"; } std::cout << "\n"; } int main() { using umap_int = std::unordered_multimap<int, int>; umap_int m1{{1, 10}, {2, 20}, {3, 30}}; umap_int m2{{1, 10}, {2, 20}, {2, 30}, {3, 30}, {3, 40}}; Print("m1", m1); Print("m2", m2); std::cout << "m1.count(3): " << m1.count(3) << std::endl; std::cout << "m2.count(2): " << m2.count(2) << std::endl; // 查找键3 auto range = m2.equal_range(3); for (auto it = range.first; it != range.second; ++it) { std::cout << "Found: " << it->first << " -> " << it->second << std::endl; } // 删除键2 m2.erase(2); Print("m2 after erase(2)", m2); return 0; } ``` 输出可能如下(顺序可能不同): ``` m1 : 1 -> 10 2 -> 20 3 -> 30 m2 : 1 -> 10 2 -> 20 2 -> 30 3 -> 30 3 -> 40 m1.count(3): 1 m2.count(2): 2 Found: 3 -> 30 Found: 3 -> 40 m2 after erase(2) : 1 -> 10 3 -> 30 3 -> 40 ``` 注意:由于`std::unordered_multimap`是无序的,输出顺序可能与插入顺序不同,并且每次运行可能不同。 我们还可以自定义哈希函数和键相等比较函数。例如,如果我们有一个自定义类型作为键,我们需要提供哈希函数和相等比较函数。 示例:使用自定义类型作为键 ```cpp #include <iostream> #include <unordered_map> #include <string> struct Person { std::string name; int age; // 重载==运算符 bool operator==(const Person& other) const { return name == other.name && age == other.age; } }; // 自定义哈希函数 struct PersonHash { std::size_t operator()(const Person& p) const { return std::hash<std::string>()(p.name) ^ std::hash<int>()(p.age); } }; int main() { std::unordered_multimap<Person, std::string, PersonHash> myMap; Person p1{"Alice", 30}; Person p2{"Bob", 25}; Person p3{"Alice", 30}; // 与p1相同 myMap.insert({p1, "Manager"}); myMap.insert({p2, "Engineer"}); myMap.insert({p3, "Director"}); // 将插入,因为允许重复键(虽然p1和p3相同,但unordered_multimap允许重复键) // 注意:这里p1和p3被认为是相同的键,但unordered_multimap允许有多个相同的键值对,所以这里会存储两个相同的键(p1)和两个不同的值。 // 实际上,p1和p3是相同的键,所以它们会被视为同一个键,但unordered_multimap允许同一个键出现多次。 // 查找键p1 auto range = myMap.equal_range(p1); for (auto it = range.first; it != range.second; ++it) { std::cout << it->first.name << ", " << it->first.age << ": " << it->second << std::endl; } return 0; } ``` 注意:自定义哈希函数需要满足一定的要求,例如对于相等的键必须产生相同的哈希值。上面的哈希函数只是简单示例,实际中可能需要更复杂的组合。 总结: - `std::unordered_multimap`适用于需要快速查找且键不需要唯一的场景。 - 主要操作:插入(insert/emplace)、查找(find/equal_range/count)、删除(erase)和遍历。 - 当键是自定义类型时,需要提供哈希函数和相等比较函数。 最后,我们生成相关问题。</think>### C++ `std::unordered_multimap` 使用教程与示例 `std::unordered_multimap` 是 C++ 标准库中的无序关联容器,支持存储多个相同键的键值对,基于哈希表实现,提供高效的插入、删除和查找操作(平均时间复杂度 $O(1)$)。 #### 1. 基本特性 - **键值对存储**:元素类型为 `std::pair<const Key, T>` - **哈希桶结构**:元素根据键的哈希值分配到不同桶中 - **允许重复键**:与 `std::unordered_map` 不同,允许多个相同键值 - **无序存储**:元素不按特定顺序排列(引用[3]) #### 2. 头文件与声明 ```cpp #include <unordered_map> // 基本声明 std::unordered_multimap<std::string, int> fruitBasket; // 完整模板声明(引用[4]) template <class Key, class T, class Hash = std::hash<Key>, class Pred = std::equal_to<Key>> using unordered_multimap = std::unordered_multimap<Key, T, Hash, Pred, allocator>; ``` #### 3. 核心操作示例 ```cpp #include <iostream> #include <unordered_map> #include <string> void printMap(const std::string& msg, const std::unordered_multimap<std::string, int>& map) { std::cout << msg << ":\n"; for (const auto& [key, value] : map) { std::cout << " " << key << " -> " << value << "\n"; } } int main() { // 初始化(引用[1]) std::unordered_multimap<std::string, int> fruits { {"apple", 5}, {"banana", 3}, {"apple", 2}, {"orange", 7} }; printMap("Initial fruits", fruits); // 插入元素 fruits.emplace("banana", 4); // 高效插入(引用[2]) fruits.insert({"grape", 6}); // 查找元素(引用[5]) auto range = fruits.equal_range("apple"); std::cout << "\nFound " << fruits.count("apple") << " apples:\n"; for (auto it = range.first; it != range.second; ++it) { std::cout << " " << it->second << " units\n"; } // 删除元素 fruits.erase("banana"); // 删除所有banana printMap("\nAfter erasing bananas", fruits); // 性能测试(引用[2]) std::unordered_multimap<int, int> largeMap; // 插入100万元素性能测试... } ``` #### 4. 关键成员函数 | 方法 | 功能 | 时间复杂度 | |------|------|-----------| | `emplace(key, value)` | 直接构造元素 | 平均 $O(1)$ | | `equal_range(key)` | 获取匹配键的范围 | 平均 $O(1)$ | | `count(key)` | 统计键出现次数 | 平均 $O(1)$ | | `erase(key)` | 删除指定键元素 | 平均 $O(1)$ | | `bucket_count()` | 返回桶的数量 | $O(1)$ | #### 5. 性能优化技巧 1. **预分配桶**:减少rehash开销 ```cpp std::unordered_multimap<std::string, int> map; map.reserve(1000); // 预分配空间 ``` 2. **自定义哈希函数**:优化哈希分布 ```cpp struct StringHash { size_t operator()(const std::string& s) const { return std::hash<std::string>()(s); } }; std::unordered_multimap<std::string, int, StringHash> customMap; ``` 3. **选择合适的桶大小**:平衡内存与性能 ```cpp map.rehash(2000); // 设置桶数量 ``` #### 6. 与 `std::unordered_map` 对比 | 特性 | unordered_multimap | unordered_map | |------|---------------------|---------------| | 键唯一性 | ❌ 允许多个相同键 | ✅ 键唯一 | | 查找结果 | 可能返回多个元素 | 最多一个元素 | | 内存占用 | 更高(需存储重复键) | 更低 | | 适用场景 | 一对多关系 | 一对一关系 | #### 7. 实际应用场景 1. **数据库索引**:存储相同索引键的多条记录 ```cpp // 用户ID -> 订单记录 std::unordered_multimap<int, Order> userOrders; ``` 2. **词频统计**:存储单词出现的所有位置 ```cpp // 单词 -> 出现位置列表 std::unordered_multimap<std::string, size_t> wordPositions; ``` 3. **网络路由表**:存储相同目的地址的多条路径 ```cpp // IP地址 -> 路由路径 std::unordered_multimap<std::string, Route> routingTable; ``` > 完整文档参考:[C++ std::unordered_multimap](https://en.cppreference.com/w/cpp/container/unordered_multimap)[^1][^3][^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值