我有独特的套(表示为位掩码)的集合,并想以消除所有其他元素的真子集的所有元素。 例如:
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
,它运行在固定的时间。
假设你标记所有的输入集。
A={1, 2, 3}, B={1, 2}, C={2, 3}, D={2, 4}, E={}
现在建立中间套,每在宇宙中元件之一,包含在那里出现的集的标签:
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} (*)
如果交集包含比设定本身以外的一些标签,那么它是一套SA子集。 这里没有其他的元素,所以答案是否定的。 但,
For C, {A,B,C,D} intersect {A,C} = {A,C}, which means that it's a subset of A.
这种方法的成本取决于套执行。 假设位图(如你暗示)。 说,有的最大大小m×n个输入集和| U | 在宇宙中的项目。 然后,中间组结构产生| U | 集大小为n比特,因此有O(| U | n)的时间来初始化它们。 设置比特需要O(nm)的时间。 计算每个交叉点如(*)
以上需要O(MN); O(MN ^ 2)所有。
把所有这些一起,我们有O(| U | N)+ O(纳米)+ O(MN ^ 2)= O(| U | N + MN ^ 2)。 使用相同的约定,你的 “所有对” 算法是O(| U | ^ 2 N ^ 2)。 由于m <= | U |,这种算法是渐近更快。 这很可能是在实践中也更快,因为没有复杂的簿记添加常数因子。
另外:在线版
在OP询问是否有这个算法的在线版本,即一个在一套套最大可以逐步保持为输入集到达一个接一个。 答案似乎是肯定的。 中间套很快告诉我们,如果一个新的集是已经看到一个子集 。 但是,如何快速判断它是一个集 ? 而且,如果是的话,它的最大的现有套? 在这种情况下,那些最大的集不再极大,必须由新的取代。
最关键的是要注意, A
是的超集B
当且仅当A'
是的一个子集B'
(刻度”表示补集)。
在此之后的灵感,我们维持中间设置如前。 当一个新的输入所设置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) )'
再次在滴答”设置的补充。 该联盟的形式可能有点快来计算。 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}
该补的计算既棘手和漂亮由于算法的在线本质。 输入宇宙补充仅需要包括迄今所看到的输入元素。 中间套宇宙只包括当前最大的收藏品中套标签。 对于许多输入流这一集的大小将趋于稳定或随时间而减少。
我希望这是有帮助的。
摘要
在这里工作的一般原则是一个功能强大的想法,在算法设计往往作物。 这是反向映射。 当你发现自己做了线性搜索,找到与特定属性的项目,考虑建设从属性映射回项目。 通常它是便宜构造此图,它强烈地减少搜索时间。 首要的例子是置换地图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
这个问题已经研究了文学。 鉴于S_1,...,S_K它们的子集{1,...,N},耶林[1]给出的算法找到{S_1,...,S_K}的时间为O的最大子集(KDM)其中d是S_I的平均尺寸,并且m是{S_1,...,S_K}的最大子集的基数。 此后来对于一些范围的由耶林和Jutla [2] O参数改善((KD)^ 2 / SQRT(日志(KD)))。 据认为,一个真正的次二次算法,这个问题不存在。
[1]丹尼尔M.耶林:用于子集测试和查找最大集算法。 1992年SODA:386-392。
[2]丹尼尔M.耶林,Charanjit S. Jutla:寻找极值设定在比二次时间要少。 天道酬勤。 处理。 快报。 48(1):29-34(1993)。
把我的头顶部有一个O(d * N *日志(N)),其中d是唯一的号码的数量。
递归函数“帮手”的工作原理如下:@arguments是组和域(套唯一编号的个数):基本情况:
- 如果域是空的,回报
- 如果集合为空,或者内置有长度等于1,返回
迭代的情况下:
- 拆除将所有空套
- 挑在域中的元素d
- 从域中删除d
- 单独组的成基于该组是否包含d两组(集1&SET2)
- 从各组在集删除d
- 设置结果=工会(辅助(集1,域),帮助(集2,域))
- 对于每一组中设置1加d回
- 返回结果
需要注意的是运行时间取决于所使用的设置执行。 如果一个双向链表被用来存储设置,则:
步骤1-5,7取O(N)步骤6的联合是O(N *日志(N))由分选,然后合并
因此总的算法是O(d * N *日志(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());
}
}
*新的一年是破坏性
前处理的假设:
输入组由降序排序长度 每个组由值排序升序 有访问总长度和为每个组
方法2 - 使用桶方法
同样的假设。 唯一可以假设? (即不存在{1,4,6},{1,4,6}),否则,你就需要在某一时刻检查不同,可能一旦创建桶。
半伪
List<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
方法#1( 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);
}
}
这将采取一套套,并通过每一个迭代。 随着每一个,它会通过每一组中集再次重复。 作为嵌套迭代发生,它会如果外集是相同的嵌套组(从内迭代)(如果是这样,不进行检查)比较,它也将比较,如果外组具有总更大比组嵌套(如果总越大,则外集不能是一个适当子集),它然后将比较,如果外集具有比项嵌套集合的量较小。
一旦这些检查完成它开始与外集的第一个项目,并将其与嵌套组的第一项进行比较。 如果它们不相等,它会检查嵌套组的下一个项目。 如果他们是平等的,那么它增加了一个计数器,然后将比较外组与之在内部的一组离开的地方的下一个项目。
如果达到一个点,匹配比较的量等于在所述外集的项目数量,然后所述外集已被发现是内组的适当子集。 它被标记被排除在外,而比较被停止。