Euclid算法的时间复杂度


97

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

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

它似乎取决于ab。我的想法是时间复杂度为O(a%b)。那是对的吗?有没有更好的方法可以写呢?


14
参见Knuth TAOCP,第2卷-他提供了广泛的报道。只是FWIW,有几个小窍门:与不成比例a%b。最坏的情况是当ab是连续的斐波那契数时。
杰里·科芬

3
@JerryCoffin注意:如果您想以更正式的方式证明最坏的情况确实是斐波那契数,请考虑在终止之前证明第n步必须至少等于gcd乘以数学归纳法得出的第n个斐波那契数。
Mygod

Answers:


73

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

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

现在,a和b都将减少,而不是仅减少一个,这使得分析更加容易。您可以将其分为几种情况:

  • 小小A: 2a <= b
  • 小小B: 2b <= a
  • 小甲:2a > b但是a < b
  • 小B:2b > a但是b < 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/2减少。a+b25%
  • 小B:a将变为a-b,小于至少a/2减少a+b25%
  • 等于:a+b降至0,明显降低a+b25%

因此,通过案例分析,每个双步步数a+b至少减少了25%。在a+b被迫降至之下之前,这种情况最多会发生一次1S直到达到0为止的总步数()必须满足(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来避免多对数因子。


您能解释一下为什么“ b%(a%b)<a”吗?
Michael Heidelberg

3
@MichaelHeidelberg x % y不能大于x且必须小于ya % b最多也是如此a,强迫b % (a%b)低于最多的东西,a因此总的来说小于a
Craig Gidney

@ Cheersandhth.-Alf您认为首选术语稍有不同是“严重错误”吗?当然,我使用CS术语。这是计算机科学的问题。无论如何,我澄清了答案,说“数字位数”。
Craig Gidney

@CraigGidney:感谢您解决此问题。现在,我从纯学者撰写的许多Wikipedia文章中认识到交流问题。考虑一下:谈论位数而不是像我在评论中那样只写O(log(min(a,b))的主要原因是为了使非数学人员更容易理解。这就是数字位数的用途目的是帮助那些有挑战性的人们。当您将这个概念命名为 “大小”,并在其他地方进行定义时,不要在此处谈论“日志”。最后,您会
感到困惑

最后一段不正确。如果将相关的伸缩级数相加,即使使用教科书二次时分算法,时间复杂度也仅为O(n ^ 2)。
EmilJeřábek

27

分析算法的合适方法是确定最坏情况。涉及斐波那契对时,欧几里得GCD最坏的情况发生。 void EGCD(fib[i], fib[i - 1]),其中i> 0。

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

在此处输入图片说明

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

让我们尝试更大的斐波那契数,即121393和75025。在此我们还可以注意到,它花了24次迭代(或递归调用)。

在此处输入图片说明

您还可以注意到,每次迭代都会产生一个斐波那契数。这就是为什么我们要进行如此多的操作。实际上,仅斐波那契数不能获得相似的结果。

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

让我们解决递归关系:

在此处输入图片说明

我们可以说,然后欧几里德GCD可以使日志(XY)操作最多


2
我认为这种分析是错误的,因为基数取决于输入。
HopefulHelpful

您能证明依赖的基础代表问题吗?
Mohamed Ennahdi El Idrissi

1
基数显然是黄金分割率。为什么?因为计算nod(13,8)与nod(8,5)只需要多花一步。对于固定的x(如果y <x),最坏的情况是x = fib(n + 1),y = fib(n)。此处y取决于x,因此我们只能看x。
Stepan'7

17

维基百科的文章对此有很好的了解。

它甚至对值对都有一个很好的复杂度图。

它不是 O(a%b)

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


什么是n代表?
IVlad

@IVlad:位数。我已经澄清了答案,谢谢。
JoshD

对于OP的算法,使用(大整数)除法(而不是减法)实际上更像是O(n ^ 2 log ^ 2n)。
Alexandre C.

@Alexandre C .:请记住n = ln b。大整数的模数的规则复杂度是多少?是O(log n log ^ 2 log n)
JoshD

@JoshD:就像这样,我想我错过了一个log n项,在这种情况下,最终复杂度(对于带除法的算法)是O(n ^ 2 log ^ 2 n log n)。
Alexandre C.

13

这里

特别是这部分:

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

替代文字

O(log min(a, b))一个好的上限也是如此。


3
步数是正确的,但是并不能说明每个步本身的复杂性,步数随位数(ln n)的变化而变化。
JoshD

9

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

首先考虑一下,如果我们尝试取两个斐波那契数F(k + 1)和F(k)的gcd怎么办。您可能会很快观察到Euclid的算法迭代到F(k)和F(k-1)。也就是说,在每次迭代中,我们将斐波那契数列向下移动一个数字。由于斐波那契数为O(Phi ^ k),其中Phi是黄金比率,因此我们可以看到GCD的运行时间为O(log n),其中n = max(a,b),log具有Phi的底数。接下来,我们可以通过观察斐波那契数始终产生对,以确保余数在每次迭代中保持足够大,直到您到达该系列的开始才变为零,从而证明这是最坏的情况。

我们可以使O(log n)的约束更严格,其中n = max(a,b)。假设b> = a,因此我们可以在O(log b)处写bound。首先,观察GCD(ka,kb)= GCD(a,b)。由于k的最大值是gcd(a,c),我们可以在运行时中将b替换为b / gcd(a,b),从而使O的界限更加严格(log b / gcd(a,b))。


您能否提供正式的证明,证明斐波那契号对欧几里得算法产生最坏的影响?
Akash

4

欧几里得算法最坏的情况是,每一步的余数最大。斐波那契数列的两个连续项。

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

注意,复杂度总是根据输入的大小给出,在这种情况下是位数。


4

当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)。


3

下面是书中的分析数据结构与算法分析- C马克·艾伦·韦斯(第二版,2.4.4):

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

这是我们将要使用的一个定理

如果 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)

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


2

加布里埃尔·拉姆(Gabriel Lame)定理用log(1 / sqrt(5)*(a + 1/2))-2限制步数,其中对数的底数是(1 + sqrt(5))/ 2。这是该算法最坏的情况,当输入是连续的Fibanocci数时发生。

更为宽松的限制是:日志a,Koblitz隐含了日志的底数为(sqrt(2))的日志。

为了进行加密,我们通常考虑算法的按位复杂度,同时考虑到位大小大约由k = loga给出。

这是对欧几里得·阿尔戈里斯(Euclid Algorith)的按位复杂度的详细分析:

尽管在大多数参考文献中,欧几里德算法的按位复杂度由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 ...> rm-1> rm> 0 .....(1)

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

通过Koblitz的著作(数论与密码学课程)的证明,可以证明:ri + 1 <(ri-1)/ 2 .................( 2)

同样在Koblitz中,将k位正整数除以l位正整数(假设k> = l)所需的位数为:(k-l + 1).l ...... .............(3)

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

现在可以通过Koblitz中的注释将其减少为O(loga)^ 2。

考虑ki = logri +1

由(1)和(2)得到:ki + 1 <= ki for i = 0,1,...,m-2,m-1和ki + 2 <=(ki)-1 for i = 0 ,1,...,m-2

并由(3)m个总成本的总和为:i = 0,1,2,..,m的SUM [(ki-1)-((ki)-1))] ki

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

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


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

与斐波那契数对,有什么区别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 % 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博士的工作(最后一张幻灯片),上面的循环是对数的。

在此处输入图片说明

是的,太小了,因为模拟器最多可以告诉迭代次数。当在欧几里得GCD上进行探测时,非斐波那契对的迭代次数将少于斐波那契。


由于此研究是使用C语言进行的,因此精度问题可能会产生错误/不精确的值。这就是为什么使用long long来更好地拟合名为factor的浮点变量的原因。使用的编译器是MinGW 2.95。
Mohamed Ennahdi El Idrissi 2014年

1

每一步都有两种情况

b> = a / 2,则a,b = b,a%b将使b最多为其先前值的一半

b <a / 2,则a,b = b,%b最多等于先前值的一半,因为b小于a / 2

因此,在每一步中,算法都会将至少一个数字减少到至少减少一半。

在最多O(log a)+ O(log b)步骤中,这将简化为简单情况。得出O(log n)算法,其中n是a和b的上限。

在这里找到

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.