我很难确定欧几里得算法的时间复杂度。这个伪代码如下:
function gcd(a, b)
while b ≠ 0
t := b
b := a mod b
a := t
return a
看起来这取决于a和b。我认为时间复杂度为O(a%b)。这正确吗?有更好的写法吗?
我很难确定欧几里得算法的时间复杂度。这个伪代码如下:
function gcd(a, b)
while b ≠ 0
t := b
b := a mod b
a := t
return a
看起来这取决于a和b。我认为时间复杂度为O(a%b)。这正确吗?有更好的写法吗?
分析欧几里得算法时间复杂度的一个技巧是观察两次迭代之间发生了什么:
a', b' := a % b, b % (a % b)
现在a和b都会减少,而不是只有其中一个,这使得分析更容易。您可以将其分为以下情况:
2a <= b
2b <= a
2a > b
但 a < b
2b > a
但 b < a
a == b
现在我们将展示每种情况如何使总和a+b
减少至少四分之一:
2a <= b
且b % (a % b) < a
,因此b
至少减少一半,所以a+b
至少减少25%
2b <= a
且a % b < b
,因此a
至少减少一半,所以a+b
至少减少25%
b
将变为b-a
,小于b/2
,使a+b
至少减少25%
。a
将变为a-b
,小于a/2
,使a+b
至少减少25%
。a+b
降至0
,显然会将a+b
至少减少25%
。因此,通过案例分析,每个双步都会将a+b
至少减少25%
。在a+b
强制降至1
之前,最多可以发生这种情况的次数是有限的。直到我们达到0的总步数(S
)必须满足(4/3)^S <= A+B
。现在就开始计算:
(4/3)^S <= A+B
S <= lg[4/3](A+B)
S is O(lg[4/3](A+B))
S is O(lg(A+B))
S is O(lg(A*B)) //because A*B asymptotically greater than A+B
S is O(lg(A)+lg(B))
//Input size N is lg(A) + lg(B)
S is O(N)
因此,迭代次数与输入数字的位数成线性关系。对于适合放入CPU寄存器中的数字,可以合理地将迭代模拟为花费恒定时间,并假装gcd的总运行时间是线性的。
当然,如果您处理的是大整数,则必须考虑每次迭代中的模数运算不具有恒定的成本。粗略地说,总渐进运行时间将是n^2乘以一个多对数因子。类似于n^2 lg(n) 2^O(log* n)
之类的东西。可以通过使用二进制GCD算法来避免多对数因子。
x % y
的值不能大于 x
,必须小于 y
。因此,a % b
最大只能是 a
,这强制 b % (a%b)
的值低于不超过 a
的某个值,因此总体上要比 a
小。 - Craig Gidneyvoid EGCD(fib[i], fib[i - 1])
。在维基百科文章上有很好的介绍。
它甚至还提供了值对复杂性的漂亮图表。
它不是O(a%b)
。
众所周知(请参见文章),它所需的步骤数永远不会超过较小数字的位数的五倍。因此,步骤的最大数量随着数字位数的增加而增长(ln b)
。每个步骤的成本也随着数字位数的增加而增长,因此其复杂度受到O(ln^2 b)
的限制,其中b是较小的数字。这是一个上限,实际时间通常更短。
n = ln b
。大数取模的常规复杂度是多少?它是 O(log n log^2 log n) 吗? - JoshD以下是《C语言数据结构与算法分析》(第二版,2.4.4)一书中的分析:
欧几里得算法通过不断计算余数直到达到0来工作,最后一个非零余数就是答案。
以下是代码:
unsigned int Gcd(unsigned int M, unsigned int N)
{
unsigned int Rem;
while (N > 0) {
Rem = M % N;
M = N;
N = Rem;
}
Return M;
}
这里有一个我们将要使用的定理:
如果 M > N,那么 M mod N < M/2。
证明:
有两种情况。如果N <= M/2,则由于余数小于N,此情况下定理成立。另一种情况是N > M/2。但是,N除M有余数 M - N < M/2,证明了该定理。
因此,我们可以得出以下推论:
Variables M N Rem
initial M N M%N
1 iteration N M%N N%(M%N)
2 iterations M%N N%(M%N) (M%N)%(N%(M%N)) < (M%N)/2
2logN = O(logN)
。当n和m都是连续的斐波那契数时,最坏情况将会出现。
gcd(Fn,Fn−1)=gcd(Fn−1,Fn−2)=⋯=gcd(F1,F0)=1,第n个斐波那契数是1.618^n,其中1.618是黄金比例。
因此,要找到gcd(n,m),递归调用的次数将为Θ(logn)。
欧几里得算法的最坏情况是每步余数都取最大值,即斐波那契数列的两个连续项。
当a和b的位数分别为n和m时,假设n >= m,则该算法使用O(m)次除法。
请注意,复杂度总是以输入的“大小”为单位给出,本例中为数字的位数。
int iterativeEGCD(long long n, long long m) {
long long a;
int numberOfIterations = 0;
while ( n != 0 ) {
a = m;
m = n;
n = a % n;
numberOfIterations ++;
}
printf("\nIterative GCD iterated %d times.", numberOfIterations);
return m;
}
使用斐波那契对,iterativeEGCD()
和iterativeEGCDForWorstCase()
之间没有区别,其中后者如下所示:
int iterativeEGCDForWorstCase(long long n, long long m) {
long long a;
int numberOfIterations = 0;
while ( n != 0 ) {
a = m;
m = n;
n = a - n;
numberOfIterations ++;
}
printf("\nIterative GCD iterated %d times.", numberOfIterations);
return m;
}
是的,使用斐波那契数对,n = a % n
和 n = a - n
是完全相同的。
我们还知道,在先前回答同一问题时,有一个主导的递减因子:factor = m / (n % m)
。
因此,为了将欧几里得算法的迭代版本形成一个定义良好的形式,我们可以将其描绘成一个像这样的“模拟器”:
void iterativeGCDSimulator(long long x, long long y) {
long long i;
double factor = x / (double)(x % y);
int numberOfIterations = 0;
for ( i = x * y ; i >= 1 ; i = i / factor) {
numberOfIterations ++;
}
printf("\nIterative GCD Simulator iterated %d times.", numberOfIterations);
}
是的,小Oh因为模拟器告诉了迭代次数最多。当在欧几里得GCD上探测非斐波那契对时,所需的迭代次数比斐波那契对要少。
a%b
不成比例。最坏情况是当a
和b
是相邻的斐波那契数时。 - Jerry Coffin