如何确定列表是否是另一个列表的子集?

时间:2009-08-26 15:53:06

标签: php c++ c algorithm scala

确定列表是否是另一个列表的子集的有效方法是什么?

示例:

is_subset(List(1,2,3,4),List(2,3))    //Returns true
is_subset(List(1,2,3,4),List(3,4,5))  //Returns false

我主要寻找有效的算法,而不是太关心列表的存储方式。它可以存储在数组,链接列表或其他数据结构中。

由于

编辑:列表已排序

13 个答案:

答案 0 :(得分:22)

您可以进行一些权衡。让我们假设您有两组元素S和T,它们来自宇宙U.我们想确定S≥T。在其中一个给出的例子中,我们有

S = {1,2,3,4}
T = {3,4,5}
U = {1,2,3,4,5}

1。排序列表(或平衡搜索树)
大多数海报建议的方法。如果你已经有了排序列表,或者不关心创建它们所需的时间长度(比如,你不经常这样做),那么这个算法基本上是线性时间和空间。这通常是最好的选择。

(为了公平对待其他选择,时间和空间界限实际上应该在适当的位置包含“Log | U |”因子,但这通常不是重复的)

数据结构:S和T中每一个的排序列表。或者可以在恒定空间中迭代的平衡搜索树(例如AVL树,红黑树,B +树)。

算法:对于T中的每个元素,按顺序,对该元素进行线性搜索。记住每次搜索停止的地方,然后在那里开始下一次搜索。如果每次搜索都成功,那么S≥T。

时间复杂度:关于 O( | S | Log | S | + | T | Log | T | 创建已排序列表, O( max(| S |,| T |)进行比较。

空间复杂度:关于 O( | S | + | T |

示例(C ++)

#include <set>
#include <algorithm>

std::set<int> create_S()
{
    std::set<int> S;
    // note: std::set will put these in order internally
    S.insert(3);
    S.insert(2);
    S.insert(4);
    S.insert(1);
    return S;
}

std::set<int> create_T()
{
    std::set<int> T;
    // note std::set will put these in order internally
    T.insert(4);
    T.insert(3);
    T.insert(5);
    return T;
}

int main()
{
    std::set<int> S=create_S();
    std::set<int> T=create_T();
    return std::includes(S.begin(),S.end(), T.begin(), T.end());
}

2。哈希表
使用哈希表可以获得比排序列表更好的平均时间复杂度。大型集合的改进行为是以小集合的性能普遍较差为代价的。

与排序列表一样,我忽略了宇宙大小所带来的复杂性。

数据结构:S的哈希表,任何可以快速迭代的东西。

算法:将S的每个元素插入其哈希表中。然后,对于T中的每个元素,检查它是否在哈希表中。

时间复杂度 O( | S | + | T | 进行设置, O( | T | 进行比较。

空间复杂性 O( | S | + | T |

示例(C ++)

#include <tr1/unordered_set>

std::tr1::unordered_set<int> create_S()
{
    std::tr1::unordered_set<int> S;
    S.insert(3);
    S.insert(2);
    S.insert(4);
    S.insert(1);
    return S;
}

std::tr1::unordered_set<int> create_T()
{
    std::tr1::unordered_set<int> T;
    T.insert(4);
    T.insert(3);
    T.insert(5);
    return T;
}

bool includes(const std::tr1::unordered_set<int>& S, 
              const std::tr1::unordered_set<int>& T)
{
    for (std::tr1::unordered_set<int>::const_iterator iter=T.begin();
         iter!=T.end();
         ++iter)
    {
        if (S.find(*iter)==S.end())
        {
            return false;
        }
    }
    return true;
}

int main()
{
    std::tr1::unordered_set<int> S=create_S();
    std::tr1::unordered_set<int> T=create_T();
    return includes(S,T);
}

3。位集
如果你的宇宙特别小(假设你只能有元素0-32),那么bitset是一个合理的解决方案。运行时间(再次,假设您不关心设置时间)基本上是不变的。如果您关心设置,它仍然比创建排序列表更快。

不幸的是,即使是中等大小的宇宙,bitsets也会很快变得笨拙。

数据结构:S和T中每一个的位向量(通常是一个机器整数)。在给定的例子中,我们可能编码S = 11110和T = 00111。

算法:通过计算S中每个位的按位'和'与T中的相应位来计算交点。如果结果等于T,则S≥T。

时间复杂度 O( | U | + | S | + | T | 进行设置, O(< / b> | U | 进行比较。

空间复杂性 O( | U |

示例:(C ++)

#include <bitset>

// bitset universe always starts at 0, so create size 6 bitsets for demonstration.
// U={0,1,2,3,4,5}

std::bitset<6> create_S()
{
    std::bitset<6> S;
    // Note: bitsets don't care about order
    S.set(3);
    S.set(2);
    S.set(4);
    S.set(1);
    return S;
}

std::bitset<6> create_T()
{
    std::bitset<6> T;
    // Note: bitsets don't care about order
    T.set(4);
    T.set(3);
    T.set(5);
    return T;
}

int main()
{
    std::bitset<6> S=create_S();
    std::bitset<6> T=create_T();

    return S & T == T;
}

4。 Bloom filters
比特集的所有速度优势,而没有比特集所具有的宇宙大小的令人讨厌的限制。只有一个缺点:他们有时(通常,如果你不小心)给出错误的答案:如果算法说“不”,那么你肯定没有包含。如果算法说“是”,您可能会也可能不会。通过选择较大的滤波器大小和良好的散列函数可以获得更高的精度。

鉴于他们可以而且会给出错误的答案,Bloom过滤器可能听起来像一个可怕的想法。但是,它们有明确的用途。通常,人们会使用Bloom过滤器快速执行许多包含检查,然后使用较慢的确定性方法来保证需要时的正确性。链接的维基百科文章提到了一些使用Bloom过滤器的应用程序。

数据结构Bloom filter是一个奇特的位集。必须事先选择过滤器大小和散列函数。

算法(草图):将bitset初始化为0.要将一个元素添加到bloom过滤器,请使用每个哈希函数对其进行哈希处理,并在bitset中设置相应的位。确定包含就像对位集一样。

时间复杂度 O( 过滤器尺寸

空间复杂度 O( 过滤器尺寸

正确性概率:如果答案为“S不包含T”,则始终更正。如果它回答“S包括T”,则类似于0.6185 ^(| S | x | T | /(过滤器大小)))。特别是,必须根据| S |的乘积选择滤波器大小和| T |给出合理的准确概率。

答案 1 :(得分:15)

对于C ++,最好的方法是使用std::includes算法:

#include <algorithm>

std::list<int> l1, l2;
...
// Test whether l2 is a subset of l1
bool is_subset = std::includes(l1.begin(), l1.end(), l2.begin(), l2.end());

这要求对两个列表进行排序,如问题中所述。复杂性是线性的。

答案 2 :(得分:10)

只是想提一下Python有一个方法:

return set(list2).issubset(list1)

或者:

return set(list2) <= set(list1)

答案 3 :(得分:7)

如果两个列表都是有序的,一个简单的解决方案是同时遍历两个列表(两个列表中都有两个凹凸指针),并验证第二个列表中的所有元素是否出现在第一个列表中(直到所有找到元素,或直到你在第一个列表中找到一个更大的数字。)

C ++中的伪代码看起来像这样:

List l1, l2;
iterator i1 = l1.start();
iterator i2 = l2.start();
while(i1 != l1.end() && i2 != l2.end()) {
  if (*i1 == *i2) {
    i1++;
    i2++;
  } else if (*i1 > *i2) {
    return false;
  } else {
    i1++;
  }
}
return true;

(显然不会按原样运作,但这个想法应该很清楚)。

如果未对列表进行排序,则可以使用哈希表 - 在第一个列表中插入所有元素,然后检查第二个列表中的所有元素是否都显示在哈希表中。

这些是算法答案。在不同的语言中,有默认的内置方法来检查它。

答案 4 :(得分:3)

如果您担心订购或连续性,可能需要使用Boyer-MooreHorspool algorithm

问题是,你想将[2,1]视为[1,2,3]的子集吗?你想[1,3]被认为是[1,2,3]的一个子集吗?如果对这两个问题的答案都是肯定的,您可以考虑上面链接的算法之一。否则,您可能需要考虑哈希集。

答案 5 :(得分:3)

Scala,假设你的意思是子集的子序列:

def is_subset[A,B](l1: List[A], l2: List[B]): Boolean =
  (l1 indexOfSeq l2) > 0

无论如何,子序列只是一个子串问题。最佳算法包括Knuth-Morris-Pratt和Boyer-Moore,以及一些更复杂的算法。

如果你真的是指子集,那么你说的是集合而不是列表,你可以在Scala中使用subsetOf方法。算法将取决于集合的存储方式。以下算法适用于列表存储,这是非常不理想的。

def is_subset[A,B](l1: List[A], l2: List[B]): Boolean = (l1, l2) match {
  case (_, Nil) => true
  case (Nil, _) => false
  case (h1 :: t1, h2 :: t2) if h1 == h2 => is_subset(t1, t2)
  case (_ :: tail, list) => is_subset(tail, list)
}

答案 6 :(得分:3)

对于scala trunk中的indexOfSeq,我实现了KMP,您可以检查:SequenceTemplate

答案 7 :(得分:1)

如果您可以将数据存储在哈希集中,则只需检查list1是否包含list2中每个x的x。它将接近list2大小的O(n)。 (当然,您也可以对其他数据结构执行相同操作,但这会导致不同的运行时)。

答案 8 :(得分:1)

这在很大程度上取决于语言/工具包,以及列表的大小和存储。

如果对列表进行排序,则单个循环可以确定这一点。您可以开始走向较大的列表,试图找到较小列表的第一个元素(如果您将值传递给它则中断),然后继续前进到下一个元素,并从当前位置继续。这很快,因为它是一个循环/一次通过算法。

对于未排序的列表,从第一个列表的元素构建某种形式的哈希表通常最快,然后从哈希中搜索第二个列表中的每个元素。这是许多.NET LINQ扩展在内部用于列表中项目搜索的方法,并且可以很好地扩展(尽管它们具有相当大的临时内存要求)。

答案 9 :(得分:1)

func isSubset ( @list, @possibleSubsetList ) {
    if ( size ( @possibleSubsetList ) > size ( @list ) ) {
        return false;
    }
    for ( @list : $a ) {
        if ( $a != @possibleSubsetList[0] ) {
            next;
        } else {
            pop ( @possibleSubsetList );
        }
    }
    if ( size ( @possibleSubsetList ) == 0 ) {
        return true;
    } else {
        return false;
    }
}

O(n)中提琴。当然,isSubset((1,2,3,4,5),(2,4))将返回true

答案 10 :(得分:0)

您应该看一下STL方法搜索的实现。这就是我认为可以完成的C ++方式。

http://www.sgi.com/tech/stl/search.html

说明

当逐个元素进行比较时,搜索在[first1,last1]范围内查找与[first2,last2]相同的子序列。

答案 11 :(得分:0)

您可以看到问题,以检查列表是否是另一个列表的子集,同样的问题是验证子字符串是否属于字符串。最着名的算法是KMP(Knuth-Morris-Pratt)。查看维基百科的伪代码,或者只使用您喜欢的语言中的一些String.contains方法。 =)

答案 12 :(得分:-1)

高效的算法使用某种状态机,你在内存中保持接受状态(在python中):

def is_subset(l1, l2):
    matches = []
    for e in l1:
        # increment
        to_check = [0] + [i+1 for i in matches]
        matches = [] # nothing matches
        for i in to_check:
            if l2[i] = e:
                if i == len(l2)-1:
                    return True
                matches.append(i)
    return False

编辑:当然如果列表已排序,您不需要该算法,只需执行:

def is_subset(l1, l2):
    index = 0
    for e in l1:
        if e > l2[index]:
            return False
        elif e == l2[index]:
            index += 1
        else:
            index == 0
        if index == len(l2):
            return True
    return False