我有一组独特的集合(表示为位掩码),并希望消除作为另一个元素的正确子集的所有元素。例如:
input = [{1, 2, 3}, {1, 2}, {2, 3}, {2, 4}, {}]
output = [{1, 2, 3}, {2, 4}]
我无法为此找到一个标准算法,甚至找不到这个问题的名称,所以我称其为“最大子集”,因为缺少其他任何东西。这是一个O(n ^ 2)算法(用Python来表示具体性),假设is_subset_func
是O(1): 1
def eliminate_subsets(a, cardinality_func, is_subset_func):
out = []
for element in sorted(a, reverse=True, key=cardinality_func):
for existing in out:
if is_subset_func(element, existing):
break
else:
out.append(element)
return out
是否有更高效的算法,希望是O(n log n)或更好?
1 对于常量大小的位掩码,在我的情况下也是如此,is_subset_func
只是element & existing == element
,它在恒定时间内运行。
答案 0 :(得分:15)
假设您标记了所有输入集。
A={1, 2, 3}, B={1, 2}, C={2, 3}, D={2, 4}, E={}
现在构建中间集,每个元素在Universe中一个,包含它出现的集合的标签:
1={A,B}
2={A,B,C,D}
3={A,C}
4={D}
现在,对于每个输入集,计算其元素的所有标签集的交集:
For A, {A,B} intesect {A,B,C,D} intersect {A,C} = {A} (*)
如果交集包含除集合本身之外的某些标签,那么它就是该集合的子集。这里没有其他元素,所以答案是否定的。但是,
For C, {A,B,C,D} intersect {A,C} = {A,C}, which means that it's a subset of A.
此方法的成本取决于集合的实现。假设位图(正如您所暗示的那样)。假设有n个最大大小为m和| U |的输入集宇宙中的物品。然后中间组构造产生| U |大小为n位的集合,因此有O(| U | n)时间来初始化它们。设置位需要O(nm)时间。将每个交叉点计算为(*)
以上需要O(mn); O(mn ^ 2)为所有人。
将所有这些放在一起我们得到O(| U | n)+ O(nm)+ O(mn ^ 2)= O(| U | n + mn ^ 2)。使用相同的约定,您的“所有对”算法是O(| U | ^ 2 n ^ 2)。由于m <= | U |,该算法渐近地更快。它在实践中也可能更快,因为没有精心设置的簿记来增加常数因素。
添加:在线版
OP询问是否存在该算法的在线版本,即,当输入集一个接一个地到达时,可以递增地保持最大集合集合。答案似乎是肯定的。如果新集合是已经看到的子集,则中间集会快速告诉我们。但是如何快速判断它是否是超集?如果是这样,其中现有的最大集合?因为在这种情况下,那些最大集合不再是最大集合,必须用新的集合替换。
关键是要注意A
是B
的超集iff A'
是B'
的一个子集(tick'表示集补码)。
在这个灵感之后,我们像以前一样保持中间体。当新输入集S
到达时,执行与上述相同的测试:让I(e)
成为输入元素e
的中间集。然后这个测试是
For X = \intersect_{e \in S} . I(e), |X| > 0
(在这种情况下,它大于零而不是如上所述,因为S
尚未在I
中。)如果测试成功,则新集合是(可能是不正确的)子集现有的最大集合,因此可以丢弃。
否则我们必须将S
添加为新的最大集,但在此之前,请计算:
Y = \intersect_{e \in S'} . I'(e) = ( \union_{e \in S'} . I(e) )'
再次将tick'设置为补码。联合表单的计算速度可能会快一些。 Y
包含S
取代的最大集合。必须从最大集合和I
中删除它们。最后添加S
作为最大集合,并使用I
的元素更新S
。
让我们通过我们的例子。当A
到达时,我们会将其添加到I
并拥有
1={A} 2={A} 3={A}
当B
到达时,我们会找到X = {A} intersect {A} = {A}
,因此抛弃B
并继续。 C
也是如此。当D
到达时,我们会找到X = {A} intersect {} = {}
,因此请继续Y = I'(1) intersect I'(3) = {} intersect {}
。这正确地告诉我们A
中没有包含最大集合D
,因此没有任何内容可以删除。但必须将其添加为新的最大集合,I
变为
1={A} 2={A,D} 3={A} 4={D}
E
的到来不会导致任何变化。然后是新组F={2, 3, 4, 5}
的到来。我们找到了
X = {A} isect {A,D} isect {A} isect {D} isect {}
所以我们不能抛弃F
。
Y = \intersect_{e in {1}} I'(e) = I'(1) = {D}
这告诉我们D
是F
的子集,因此应在F
添加时丢弃
1={A} 2={A,F} 3={A,F} 4={F} 5={F}
由于算法的在线性质,补码的计算既棘手又好。输入补充的Universe只需要包含到目前为止看到的输入元素。中间集的Universe仅包含当前最大集合中的集合的标记。对于许多输入流,此组的大小将随着时间的推移而稳定或减小。
我希望这有用。
摘要
这里的一般原则是一种强有力的想法,即经常在算法设计中的作物。这是反向地图。每当您发现自己进行线性搜索以查找具有给定属性的项目时,请考虑从属性到项目构建地图。构建此地图通常很便宜,并且大大缩短了搜索时间。最重要的示例是一个排列映射p[i]
,它告诉您在置换数组后i
'元素占据的位置。如果您需要搜索最终位于指定位置a
的项目,则必须在p
搜索线性时间操作a
。另一方面,反向地图pi
使得pi[p[i]] == i
不再需要计算p
(因此其费用为“隐藏”),但是pi[a]
会产生在恒定时间内获得理想的结果。
通过原始海报实施
import collections
import operator
def is_power_of_two(n):
"""Returns True iff n is a power of two. Assumes n > 0."""
return (n & (n - 1)) == 0
def eliminate_subsets(sequence_of_sets):
"""Return a list of the elements of `sequence_of_sets`, removing all
elements that are subsets of other elements. Assumes that each
element is a set or frozenset and that no element is repeated."""
# The code below does not handle the case of a sequence containing
# only the empty set, so let's just handle all easy cases now.
if len(sequence_of_sets) <= 1:
return list(sequence_of_sets)
# We need an indexable sequence so that we can use a bitmap to
# represent each set.
if not isinstance(sequence_of_sets, collections.Sequence):
sequence_of_sets = list(sequence_of_sets)
# For each element, construct the list of all sets containing that
# element.
sets_containing_element = {}
for i, s in enumerate(sequence_of_sets):
for element in s:
try:
sets_containing_element[element] |= 1 << i
except KeyError:
sets_containing_element[element] = 1 << i
# For each set, if the intersection of all of the lists in which it is
# contained has length != 1, this set can be eliminated.
out = [s for s in sequence_of_sets
if s and is_power_of_two(reduce(
operator.and_, (sets_containing_element[x] for x in s)))]
return out
答案 1 :(得分:3)
这个问题已经在文献中进行了研究。给定S_1,...,S_k是{1,...,n}的子集,Yellin [1]给出了一个算法,用于在时间O(kdm)中找到{S_1,...,S_k}的最大子集其中d是S_i的平均大小,m是{S_1,...,S_k}的最大子集的基数。后来,Yellin和Jutla [2]对O((kd)^ 2 / sqrt(log(kd)))的一些参数范围进行了改进。据信,不存在针对该问题的真正的次二次算法。
[1] Daniel M. Yellin:子集测试和查找最大集的算法。 SODA 1992:386-392。
[2] Daniel M. Yellin,Charanjit S. Jutla:在不到二次的时间内寻找极值集。天道酬勤。处理。快报。 48(1):29-34(1993)。
答案 2 :(得分:2)
我的头顶有一个O(D * N * log(N)),其中D是唯一数字的数量。
递归函数“helper”的工作原理如下: @arguments是集合和域(集合中的唯一数字的数量): 基本情况:
迭代案例:
请注意,运行时取决于使用的Set实现。如果使用双向链表来存储该集,则:
步骤1-5,7取O(N) 步骤6的联合是O(N * log(N))通过排序然后合并
因此整体算法为O(D * N * log(N))
以下是执行以下内容的java代码
import java.util.*;
public class MyMain {
public static Set<Set<Integer>> eliminate_subsets(Set<Set<Integer>> sets) throws Exception {
Set<Integer> domain = new HashSet<Integer>();
for (Set<Integer> set : sets) {
for (Integer i : set) {
domain.add(i);
}
}
return helper(sets,domain);
}
public static Set<Set<Integer>> helper(Set<Set<Integer>> sets, Set<Integer> domain) throws Exception {
if (domain.isEmpty()) { return sets; }
if (sets.isEmpty()) { return sets; }
else if (sets.size() == 1) { return sets; }
sets.remove(new HashSet<Integer>());
// Pop some value from domain
Iterator<Integer> it = domain.iterator();
Integer splitNum = it.next();
it.remove();
Set<Set<Integer>> set1 = new HashSet<Set<Integer>>();
Set<Set<Integer>> set2 = new HashSet<Set<Integer>>();
for (Set<Integer> set : sets) {
if (set.contains(splitNum)) {
set.remove(splitNum);
set1.add(set);
}
else {
set2.add(set);
}
}
Set<Set<Integer>> ret = helper(set1,domain);
ret.addAll(helper(set2,domain));
for (Set<Integer> set : set1) {
set.add(splitNum);
}
return ret;
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Set<Set<Integer>> s=new HashSet<Set<Integer>>();
Set<Integer> tmp = new HashSet<Integer>();
tmp.add(new Integer(1)); tmp.add(new Integer(2)); tmp.add(new Integer(3));
s.add(tmp);
tmp = new HashSet<Integer>();
tmp.add(new Integer(1)); tmp.add(new Integer(2));
s.add(tmp);
tmp = new HashSet<Integer>();
tmp.add(new Integer(3)); tmp.add(new Integer(4));
s.add(tmp);
System.out.println(eliminate_subsets(s).toString());
}
}
*新的一年是破坏性的
答案 3 :(得分:1)
预处理假设:
相同的假设。 可以假设唯一性吗? (即没有{1,4,6},{1,4,6}) 否则,您可能需要在某些时候检查不同,可能是在创建存储桶之后。
半puedoList<Set> Sets;//input
List<Set> Output;
List<List<Set>> Buckets;
int length = Sets[0].length;//"by descending lengths"
List<Set> Bucket = new List<Set>();//current bucket
//Place each set with shared length in its own bucket
for( Set set in Sets )
{
if( set.length == length )//current Bucket
{
Bucket.add(set);
}else//new Bucket
{
length = set.length;
Buckets.Add(Bucket);
Bucket = new Bucket();
Bucket.Add(set);
}
}
Buckets.add(Bucket);
//Based on the assumption of uniqueness, everything in the first bucket is
//larger than every other set and since it is unique, they are not proper subsets
Output.AddRange(Buckets[0]);
//Iterate through the buckets
for( int i = 1; i < Buckets.length; i++ )
{
List<Set> currentBucket = Buckets[i];
//Iterate through the sets in the current bucket
for( int a = 0; a < currentBucket.length; a++ )
{
Set currentSet = currentBucket[a];
bool addSet = true;
//Iterate through buckets with greater length
for( int b = 0; b < i; b++ )
{
List<Set> testBucket = Buckets[b];
//Iterate through the sets in testBucket
for( int c = 0; c < testBucket.length; c++ )
{
Set testSet = testBucket[c];
int testMatches = 0;
//Iterate through the values in the current set
for( int d = 0; d < currentSet.length; d++ )
{
int testIndex = 0;
//Iterate through the values in the test set
for( ; testIndex < testSet.length; testIndex++ )
{
if( currentSet[d] < testSet[testIndex] )
{
setClear = true;
break;
}
if( currentSet[d] == testSet[testIndex] )
{
testMatches++;
if( testMatches == currentSet.length )
{
addSet = false;
setClear = true;
break;
}
}
}//testIndex
if( setClear ) break;
}//d
if( !addSet ) break;
}//c
if( !addSet ) break;
}//b
if( addSet ) Output.Add( currentSet );
}//a
}//i
O( n(n+1)/2 )
)......效率不够//input Sets
List<Set> results;
for( int current = 0; current < Sets.length; current++ )
{
bool addCurrent = true;
Set currentSet = Sets[current];
for( int other = 0; other < current; other++)
{
Set otherSet = Sets[other];
//is current a subset of other?
if( currentSet.total > otherSet.total
|| currentSet.length >= otherSet.length) continue;
int max = currentSet.length;
int matches = 0;
int otherIndex = 0, len = otherSet.length;
for( int i = 0; i < max; i++ )
{
for( ; otherIndex < len; otherIndex++ )
{
if( currentSet[i] == otherSet[otherInex] )
{
matches++;
break;
}
}
if( matches == max )
{
addCurrent = false;
break;
}
}
if( addCurrent ) results.Add(currentSet);
}
}
这将采用一组集合,并遍历每一组。对于每一个,它将再次遍历集合中的每个集合。当嵌套迭代发生时,它将比较外部集合是否与嵌套集合(来自内部迭代)(如果是,不进行检查),它还将比较外部集合是否具有更大的集合比嵌套集(如果总数更大,那么外集不能是一个合适的子集),它将比较外集的项数是否少于嵌套集。
完成这些检查后,它将从外部集的第一项开始,并将其与嵌套集的第一项进行比较。如果它们不相等,它将检查嵌套集的下一项。如果它们相等,那么它会向计数器添加一个,然后将外部集合的下一个项目与内部集合中的下一个项目进行比较。
如果它达到匹配比较量等于外部集合中的项目数的点,则发现外部集合是内部集合的适当子集。它被标记为被排除,比较暂停。