29

一意のセット (ビット マスクとして表される) のコレクションがあり、別の要素の適切なサブセットであるすべての要素を削除したいと考えています。例えば:

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

この問題の標準アルゴリズムを見つけることも、この問題の名前を見つけることもできなかったので、他に何もないため、「最大サブセット」と呼んでいます。is_subset_funcO(1)仮定した O(n^2) アルゴリズム (具体的には Python) を次に示します。

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_funcelement & existing == element

4

4 に答える 4

15

すべての入力セットにラベルを付けるとします。

A={1, 2, 3}, B={1, 2}, C={2, 3}, D={2, 4}, E={}

ここで、ユニバース内の要素ごとに 1 つの中間セットを作成し、それが表示されるセットのラベルを含めます。

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.

このメソッドのコストは、セットの実装によって異なります。ビットマップを考えてみましょう(あなたが示唆したように)。最大サイズ m および |U| の入力セットが n 個あるとします。宇宙のアイテム。次に、中間集合の構築により |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 は、このアルゴリズムのオンライン バージョンがあるかどうかを尋ねました。つまり、入力セットが 1 つずつ到着するにつれて、最大セットのセットを段階的に維持できるものです。答えはイエスのようです。中間セットは、新しいセットが既に見たセットのサブセットであるかどうかをすぐに教えてくれます。しかし、それがスーパーセットかどうかをすばやく判断するにはどうすればよいでしょうか? もしそうなら、既存の極大集合はどれか? この場合、これらの極大集合はもはや極大ではなく、新しい集合に置き換えなければなりません。

重要なのは、 iffAのスーパーセットが のサブセットであることに注意することです(目盛りはセットの補数を示します)。BA'B'

このインスピレーションに従って、中間セットを以前と同じように維持します。新しい入力セットSが到着したら、上記と同じテストを行います:I(e)を入力要素の中間セットとしますe。それから、このテストは

For X = \intersect_{e \in S} . I(e), |X| > 0

S(この場合、はまだ に含まれていないため、上記の 1 ではなく 0 より大きいです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}

補数の計算は、アルゴリズムがオンラインであるため、トリッキーであると同時に便利です。入力補数のユニバースには、これまでに見た入力要素のみを含める必要があります。中間セットのユニバースは、現在の最大コレクション内のセットのタグのみで構成されます。多くの入力ストリームでは、このセットのサイズは安定するか、時間とともに減少します。

これがお役に立てば幸いです。

概要

ここで働く一般原則は、アルゴリズム設計でしばしば切り抜かれる強力なアイデアです。逆マップです。特定の属性を持つアイテムを見つけるために線形検索を行っていることに気付いたときはいつでも、属性からアイテムへのマップを作成することを検討してください。多くの場合、このマップの作成は安価であり、検索時間が大幅に短縮されます。代表的な例は、配列が置換された後に 'th 要素が占めるp[i]位置を示す置換マップです。i特定の場所に到達するアイテムを検索する必要がある場合は、線形時間操作であるaを検索する必要があります。一方、計算にそれほど時間がかからないような逆写像(そのため、そのコストは「隠されている」) ですが、papipi[p[i]] == ippi[a]一定の時間で目的の結果を生成します。

投稿者による実施

import collections
import operator
from functools import reduce # only in Python 3

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
于 2013-01-01T04:18:00.840 に答える
3

この問題は、文献で研究されています。{1,...,n} の部分集合である S_1,...,S_k が与えられると、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: サブセットのテストと最大セットの検索のためのアルゴリズム。ソーダ 1992: 386-392.

[2] Daniel M. Yellin、Charanjit S. Jutla: 二次時間未満での極値セットの検索。インフォ。プロセス。レット。48(1): 29-34 (1993)。

于 2016-06-05T21:40:05.997 に答える
2

私の頭の上には O(D*N*log(N)) があり、D は一意の数です。

再帰関数「ヘルパー」は次のように機能します: @arguments はセットとドメイン (セット内の一意の数値の数) です: 基本ケース:

  1. ドメインが空の場合は、戻る
  2. セットが空であるか、セットの長さが 1 の場合は、次を返します。

反復ケース:

  1. セットからすべての空のセットを削除する
  2. ドメイン内の要素 D を選択
  3. D をドメインから削除する
  4. セットに D が含まれているかどうかに基づいて、セットを 2 つのセット (set1 & set2) に分けます。
  5. セット内の各セットから D を削除します
  6. セット結果 = ユニオン ( helper(set1,domain), helper(set2,domain) )
  7. set1 の各セットについて、D を戻す
  8. 結果を返す

ランタイムは、使用される 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());
    }


}

※年末年始はお休みです

于 2012-12-31T22:06:26.893 に答える
1

前処理の仮定:

  • 入力セットは長さの降順でソートされます
  • 各セットは値の昇順でソートされます
  • 各セットの合計と長さへのアクセスがあります

    アプローチ #2 - バケット アプローチを使用する

    同じ仮定。一意性を仮定できますか? (つまり、{1,4,6}、{1,4,6} はありません) それ以外の場合は、おそらくバケットが作成された時点で、distinct を確認する必要があります。

    半疑似

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

    これはセットのセットを取り、それぞれを繰り返します。それぞれで、セット内の各セットを再度繰り返します。ネストされた反復が行われると、外側のセットが (内側の反復からの) ネストされたセットと同じであるかどうかが比較されます (そうである場合、チェックは行われません)。ネストされたセットよりも (合計が大きい場合、外側のセットは適切なサブセットではありません)、外側のセットがネストされたセットよりもアイテムの量が少ないかどうかを比較します。

    これらのチェックが完了すると、外側のセットの最初のアイテムから開始され、ネストされたセットの最初のアイテムと比較されます。それらが等しくない場合、ネストされたセットの次の項目をチェックします。それらが等しい場合は、カウンターに 1 を追加し、外側のセットの次の項目を内側のセットの中断した場所と比較します。

    一致した比較の量が外側のセットのアイテムの数と等しくなるポイントに到達すると、外側のセットは内側のセットの適切なサブセットであることがわかります。除外フラグが立てられ、比較が停止されます。

  • 于 2012-12-31T22:07:32.290 に答える