C++ std::set
与 std::multiset
深度比较
两者都基于红黑树实现,元素自动排序,但关键区别在于元素唯一性要求。下面我将全面分析它们的差异,并提供详细的代码示例。
1. 核心差异概览
特性 | std::set |
std::multiset |
---|---|---|
元素唯一性 | 要求元素唯一 | 允许重复元素 |
插入返回值 | pair<iterator, bool> |
iterator |
count() 行为 | 返回0或1 | 返回元素出现次数 |
find() 行为 | 返回唯一元素位置 | 返回第一个匹配元素位置 |
equal_range() | 范围大小为1或0 | 范围大小可能大于1 |
erase(key) | 删除0或1个元素 | 删除所有匹配元素 |
2. 详细差异分析(含代码示例)
2.1 元素唯一性要求
std::set
:强制元素唯一性,插入重复元素会被忽略
cpp
#include <iostream>
#include <set>
int main() {
std::set<int> uniqueSet;
// 插入元素
auto [it1, inserted1] = uniqueSet.insert(10);
std::cout << "Inserted 10: " << std::boolalpha << inserted1 << "\n"; // true
auto [it2, inserted2] = uniqueSet.insert(10);
std::cout << "Inserted 10 again: " << inserted2 << "\n"; // false
std::cout << "Set elements: ";
for (int n : uniqueSet) std::cout << n << " "; // 10
std::cout << "\n\n";
}
std::multiset
:允许元素重复
cpp
#include <iostream>
#include <set>
int main() {
std::multiset<int> multiSet;
// 插入重复元素
auto it1 = multiSet.insert(20);
auto it2 = multiSet.insert(20);
std::cout << "Multiset elements: ";
for (int n : multiSet) std::cout << n << " "; // 20 20
std::cout << "\n\n";
}
2.2 插入操作返回值差异
std::set
:返回pair<iterator, bool>
first
: 指向插入/存在的元素second
: 是否成功插入
cpp
std::set<std::string> animalSet;
// 新元素插入
auto [lionIt, lionInserted] = animalSet.insert("Lion");
std::cout << "Inserted Lion: " << lionInserted << "\n"; // true
// 重复元素
auto [dupIt, dupInserted] = animalSet.insert("Lion");
std::cout << "Inserted Lion again: " << dupInserted << "\n"; // false
std::cout << "Existing element: " << *dupIt << "\n"; // Lion
std::multiset
:返回指向新元素的迭代器
cpp
std::multiset<std::string> fruitMultiSet;
// 总是返回新元素的迭代器
auto appleIt1 = fruitMultiSet.insert("Apple");
auto appleIt2 = fruitMultiSet.insert("Apple");
std::cout << "First Apple at: " << std::distance(fruitMultiSet.begin(), appleIt1) << "\n";
std::cout << "Second Apple at: " << std::distance(fruitMultiSet.begin(), appleIt2) << "\n";
2.3 查找与计数行为差异
count()
方法:
cpp
std::set<int> singleSet = {1, 2, 3};
std::multiset<int> multiSet = {1, 1, 2, 2, 2, 3};
std::cout << "Set count(2): " << singleSet.count(2) << "\n"; // 1
std::cout << "Multiset count(2): " << multiSet.count(2) << "\n"; // 3
find()
方法:
cpp
std::multiset<int> numbers = {10, 20, 20, 20, 30};
// 返回第一个匹配元素
auto foundIt = numbers.find(20);
if (foundIt != numbers.end()) {
std::cout << "First 20 found at position: "
<< std::distance(numbers.begin(), foundIt) << "\n"; // 1
// 遍历所有20
std::cout << "All 20s: ";
auto [begin, end] = numbers.equal_range(20);
for (auto it = begin; it != end; ++it) {
std::cout << *it << " "; // 20 20 20
}
}
2.4 范围查询差异
equal_range()
:
cpp
// Set示例
std::set<int> uniqueNums = {5, 10, 15, 20};
auto [setLow, setHigh] = uniqueNums.equal_range(10);
std::cout << "Set range size: "
<< std::distance(setLow, setHigh) << "\n"; // 1
// Multiset示例
std::multiset<int> duplicateNums = {5, 10, 10, 10, 15, 20};
auto [multiLow, multiHigh] = duplicateNums.equal_range(10);
std::cout << "Multiset range size: "
<< std::distance(multiLow, multiHigh) << "\n"; // 3
2.5 删除操作差异
erase(key)
:
cpp
std::multiset<int> data = {1, 2, 2, 3, 3, 3, 4};
// 删除所有2
size_t count = data.erase(2);
std::cout << "Erased " << count << " elements\n"; // 2
// 删除单个3
auto it = data.find(3);
if (it != data.end()) {
data.erase(it); // 只删除第一个3
}
// 结果: 1, 3, 3, 4
std::cout << "After erasures: ";
for (int n : data) std::cout << n << " ";
2.6 迭代器稳定性
std::multiset
中重复元素的迭代器:
cpp
std::multiset<std::string> taskSet = {"Write", "Test", "Debug"};
// 获取迭代器
auto writeIt = taskSet.find("Write");
auto testIt = taskSet.find("Test");
// 插入重复元素
taskSet.insert("Test");
taskSet.insert("Test");
// 原始迭代器仍然有效
std::cout << "Original Test: " << *testIt << "\n"; // Test
std::cout << "Write still exists: " << *writeIt << "\n"; // Write
// 但新元素可能插入在等价元素之间
std::cout << "All tasks: ";
for (const auto& t : taskSet)
std::cout << t << " "; // Debug Test Test Test Write
3. 性能对比
操作 | std::set |
std::multiset |
说明 |
---|---|---|---|
插入 (insert) | O(log n) | O(log n) | 两者相同 |
查找 (find) | O(log n) | O(log n) | 两者相同 |
计数 (count) | O(log n) | O(log n + k) | k为元素出现次数 |
范围删除 (erase) | O(k log n) | O(k log n) | k为删除元素数量 |
按值删除 (erase) | O(log n) | O(log n + k) | multiset需要删除所有副本 |
内存使用 | 通常较少 | 可能更多 | 取决于重复元素数量 |
4. 使用场景对比
何时使用 std::set
-
需要确保元素唯一性的场景
cppstd::set<std::string> uniqueUsers; void addUser(const std::string& name) { auto [_, inserted] = uniqueUsers.insert(name); if (inserted) { std::cout << "User added\n"; } else { std::cout << "User already exists\n"; } }
-
需要检查元素是否存在的场景
cppstd::set<int> validCodes = {200, 201, 204, 400, 404}; bool isValid(int code) { return validCodes.find(code) != validCodes.end(); }
-
需要键值对但不需要映射的场景(使用set)
cppstd::set<std::pair<int, int>> coordinateSet; coordinateSet.insert({10, 20}); coordinateSet.insert({30, 40});
何时使用 std::multiset
-
需要保留重复元素的排序序列
cppstd::multiset<double> temperatureReadings; void recordTemp(double temp) { temperatureReadings.insert(temp); } void printSortedReadings() { for (double t : temperatureReadings) { std::cout << t << "°C "; } }
-
需要统计元素频率
cppstd::multiset<std::string> wordBag; // 填充数据... void wordFrequency(const std::string& word) { size_t count = wordBag.count(word); std::cout << word << " appears " << count << " times\n"; }
-
需要处理等价元素的序列
cppstruct CaseInsensitiveCompare { bool operator()(const std::string& a, const std::string& b) const { return std::lexicographical_compare( a.begin(), a.end(), b.begin(), b.end(), [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } }; std::multiset<std::string, CaseInsensitiveCompare> caseInsensitiveSet; caseInsensitiveSet.insert("Apple"); caseInsensitiveSet.insert("apple"); // 视为不同元素
5. 高级技巧
5.1 安全修改元素 (C++17+)
cpp
std::set<Person> people = {{"Alice", 30}, {"Bob", 25}};
// 提取并修改
auto node = people.extract({"Alice", 30});
if (!node.empty()) {
node.value().age = 31; // 安全修改
people.insert(std::move(node));
}
// 在multiset中也可使用
std::multiset<int> nums = {1, 2, 2, 3};
auto node = nums.extract(2); // 提取第一个2
if (!node.empty()) {
node.value() = 4; // 修改为4
nums.insert(std::move(node));
}
5.2 高效合并容器 (C++17+)
cpp
std::set<int> set1 = {1, 3, 5};
std::set<int> set2 = {2, 4, 5}; // 注意重复元素
// 合并set
set1.merge(set2);
// set1: {1,2,3,4,5}, set2: {5}
std::multiset<int> mset1 = {1, 1, 2};
std::multiset<int> mset2 = {1, 2, 3};
// 合并multiset
mset1.merge(mset2);
// mset1: {1,1,1,2,2,3}, mset2: 空
总结
std::set
和std::multiset
的主要差异在于元素唯一性处理:
- 使用
std::set
时,每个元素必须是唯一的,适用于需要确保唯一性的场景 - 使用
std::multiset
时,允许元素重复,适用于需要保留重复元素的排序序列
选择建议:
- 需要唯一性保证 → 选择
std::set
- 需要保留重复元素 → 选择
std::multiset
- 需要高效元素频率统计 → 选择
std::multiset
- 需要范围查询且结果唯一 → 选择
std::set
两者共享相同的底层实现(红黑树),提供O(log n)的查找、插入和删除操作。从C++17开始,两者都支持高效的节点操作(extract/merge),可以安全修改元素内容并高效合并容器。