我正在编写一个程序,我必须测试一组唯一整数A
是否属于另一组唯一数字B
。但是,这个操作可能每秒进行几百次,所以我正在寻找一种有效的算法来实现它。
例如,如果A = [1 2 3]
和B = [1 2 3 4]
,则为真,但如果B = [1 2 4 5 6]
,则为假。
我不确定排序和比较的效率如何,所以我想知道是否有更高效的算法。
我提出的一个想法是给每个数字n
提供相应的n
'素数:即1 = 2,2 = 3,3 = 5,4 = 7等。然后我可以计算A
的产品,如果该产品是B
的类似产品的一个因素,我们可以说A
是类似B
的子集确定无疑。例如,如果A = [1 2 3]
,B = [1 2 3 4]
素数为[2 3 5]和[2 3 5 7]且产品2 * 3 * 5 = 30且2 * 3 * 5 * 7 = 210 。由于210%30 = 0,A
是B
的子集。我期待最大的整数最多为几百万,所以我认为这是可行的。
有更高效的算法吗?
答案 0 :(得分:2)
渐近最快的方法是将每个集合放在哈希表中并查询每个元素,这是O(N)
时间。你不能做得更好(因为它需要花费很多时间来阅读数据)。
大多数设置数据结构已经支持预期和/或摊销的O(1)查询时间。有些语言甚至支持此操作。例如在python中,你可以做到
A < B
当然,根据“重复此操作”的含义,图片会发生剧烈变化。如果您能够在将数据添加到集合中时对数据进行预先计算(这可能是您有能力的话),这将允许您将最小O(N)
时间包含在其他操作中,例如构建集合。但是,如果不了解更多,我们就无法提出建议。
假设您完全控制了设置数据结构,那么保留正在运行的产品的方法(无论何时添加元素,进行单O(1)
次乘法)都是一个非常好的想法,如果存在可分性测试比O(N)
快...实际上你的解决方案非常聪明,因为我们可以只进行一次ALU划分并希望我们处于浮动容差范围内。请注意,从20x
开始,我认为这只会让你的加速因子大概为21! > 2^64
。使用同余 - 模 - 整数可能有一些技巧,但我想不出任何。我有一个轻微的预感但是没有比O(#primes)
更快的可分性测试,尽管我想被证明是错的!
如果你在重复上反复这样做,你可能会受益于缓存,具体取决于你在做什么;给每一组提供一个唯一的ID(虽然这会使更新变得困难,但你可能会讽刺地希望做一些与制作指纹的方案完全相同的东西,但mod max_int_size与检测碰撞)。要管理内存,您可以将非常昂贵的集合比较(例如,检查巨型集是否属于其自身)固定到缓存中,否则如果遇到内存问题则使用最新策略。关于这一点的好处是它与逐元素拒绝测试协同作用。也就是说,如果它们没有很多重叠元素,你将快速抛出集合,但如果它们有许多重叠元素,则计算将花费很长时间,如果重复这些计算,缓存可能会派上用场。
答案 1 :(得分:1)
让A和B成为两组,并且你想要检查A是否是B的一个子集。我想到的第一个想法是对两个集合进行排序,然后简单地检查A中是否包含A的每个元素,如下:
令n_A和n_B分别为A和B的基数。设i_A = 1,i_B = 1.然后,下面的算法(即O(n_A + n_B))将解决问题:
// A and B assumed to be sorted
i_A = 1;
i_B = 1;
n_A = size(A);
n_B = size(B);
while (i_A <= n_A) {
while (A[i_A] > B[i_B]) {
i_B++;
if (i_B > n_B) return false;
}
if (A[i_A] != B[i_B}) return false;
i_A++;
}
return true;
同样的事情,但是以更具功能性,递归的方式(有些人会发现以前更容易理解,其他人可能会发现这个更容易理解):
// A and B assumed to be sorted
function subset(A, B)
n_A = size(A)
n_B = size(B)
function subset0(i_A, i_B)
if (i_A > n_A) true
else if (i_B > n_B) false
else
if (A[i_A] <= B[i_B]) return (A[i_A] == B[i_B]) && subset0(i_A + 1, i_B + 1);
else return subset0(i_A, i_B + 1);
subset0(1, 1)
在最后一个例子中,请注意subset0是尾递归的,因为如果(A[i_A] == B[i_B])
为假,则不会有递归调用,否则,如果(A[i_A] == B[i_B])
为真,则不需要保留信息,因为true && subset0(...)
的结果与subset0(...)
完全相同。因此,任何智能编译器都能够将其转换为循环,避免堆栈溢出或由函数调用引起的任何性能命中。
这肯定会有效,但是如果您拥有并提供有关您的集合的更多信息,例如集合中值的概率分布,我们可能能够在一般情况下对其进行大量优化,如果您不知何故预期答案是有偏见的(即,它往往是真的,或者更经常是假的),等等。
另外,您是否已编写任何代码来实际衡量其性能?或者您是否正在尝试进行预优化?
您应该从编写最简单,最直接的解决方案开始,并衡量其性能。如果它还不能令人满意,那么你应该开始尝试优化它。
答案 2 :(得分:1)
我将提出一个O(m + n)时间测试算法。但首先,关于问题陈述的两个注释:
注1 - 您的编辑说套装尺寸可能是几千,而数字可能会达到一百或两百。 在下面,让m,n表示集合A,B的大小,让R表示集合中允许的最大数字的大小。
注2 - 您提出的乘法方法效率很低。虽然它使用O(m + n)倍,但它不是O(m + n)方法,因为产品长度比O(m)和O(n)差,所以它需要大于O(m ^ 2) + n ^ 2)时间,这比基于排序的方法所需的O(m ln(m)+ n ln(n))时间更差,而后者又比O(m + n)时间差。以下方法。
对于下面的演示,我认为集合A,B可以在测试之间完全改变,你说这些测试每秒可以发生几百次。如果有部分更改,并且您知道哪个p元素在A中从一个测试更改为下一个测试,哪个q在B中更改,则可以修改该方法以在每个测试的O(p + q)时间内运行。
步骤0.(仅在开始时执行一次。)根据需要清除包含R位或字节的数组F.
步骤1.(每个测试代码的初始步骤。)对于i从0到n-1,设置F [B [i]],其中B [i]表示集合B的第i个元素。是O(n)。
步骤2.对于i从0到m-1,{test F [A [i]]。如果清楚,则报告A不是B的子集,并转到步骤4;否则继续}。这是O(m)。
步骤3.报告A是B的子集。
步骤4.(清除使用的位)对于i从0到n-1,清除F [B [i]]。这是O(n)。
初始步骤(清除数组F)为O(R),但步骤1-4为O(m + n)时间。
答案 3 :(得分:1)
考虑到整数大小的限制,如果B集的集合很小并且很少变化,则考虑将B集表示为位集(由整数集成员索引的位数组)。这不需要排序,并且每个元素的测试非常快。
如果A成员被排序并且倾向于聚集在一起,那么通过一次测试位集中一个单词中的所有元素来获得另一个加速。