PengOne 建议的方法可行,但效率不高。要了解它失败的原因,请考虑以下病态示例:
假设你有一个全域 U,它有 n 个不同的元素,并让你正在搜索的所有集合的集合由 U 的所有子集组成,其中正好有 k 个元素。那么,这里没有任何一对集合是严格包含在彼此中的。因此,在最坏的情况下,您将不得不搜索所有 n 选择 k 个可能的集合!换句话说,在最坏的情况下,使用他提出的数据结构并不比简单的线性搜索好。
显然,您可以做得比这更好,并且要使用的正确数据结构将是一个 trie: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 的答案,这或多或少地得出了关于该问题的相同结论(以我在他的帖子中移入 cmets 的一些技术挑剔为模。)
编辑 2:意识到这实际上只是二元决策图的一个特例。现在真的没有足够的精力来修复这篇文章,所以就让它保持原样吧。也许明天修复它。 http://en.wikipedia.org/wiki/Binary_decision_diagram