确定列表是否是另一个列表的子集的有效方法是什么?
示例:
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
我主要寻找有效的算法,而不是太关心列表的存储方式。它可以存储在数组,链接列表或其他数据结构中。
由于
编辑:列表已排序
答案 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-Moore或 Horspool 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