我有很多套,例如{{2,4,5} , {4,5}, ...}.
给定其中一个子集,我想迭代所有其他子集,这些子集是该子集的严格子集。也就是说,如果我对集A
感兴趣,例如{2,4,5}
,我想找到所有集合B
,其中相对补集B / A = {},
为空集。一些可能性可能是{2,4}
,{2,5}
但不是{2,3}
我当然可以线性搜索并每次检查,但我正在为更大的集合和子集(如果重要)寻找有效的数据结构。子集的数量通常为数十万,但如果它有所不同,我会对它可能达到数亿的情况感兴趣。子集的大小通常为10秒。
我用C ++编程
由于
答案 0 :(得分:7)
在数学上,你应该为你的集合构造Hasse diagram,这将是部分有序的集合,其顶点是你的集合和由包含给出的箭头。基本上,如果A --> B
严格包含A
并且没有B
严格C
,则您希望使用箭头A
创建directed, acyclic graph包含C
和C
严格包含B
。
这实际上将是一个排名的poset,这意味着你可以根据集合的基数来跟踪有向图的“级别”。这有点像创建哈希表以跳转到正确的集合。
从A
开始,只需在图表中按BFS查找A
的所有正确子集。
如何实现:(伪代码)
for (C in sets) {
for (B in HasseDiagram at rank rank(C)+1) {
if (C contains B)
addArrow(C,B)
}
for (A in HasseDiagram at rank rank(C)+1) {
if (C contains A)
addArrow(A,C)
}
addToDiagram(C)
}
为了快速完成此子程序和所有子程序,如果i
位于1
且{{1} i
,则可以对每个数据集C
0
进行编码。 }} 除此以外。这使得测试遏制和确定等级变得微不足道。
如果您拥有所有可能的子集,则上述方法可以。既然你可能错过了一些,你将不得不检查更多的东西。对于伪代码,您需要将rank(C)-1
更改为最大整数l < rank(C)
,以使HasseDiagram的某些元素具有等级l
,对rank(C)+1
类似。然后,当您将图集C
添加到图表中时:
如果A
涵盖C
,那么您只需要检查B
所涵盖的排名较低的集A
。
如果C
涵盖B
,那么您只需要查看排名较高的A
B
,X
。
“Y
封面X -> Y
”我的意思是有一个箭头C
,而不仅仅是一条路径。
此外,当您使用上述检查之一在A
和B
之间插入A --> B
时,您需要在添加{{1}时删除箭头A --> C
}和C --> B
。
答案 1 :(得分:4)
我建议将所有集合存储在树中。树的每个节点将表示包含指定的整数初始列表的所有集合。我希望节点包含以下信息:
鉴于此树和子集,您可以使用递归和回溯搜索集合的所有子集。在搜索中,您从子集的第一个元素开始,查找包含该元素的所有子集,然后搜索不包含该元素的所有子集。
构建此树最多占用时间和空间O(n * m * k)
,其中n
是子集数m
是每个子集的平均元素数,k
是可以在集合中的元素世界的大小。使用比k
元素的子集可能范围小得多的随机集合集,您将不会构建大部分树,并且您的树将需要O(n * m)
。
理论上,遍历这棵树的时间可能是O(n)
。 但在实践中你会很早地修剪树的分支,并且不会遍历大多数其他子集。封套计算的背面表明,如果n
元素Universe中有k
个随机集n << 2k
,那么对该树的搜索是O(n0.5k)
。 (在每个整数中,你在集合中搜索子集的时间的一半,你将搜索分成2,一半时间不在你的集合中,你消除了一半的空间。{{ 1}}你有j
个整数搜索大小为2j/2
的集合的整数。因此,当你将搜索结果缩小到单个其他子集进行比较时,有{{1} }搜索继续。位图的最终比较是2-jn
。)
注意:我相信信封计算后面的每个O(n0.5)
的平均效果为O(k)
,但收敛速度很慢。更确切地说,我怀疑性能的算术平均值是o(n0.5+epsilon)
。但epsilon > 0
篇需要很长时间才能收敛。
请注意,使用树中此点或更低位置的最小集合中的附加元素数量,可以使搜索过滤掉所有太大而不能成为子集的集合。根据您的数据集,这可能会或可能不会带来有用的加速。
答案 2 :(得分:3)
PengOne提出的方法可行,但效率不高。要了解它失败的原因,请考虑以下病态示例:
假设你有一个宇宙U,它有n个不同的元素,让你搜索的所有集合的集合包含U的所有子集,其中只有k个元素。那么这里没有一对集合严格地包含在一起;所以在最坏的情况下你必须搜索所有n选择k个可能的集合!换句话说,在最坏的情况下,使用他提出的数据结构并不比天真的线性搜索更好。
显然,你可以做得比这更好,正确使用的数据结构将是一个特里:http://en.wikipedia.org/wiki/Trie
要使trie适用于集合而不仅仅是字符串,只需在通用集的元素上修改排序就足够了,然后将每个子集编码为有限长度的二进制字符串,其中第i个字符是0或1取决于集合是否包含第i个元素。这是python中的一个实现
import math
class SetTree:
def __init__(self, index, key, left, right):
self.index = index
self.key = key
self.left = left
self.right = right
cached_trees = { }
cached_index = 2
def get_index(T):
if isinstance(T, SetTree):
return T.index
if T:
return 1
return 0
def make_set_tree(key, left, right):
global cached_trees, cached_index
code = (key, get_index(left), get_index(right))
if not code in cached_trees:
cached_trees[code] = SetTree(cached_index, key, left, right)
cached_index += 1
return cached_trees[code]
def compute_freqs(X):
freqs, total = {}, 0
for S in X:
for a in S:
if a in freqs:
freqs[a] += 1
else:
freqs[a] = 1
total += 1
U = [ (-f, a) for a,f in freqs.items() ]
U.sort()
return U
#Constructs the tree recursively
def build_tree_rec(X, U):
if len(X) == 0:
return False
if len(U) == 0:
return True
key = U[0][1]
left_elems = [ S for S in X if key in S]
if len(left_elems) > 0:
return make_set_tree(key,
build_tree_rec(left_elems, U[1:]),
build_tree_rec([ S for S in X if not key in S ], U[1:]))
return build_tree_rec(X, U[1:])
#Build a search tree recursively
def build_tree(X):
U = compute_freqs(X)
return build_tree_rec(X, U)
#Query a set tree to find all subsets contained in a given set
def query_tree(T, S):
if not isinstance(T, SetTree):
return [ [] ] if T else []
if T.key in S:
return [ U + [ T.key ] for U in query_tree(T.left, S) ] + query_tree(T.right, S)
return query_tree(T.right, S)
#Debugging function: Converts a tree to a tuple for printing
def tree_to_tuple(T):
if isinstance(T, SetTree):
return (T.key, tree_to_tuple(T.left), tree_to_tuple(T.right))
return T
现在这是一个示例用法:
In [15]: search_tree = set_search.build_tree(set_family)
In [16]: set_search.tree_to_tuple(search_tree)
Out[16]:
(2,
(4, (5, True, True), (5, True, (3, True, False))),
(4, (5, True, False), (1, True, False)))
In [17]: set_search.query_tree(search_tree, set([2,3,4,5]))
Out[17]: [[5, 4, 2], [4, 2], [5, 2], [3, 2], [5, 4]]
In [18]: set_search.query_tree(search_tree, set([1,2,3,4,5]))
Out[18]: [[5, 4, 2], [4, 2], [5, 2], [3, 2], [5, 4], [1]]
In [19]: set_search.query_tree(search_tree, set([2,4,5]))
Out[19]: [[5, 4, 2], [4, 2], [5, 2], [5, 4]]
In [20]: set_search.query_tree(search_tree, set([2,5]))
Out[20]: [[5, 2]]
In [21]: set_search.query_tree(search_tree, set([1]))
Out[21]: [[1]]
In [22]: set_search.query_tree(search_tree, set([15]))
Out[22]: []
请注意,query_tree执行的工作量与子树的大小成比例,子树的大小表示query_tree返回的所有结果的集合。因此,我们的目标是计算其中一个子项的大小(平均),然后作为次要目标来最小化此数量。实现此目的的一种方法是根据下降频率对通用元素进行重新排序,以便在树的较低级别中尽可能少地重复这些元素。这种优化也在上面的代码中完成。辅助优化是缓存已经搜索过的树,以避免重做不必要的工作。
编辑:在我完成输入之后,我看到了btilly的答案,这或多或少得出了关于这个问题的相同结论(模拟了一些技术挑剔,我已将其转移到他的帖子的评论中。)< / p>编辑2:意识到这实际上只是二元决策图的一个特例。现在没有足够的精力来修复写入,所以会保持原样。或许明天再修好。 http://en.wikipedia.org/wiki/Binary_decision_diagram
答案 3 :(得分:0)
这很有趣。我喜欢PengOne建议的Hasse图表方法,但我认为你可以使用素数技巧快速构建Hasse图。假设所有集合的并集导致自然数字1到N.将这些数字中的每一个映射到相应的素数,例如:
PrimeMap [1] = 2;
PrimeMap [2] = 3;
PrimeMap [3] = 5;
接下来,通过乘以对应于集合中的数字的每个素数来计算每个集合的“得分”。例如,集合{1,2,3}将得分为2 * 3 * 5 = 30.现在,对于集合A是另一集合的适当子集,B得分(A)必须除以得分(B)(得分) {1,2},{2,3}和{1,3}为6,15和10,每个除以30)。使用此分数来构建Hasse图表。
编辑:这似乎是一个很好的理论解决方案。可能不是要走的路。 yi_H建议的位集也同样好,不会遇到大整数问题。
答案 4 :(得分:0)
看看这个实现Hasse图的python库python-lattice] 1