欧几里得算法的时间复杂度

时间:2010-10-20 16:59:07

标签: algorithm big-o time-complexity iteration

我很难确定Euclid最大公分母算法的时间复杂度。伪代码中的这种算法是:

function gcd(a, b)
    while b ≠ 0
       t := b
       b := a mod b
       a := t
    return a

似乎依赖于 a b 。我的想法是时间复杂度是O(a%b)。那是对的吗?有没有更好的方法来写这个?

11 个答案:

答案 0 :(得分:63)

分析Euclid算法的时间复杂度的一个技巧是遵循两次迭代中发生的事情:

a', b' := a % b, b % (a % b)

现在a和b都会减少,而不是只减少一个,这使得分析更容易。你可以把它分成几个案例:

  • 微小的A:2a <= b
  • Tiny B:2b <= a
  • 小A:2a > b,但a < b
  • 小B:2b > ab < a
  • 等于:a == b

现在我们将展示每一个案例将总a+b减少至少四分之一:

  • 微小A:b % (a % b) < a2a <= b,因此b减少了至少一半,因此a+b减少了至少25%
  • 微小B:a % b < b2b <= a,因此a减少了至少一半,因此a+b减少了至少25%
  • 小A:b将变为b-a,小于b/2a+b至少减少25%
  • 小B:a将变为a-b,小于a/2a+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倍。 Something like n^2 lg(n) 2^O(log* n)。相反,使用binary gcd可以避免多对数因子。

答案 1 :(得分:24)

分析算法的合适方法是确定最坏情况。 Euclidean GCD的最坏情况发生在涉及Fibonacci对时。 void EGCD(fib[i], fib[i - 1]),其中i&gt; 0

例如,让我们选择股息为55,除数为34的情况(回想一下我们仍在处理斐波那契数字)。

enter image description here

正如您可能注意到的,此操作花费了8次迭代(或递归调用)。

让我们尝试更大的斐波纳契数,即121393和75025.我们也可以注意到它需要24次迭代(或递归调用)。

enter image description here

您还可以注意到每次迭代都会产生斐波纳契数。这就是我们有这么多业务的原因。我们不能仅用Fibonacci数字获得类似的结果。

因此,时间复杂度将由小Oh(上限)表示,这次。下限是直观的Omega(1):例如500除以2的情况。

让我们解决递归关系:

enter image description here

我们可以说欧几里德GCD可以使log(xy)操作最多

答案 2 :(得分:17)

wikipedia article上可以看到这一点。

它甚至可以为价值对提供一个很好的复杂图。

不是O(a%b)

众所周知(参见文章),它将永远不会超过较小数字中数字位数的五倍。因此,最大步数随着数字(ln b)的增长而增长。每个步骤的成本也随着数字的增加而增长,因此复杂性受O(ln^2 b)的约束,其中b是较小的数字。这是一个上限,实际时间通常较少。

答案 3 :(得分:11)

请参阅here

特别是这部分:

  

Lamé表明,对于两个小于n的数字,达到最大公约数所需的步数是

     

alt text

所以O(log min(a, b))是一个很好的上限。

答案 4 :(得分:8)

这是对Euclid算法的运行时复杂性的直观理解。正式证明包含在各种文本中,例如算法导论和TAOCP第2卷。

首先想想如果我们试图取两个Fibonacci数F(k + 1)和F(k)的gcd。你可能很快就会发现Euclid的算法迭代到F(k)和F(k-1)。也就是说,每次迭代我们都会在Fibonacci系列中向下移动一个数字。由于Fibonacci数是O(Phi ^ k),其中Phi是黄金比率,我们可以看到GCD的运行时间是O(log n),其中n = max(a,b)并且log具有Phi的基数。接下来,我们可以证明这是最糟糕的情况,通过观察Fibonacci数始终产生对,其中剩余部分在每次迭代中保持足够大,并且在你到达系列的开始之前永远不会变为零。

我们可以使O(log n)n = max(a,b)更加紧密。假设b> = a,我们可以在O(log b)处写入绑定。首先,观察GCD(ka,kb)= GCD(a,b)。由于k的最大值是gcd(a,c),我们可以在运行时用b / gcd(a,b)替换b,从而导致更严格的O界限(log b / gcd(a,b))。

答案 5 :(得分:4)

欧几里德算法的最坏情况是剩余部分在每一步都是最大的,即。连续两个Fibonacci序列。

当n和m是a和b的位数时,假设n> = m,算法使用O(m)除法。

请注意,复杂性始终以输入的尺寸表示,在本例中为数字位数。

答案 6 :(得分:2)

当n和m都是连续的Fibonacci数时,会出现最坏的情况。

gcd(Fn,Fn-1)= gcd(Fn-1,Fn-2)=⋯= gcd(F1,F0)= 1且第n个斐波纳契数是1.618 ^ n,其中1.618是黄金比率。< / p>

因此,要找到gcd(n,m),递归调用的数量将为Θ(logn)。

答案 7 :(得分:1)

然而,对于迭代算法,我们有:

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;
}

使用Fibonacci对,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;
}

是的,对于Fibonacci Pairs,n = a % nn = a - n,这是完全相同的。

我们也知道,在同一问题的早期回复中,存在一个普遍的减少因素:factor = m / (n % m)

因此,为了以定义的形式塑造欧几里德GCD的迭代版本,我们可能会将其描述为“模拟器”:

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);
}

基于Jauhar Ali博士的work(最后一张幻灯片),上面的循环是对数的。

enter image description here

是的,小哦,因为模拟器告诉迭代次数最多。在欧几里德GCD上探测时,非斐波纳契对的迭代次数比斐波那契少。

答案 8 :(得分:0)

Gabriel Lame的定理按log(1 / sqrt(5)*(a + 1/2)) - 2限制步数,其中log的基数为(1 + sqrt(5))/ 2。这是针对算法的最坏情况场景,当输入是连续的Fibanocci数时就会发生。

稍微宽松的界限是:log a,其中日志的基数是(sqrt(2))由Koblitz暗示。

出于加密目的,我们通常会考虑算法的按位复杂性,同时考虑到位大小约为k = loga。

这是对Euclid算法的按位复杂性的详细分析:

虽然在大多数参考文献中,欧几里德算法的按位复杂度由O(loga)^ 3给出,但存在更严格的界限,即O(loga)^ 2.

考虑; r0 = a,r1 = b,r0 = q1.r1 + r2。 。 。 ,ri-1 = qi.ri + ri + 1 ,. 。 。 ,rm-2 = qm-1.rm-1 + rm rm-1 = qm.rm

观察:a = r0> = b = r1> r2> r3 ...&gt; rm-1> rm> 0 ..........(1)

和rm是a和b的最大公约数。

Koblitz的书(理论与密码学课程)中的一篇论文可以证明:ri + 1&lt;(ri-1)/ 2 ............... ..(2)

再次在Koblitz中,将k位正整数除以1位正整数所需的位操作数(假设k> = 1)如下:(k-1 + 1).l ... .....................(3)

通过(1)和(2),除数的数量为O(loga),因此(3)总复杂度为O(loga)^ 3.

现在,这可以通过Koblitz中的一句话减少到O(loga)^ 2。

考虑ki = logri +1

由(1)和(2)得到:ki + 1&lt; = ki,i = 0,1,...,m-2,m-1和ki + 2 <=(ki)-1 I = 0,1,...,M-2

和(3)m除数的总成本受以下因素限制:SUM [(ki-1) - ((ki)-1))] * ki,i = 0,1,2,..,米

重新排列:SUM [(ki-1) - ((ki)-1))] * ki <= 4 * k0 ^ 2

因此欧几里德算法的按位复杂度为O(loga)^ 2。

答案 9 :(得分:0)

这是 Mark Allen Weiss (第二版,2.4.4)在 C中的数据结构和算法分析 中的分析。 :

  

Euclid的算法通过连续计算余数直到达到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;
}

这是我们要使用的 THEOREM

  

如果 M> N,则然后 M mod N

证明:

  

有两种情况。如果N <= M / 2,则由于余数较小   比N的定理在这种情况下成立。另一种情况是N> M / 2。   但随后N进入M一次,余数为M-N

因此,我们可以进行以下推断:

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)

     

请注意,假设M> = N,该算法将计算Gcd(M,N)(如果N> M,则循环的第一次迭代会交换它们。)

答案 10 :(得分:0)