高效的算法找出所有极大子集(Efficient algorithm for finding all

2019-07-17 17:33发布

我有独特的套(表示为位掩码)的集合,并想以消除所有其他元素的真子集的所有元素。 例如:

input = [{1, 2, 3}, {1, 2}, {2, 3}, {2, 4}, {}]
output = [{1, 2, 3}, {2, 4}]

我一直没能找到这个问题的标准算法对于这一点,甚至一个名字,所以我呼吁缺乏别的它“最大的子集”。 下面是一个为O(n ^ 2)算法(在Python为了具体),假设is_subset_funcO(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 ,它运行在固定的时间。

Answer 1:

假设你标记所有的输入集。

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的最大集合,并更新IS的元素。

让我们通过我们的例子中工作。 当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 ,你必须寻找pa ,线性时间的操作。 在另一方面,一个逆映射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


Answer 2:

这个问题已经研究了文学。 鉴于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)。



Answer 3:

把我的头顶部有一个O(d * N *日志(N)),其中d是唯一的号码的数量。

递归函数“帮手”的工作原理如下:@arguments是组和域(套唯一编号的个数):基本情况:

  1. 如果域是空的,回报
  2. 如果集合为空,或者内置有长度等于1,返回

迭代的情况下:

  1. 拆除将所有空套
  2. 挑在域中的元素d
  3. 从域中删除d
  4. 单独组的成基于该组是否包含d两组(集1&SET2)
  5. 从各组在集删除d
  6. 设置结果=工会(辅助(集1,域),帮助(集2,域))
  7. 对于每一组中设置1加d回
  8. 返回结果

需要注意的是运行时间取决于所使用的设置执行。 如果一个双向链表被用来存储设置,则:

步骤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());
    }


}

*新的一年是破坏性



Answer 4:

前处理的假设:

  • 输入组由降序排序长度
  • 每个组由值排序升序
  • 有访问总长度和为每个组

    方法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);
     }
    }
    

    这将采取一套套,并通过每一个迭代。 随着每一个,它会通过每一组中集再次重复。 作为嵌套迭代发生,它会如果外集是相同的嵌套组(从内迭代)(如果是这样,不进行检查)比较,它也将比较,如果外组具有总更大比组嵌套(如果总越大,则外集不能是一个适当子集),它然后将比较,如果外集具有比项嵌套集合的量较小。

    一旦这些检查完成它开始与外集的第一个项目,并将其与嵌套组的第一项进行比较。 如果它们不相等,它会检查嵌套组的下一个项目。 如果他们是平等的,那么它增加了一个计数器,然后将比较外组与之在内部的一组离开的地方的下一个项目。

    如果达到一个点,匹配比较的量等于在所述外集的项目数量,然后所述外集已被发现是内组的适当子集。 它被标记被排除在外,而比较被停止。



  • 文章来源: Efficient algorithm for finding all maximal subsets
    标签: algorithm set