c++ - 为什么 multiset 保留重复元素的单独实例而不是它们的数量?

标签 c++ time-complexity multiset unordered-multiset

我最近发现 multiset<T> STL 中的实现实际上在树中保留相同重复元素的不同拷贝。 我之前的期望是它在内部使用 map<T, int>并只保留重复元素的计数。

与仅保持计数相比,这种实现在什么情况下可以带来好处? multiset 有任何用例吗?如果内部实现发生变化,代码会在哪里中断?或者有没有什么操作如果改变了会增加复杂度?

我想知道该选择背后的思考过程是什么?

最佳答案

默认 std::multiset使用 operator<决定两个元素是否等价(注意:等价不等价!)。现在考虑这种元素类型:

struct foo { 
    int x;
    int y;
    bool operator<(const foo& other) { return x < other.x; }
    bool operator==(const foo& other) { return (x==other.x) && (y==other.y);
};

!(a < b) && !(b < a) 时,两个元素被认为是等价的,这通常并不意味着 a == b .因此,仅存储计数是不够的。

此外,即使相等也不意味着身份( a==b 并不意味着 &a==&b )。由于容器拥有它的元素,容器中的两个元素不可能相同,它们总是两个不同的对象。另外,在上面foo我们可以添加一个成员 z这既不考虑operator<也不是 operator==但可以通过 getter 访问。


排序容器通常检查等价性而不是相等性的原因是它们无论如何都需要一种方法来比较元素(它们是排序的)。能够通过 < 比较两个元素足以检查等效性,同时需要 ==此外会使容器不那么通用而没有明显的好处。如果你愿意,你总是可以使用一个比较器,它使得等价性确实意味着相等(在上面的例子中:在 y 中也比较 operator<)。


正如评论中已经提到的,如果你想要你描述的行为,你可以使用 std::map<foo,size_t> (也使用 operator< 作为键的默认比较器)。而不是只存储 foo您可以存储一对 foo和伯爵。现在,因为两个 foox相同被认为是等价的,它们确实映射到相同的键:

 foo f,g;
 f.x = 42; f.y = 0;
 g.x = 42; g.y = 100;
 std::map<foo,size_t> counter;
 ++counter[f];
 ++counter[g];

因此,counter将包含 1 个元素:f 的拷贝该键的计数将为 2 .

关于c++ - 为什么 multiset 保留重复元素的单独实例而不是它们的数量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68372289/

相关文章:

c++ - 为什么这两个片段会产生不同的结果?

java - 在 Guava 中,如何创建具有单个元素和 n 次出现的 Multiset

java - 这个简单程序的运行时间——时间复杂度

c# - 非 bool 值 "truth table"创建

algorithm - 从两个 3D 多集数组中找到任意两个相应多集的交集大小的更快方法

c++ - std::binding to a lambda: 编译错误

c++ - 将遗留 C 代码集成到多线程 C++ 代码中

c++ - 为什么C++语言的malloc函数前面要写指针类型?

algorithm - 有没有一种通用的方法来计算带步骤的for循环的渐近时间复杂度?

algorithm - 迭代程序的时间复杂度分析