
在C++标准模板库(STL)中,关联容器(Associative Containers)是一类非常重要的数据结构,它们通过键值(Key-Value)对的形式来存储元素,并且提供了基于键的快速查找、插入和删除操作。在关联容器中,multiset是一种允许重复元素的有序容器,它基于红黑树实现,能够高效地进行元素的插入、删除和查找。它与 set 非常相似,但允许存储多个具有相同键的元素。这使得 multiset 在处理需要存储重复元素且要求元素有序的场景时非常有用。
multiset是C++ STL中的一个关联容器,用于存储一组有序的元素,允许存在重复值。它是基于红黑树(一种自平衡二叉搜索树)实现的,因此元素在容器中始终保持有序状态。multiset的主要特性包括:
set不同,multiset允许存储多个相同的元素。multiset的底层实现是红黑树。红黑树是一种自平衡的二叉搜索树,通过颜色标记和旋转操作来保持树的平衡,从而确保各种操作的时间复杂度稳定在O(log n)。红黑树的每个节点都包含一个键值和一个颜色属性(红或黑),并且满足以下性质:
这些性质保证了红黑树的高度大致为O(log n),从而使得multiset的各种操作具有对数时间复杂度。
template <
class Key,
class T,
class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<const Key, T>>
> class multimap;Key:键类型,用于排序和索引
T:值类型,与键关联的数据
Compare:比较函数对象(默认std::less)
Allocator:内存分配器(通常使用默认)
在使用multiset之前,需要包含相应的头文件,并使用std命名空间:
#include <iostream>
#include <set>
using namespace std;以下是几种常见的 multiset 定义和初始化方式:
#include <iostream>
#include <set>
int main() {
multiset<int> mset1; // 创建一个空的multiset,元素类型为int
multiset<string> mset2; // 创建一个空的multiset,元素类型为string
// 使用初始化列表创建multiset
multiset<int> mset3 = {1, 2, 3, 4, 5};
// 使用迭代器范围创建multiset
vector<int> vec = {1, 2, 3, 4, 5};
multiset<int> mset4(vec.begin(), vec.end());
return 0;
}可以使用 insert() 方法向 multiset 中插入元素:
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms;
// 插入单个元素
ms.insert(5);
ms.insert(3);
ms.insert(3); // 允许插入重复元素
// 插入一个范围的元素
int arr[] = {1, 2, 2, 4};
ms.insert(arr, arr + 4);
// 输出 multiset 中的元素
for (auto it = ms.begin(); it != ms.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
可以使用 find() 方法查找指定元素,它会返回指向第一个匹配元素的迭代器。如果没有找到,则返回 end() 迭代器:
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms = {1, 2, 2, 3, 4, 4, 4};
auto it = ms.find(2);
if (it != ms.end()) {
std::cout << "Found element: " << *it << std::endl;
} else {
std::cout << "Element not found." << std::endl;
}
return 0;
}
使用 find() 方法查找元素 2,如果找到则输出该元素,否则输出未找到的信息。
可以使用 erase() 方法删除元素,有几种不同的使用方式:
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms = {1, 2, 2, 3, 4, 4, 4};
// 删除指定元素的所有副本
ms.erase(2);
// 删除迭代器指向的元素
auto it = ms.find(4);
if (it != ms.end()) {
ms.erase(it);
}
// 删除一个范围的元素
auto first = ms.find(3);
auto last = ms.end();
ms.erase(first, last);
// 输出剩余元素
for (auto it = ms.begin(); it != ms.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
可以使用迭代器或范围 for 循环来遍历 multiset 中的元素:
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms = {1, 2, 2, 3, 4, 4, 4};
// 使用迭代器遍历
for (auto it = ms.begin(); it != ms.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 使用范围 for 循环遍历
for (auto elem : ms) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
默认情况下,multiset 使用元素的小于运算符(<)进行排序。但我们可以通过自定义比较函数来改变排序规则:
#include <iostream>
#include <set>
// 自定义比较函数,实现降序排序
struct Greater {
bool operator()(const int& a, const int& b) const {
return a > b;
}
};
int main() {
std::multiset<int, Greater> ms = {1, 2, 2, 3, 4, 4, 4};
for (auto elem : ms) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
定义了一个名为 Greater 的结构体,并重载了 () 运算符,实现了降序排序。然后使用这个自定义比较函数来定义 multiset。
可以使用 count() 方法来统计某个元素在 multiset 中出现的次数:
#include <iostream>
#include <set>
int main() {
std::multiset<int> ms = {1, 2, 2, 3, 4, 4, 4};
int count = ms.count(4);
std::cout << "Element 4 appears " << count << " times." << std::endl;
return 0;
}
可以使用 lower_bound() 和 upper_bound() 方法进行范围查找:
lower_bound(key):返回指向第一个不小于 key 的元素的迭代器。upper_bound(key):返回指向第一个大于 key 的元素的迭代器。#include <iostream>
#include <set>
int main() {
std::multiset<int> ms = {1, 2, 2, 3, 4, 4, 4};
auto lower = ms.lower_bound(2);
auto upper = ms.upper_bound(2);
for (auto it = lower; it != upper; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
使用 lower_bound() 和 upper_bound() 方法查找所有值为 2 的元素,并将它们输出。
虽然 multiset 本身没有直接提供交集、并集和差集操作的方法,但可以使用标准库中的算法来实现这些操作:
#include <iostream>
#include <set>
#include <algorithm>
#include <vector>
int main() {
std::multiset<int> ms1 = {1, 2, 2, 3, 4};
std::multiset<int> ms2 = {2, 2, 3, 5};
std::vector<int> intersection;
std::set_intersection(ms1.begin(), ms1.end(), ms2.begin(), ms2.end(), std::back_inserter(intersection));
std::cout << "Intersection: ";
for (auto elem : intersection) {
std::cout << elem << " ";
}
std::cout << std::endl;
std::vector<int> unionSet;
std::set_union(ms1.begin(), ms1.end(), ms2.begin(), ms2.end(), std::back_inserter(unionSet));
std::cout << "Union: ";
for (auto elem : unionSet) {
std::cout << elem << " ";
}
std::cout << std::endl;
std::vector<int> difference;
std::set_difference(ms1.begin(), ms1.end(), ms2.begin(), ms2.end(), std::back_inserter(difference));
std::cout << "Difference (ms1 - ms2): ";
for (auto elem : difference) {
std::cout << elem << " ";
}
std::cout << std::endl;
return 0;
}
multiset不仅可以存储基本数据类型,还可以存储自定义数据类型,只要定义了相应的比较规则。
// 自定义数据类型
struct Person {
string name;
int age;
Person(string n, int a) : name(n), age(a) {}
};
// 自定义比较函数,按年龄升序排序
struct ComparePerson {
bool operator()(const Person& a, const Person& b) {
return a.age < b.age;
}
};
multiset<Person, ComparePerson> mset = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20}
};
// 遍历输出,按年龄升序排序
for (const auto& p : mset) {
cout << p.name << " " << p.age << endl;
}multiset 基于红黑树实现。红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色)。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
在 multiset 中插入和删除元素时,红黑树会自动调整节点的颜色和结构,以保持红黑树的性质。插入操作可能会导致红黑树的不平衡,需要通过旋转和重新着色来恢复平衡。删除操作也类似,可能需要进行复杂的调整。
如前面所述,multiset 的插入、删除和查找操作的时间复杂度均为 O(logn),其中 n 是 multiset 中元素的数量。这使得 multiset 在处理大量数据时具有较好的性能。
操作 | 时间复杂度 | 说明 |
|---|---|---|
insert() | O(log n) | 插入新元素 |
erase(key) | O(log n + k) | k为删除元素数量 |
find() | O(log n) | 查找首个匹配元素 |
equal_range() | O(log n) | 获取等值范围 |
count() | O(log n + k) | k为匹配元素数量 |
lower_bound() | O(log n) | 查找第一个不小于key的元素 |
multiset 的空间复杂度为 O(n),因为需要存储所有的元素和红黑树的节点信息。
与其他容器相比,multiset 的性能特点如下:
multimap vs map
特性 | multimap | map |
|---|---|---|
键唯一性 | 允许重复键 | 键必须唯一 |
operator[] | 不支持 | 支持 |
插入返回值 | 返回迭代器 | 返回pair<iterator, bool> |
元素数量 | 可能大于键的数量 | 等于键的数量 |
典型应用场景 | 一对多关系(如事件队列) | 一对一映射(如字典) |
multimap vs unordered_multimap
// 有序版本(红黑树)
std::multimap<std::string, int> ordered;
// 无序版本(哈希表)
std::unordered_multimap<std::string, int> unordered;特性 | multimap | unordered_multimap |
|---|---|---|
排序 | 按键升序排列 | 无特定顺序 |
查找速度 | O(log n) | 平均O(1),最坏O(n) |
内存占用 | 较低 | 较高(哈希表开销) |
迭代顺序 | 稳定有序 | 依赖哈希函数 |
自定义哈希 | 不需要 | 需要自定义哈希函数 |
#include <iostream>
#include <set>
#include <string>
int main() {
std::multiset<std::string> wordSet;
std::string word;
// 输入一些单词
while (std::cin >> word) {
if (word == "quit") {
break;
}
wordSet.insert(word);
}
// 统计每个单词出现的次数
for (auto it = wordSet.begin(); it != wordSet.end(); ) {
std::string currentWord = *it;
int count = wordSet.count(currentWord);
std::cout << currentWord << " appears " << count << " times." << std::endl;
std::advance(it, count);
}
return 0;
}
通过 multiset 统计输入的单词出现的次数。
#include <iostream>
#include <set>
int main() {
std::multiset<int, std::greater<int>> priorityQueue;
// 插入一些元素
priorityQueue.insert(3);
priorityQueue.insert(1);
priorityQueue.insert(4);
priorityQueue.insert(1);
// 输出队列中的元素
for (auto elem : priorityQueue) {
std::cout << elem << " ";
}
std::cout << std::endl;
// 取出优先级最高的元素
if (!priorityQueue.empty()) {
auto top = *priorityQueue.begin();
std::cout << "Top element: " << top << std::endl;
priorityQueue.erase(priorityQueue.begin());
}
return 0;
}
使用 multiset 实现了一个简单的优先级队列,元素按照降序排列,优先级最高的元素在队列的前面。
multiset 是 C++ 标准库中一个非常有用的关联容器,它允许存储多个相同的元素,并且元素按照一定的顺序排列。multiset 基于红黑树实现,插入、删除和查找操作的时间复杂度为 O(logn)。通过自定义比较函数,可以改变元素的排序规则。multiset 在统计元素出现次数、排序和去重、实现优先级队列等场景中有广泛的应用。在使用 multiset 时,需要考虑其性能特点,根据具体的需求选择合适的容器。

using声明在模板编程中有着重要应用,如定义模板类型别名等。