我必须实现一个对等价类的元素进行分组的数据结构。
API:
interface Grouper<T>{
void same(T l, T r);
Set<EquivalenceClass<T>> equivalenceClasses();
}
interface EquivalenceClass<T>{
Set<T> members();
}
例如,分组的行为如下:
Grouper g;
g.same(a, b);
g.equivalenceClasses() -> [[a,b]]
g.same(b, a);
g.equivalenceClasses() -> [[a,b]]
g.same(b, c);
g.equivalenceClasses() -> [[a,b,c]]
g.same(d, e);
g.equivalenceClasses() -> [[a,b,c], [d,e]]
g.same(c, d);
g.equivalenceClasses() -> [[a,b,c,d]]
我正在寻找一个可以达到约1000万条目的实现。它应该被优化以填充它并获得等价类一次。
答案 0 :(得分:5)
看看Union-Find。联合(“相同”)可以在O(log N)
中轻松完成,并且可以通过一些优化在有效O(1)
中完成。 “equivalenceClasses”是O(N)
,这是访问所有内容的成本。
答案 1 :(得分:1)
如果您只想查询等价类一次,最好的解决方案是在元素上构建无向图。每个等价是两个项之间的无向边,等价类对应于连通的组件。如果你做得对,时间和空间的复杂性都是线性的。
或者,您可以使用Union-Find数据结构,这将为您提供几乎线性的时间复杂度。它也可以被认为更简单,因为所有的复杂性都被封装到数据结构中。 Union-Find不是线性的原因归结为在类增长时支持高效查询。
答案 2 :(得分:0)
Union-find是您问题的最佳数据结构,只要您只关心总运行时间(某些操作可能很慢,但所有操作的总成本保证几乎是线性的)。但是,教科书中的union-find的普通版本通常不支持枚举每个集合的成员。顾名思义,union-find通常只支持union(即same
)和find,它返回一个标识符,该标识符保证与查找同一集合中元素的调用返回的标识符相同。如果您需要枚举每个集合的成员,您可能必须自己实现它,以便您可以添加子指针,以便您可以遍历代表集合的每个树。
如果您自己实现这一点,则不必实现完整的union-find数据结构,以实现每个操作的分摊O(lg n)时间。本质上,在union-find的这个“轻”版本中,每个集合都是一个单链表,每个节点内都有一个额外的指针,指向一个集标识符节点,可以用来测试两个节点是否属于同一个列表。执行same
方法时,您可以将较小的列表附加到较大的列表,并更新较小列表的元素的集标识符。每个元素的总成本最多为O(lg n),因为元素可以是same
操作中涉及的最小O(lg n)次的较小列表的成员。