这个答案提到了算法的要点(称为 DL,因为它使用“除数列表”)并通过名为 amodb.py 的程序提供详细信息。
设 B 为输入数组,包含 N 个正整数。不失一般性,假设B[i] > K 代表所有i,并且B 是升序排列的。 (注意x%B[i] < K if B[i] < K; and where B[i] = K, one can report pairs (B[i], B[j]) for all j>i. 如果B最初没有排序,收取@的成本987654327@排序。)
在算法 DL 和程序 amodb.py 中,A 是一个数组,其中 K 预先从输入数组元素中减去。即A[i] = B[i] - K。请注意,如果a%b == K,那么对于某些j,我们有a = b*j + K 或a-K = b*j。也就是说,a%b == K iff a-K 是 b 的倍数。此外,如果a-K = b*j 和p 是b 的任何因数,那么p 是a-K 的因数。
让从 2 到 97 的素数称为“小因数”。当从某个区间[X,Y]中均匀随机抽取N个数时,N/ln(Y)量级的数将有不小的因数;相似的数字将具有最大的小因数 2;而下降的比例将有依次变大的最大的小因素。例如,平均大约N/97 可以被97 整除,大约N/89-N/(89*97) 可以被89 整除而不是97 等等。一般来说,当B 的成员是随机的时,具有某些最大小因数或没有小因数的成员列表长度小于 O(N/ln(Y))。
给定一个列表 Bd,其中包含可被最大小因子 p 整除的 B 的成员,DL 测试 Bd 的每个元素与列表 Ad 的元素,A 的那些元素可被p。但是给定一个 Bp 的列表 Bp 包含没有小因素的 B 元素,DL 将 Bp 的每个元素与 A 的所有元素进行测试。示例:如果 N=25、p=13、Bd=[18967, 23231] 和 @987654346 @,然后 DL 测试任何12779%18967, 162383%18967, 12779%23231, 162383%23231 是否为零。请注意,通过注意 12779<18967,可以将本示例(以及许多其他示例)中的测试数量减少一半,但 amodb.py 不包含该优化。
DL 为J 不同的因素制作J 不同的列表;在 amodb.py 的一个版本中,J=25 并且因子集是小于 100 的素数。J 的较大值将增加 O(N*J) 初始化除数列表的时间,但会略微减少 O(N*len(Bp)) 时间针对 A 的元素处理列表 Bp。请参见下面的结果。处理其他列表的时间是O((N/logY)*(N/logY)*J),这与先前答案方法的O(n*sqrt(Y)) 复杂性形成鲜明对比。
接下来显示的是两个程序运行的输出。在每个集合中,第一行 Found 来自一个简单的 O(N*N) 测试,第二行来自 DL。 (注意,如果逐步删除太小的 A 值,DL 和 naïve 方法都会运行得更快。)第一个测试的最后一行中的时间比率显示,对于 3.9 的加速比率低得令人失望。 DL vs naïve 方法。对于那次运行,factors 仅包含 25 个小于 100 的素数。对于第二次运行,factors 包含数字 2 到 13 以及最多 100 个素数,加速比更好,约为 4.4。
$ python amodb.py
N: 10000 K: 59685 X: 100000 Y: 1000000
Found 208 matches in 21.854 seconds
Found 208 matches in 5.598 seconds
21.854 / 5.598 = 3.904
$ python amodb.py
N: 10000 K: 97881 X: 100000 Y: 1000000
Found 207 matches in 21.234 seconds
Found 207 matches in 4.851 seconds
21.234 / 4.851 = 4.377
程序 amodb.py:
import random, time
factors = [2,3,4,5,6,7,8,9,10,11,12,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]
X, N = 100000, 10000
Y, K = 10*X, random.randint(X/2,X)
print "N: ", N, " K: ", K, "X: ", X, " Y: ", Y
B = sorted([random.randint(X,Y) for i in range(N)])
NP = len(factors); NP1 = NP+1
A, Az, Bz = [], [[] for i in range(NP1)], [[] for i in range(NP1)]
t0 = time.time()
for b in B:
a, aj, bj = b-K, -1, -1
A.append(a) # Add a to A
for j,p in enumerate(factors):
if a % p == 0:
aj = j
Az[aj].append(a)
if b % p == 0:
bj = j
Bz[bj].append(b)
Bp = Bz.pop() # Get not-factored B-values list into Bp
di = time.time() - t0; t0 = time.time()
c = 0
for a in A:
for b in B:
if a%b == 0:
c += 1
dq = round(time.time() - t0, 3); t0 = time.time()
c=0
for i,Bd in enumerate(Bz):
Ad = Az[i]
for b in Bd:
for ak in Ad:
if ak % b == 0:
c += 1
for b in Bp:
for ak in A:
if ak % b == 0:
c += 1
dr = round(di + time.time() - t0, 3)
print "Found", c, " matches in", dq, "seconds"
print "Found", c, " matches in", dr, "seconds"
print dq, "/", dr, "=", round(dq/dr, 3)