Stein.J算法求最大公约数的几个实现及分析

首先给出wikipedia上Stein算法的理论依据:

The algorithm reduces the problem of finding the GCD by repeatedly applying these identities:

  1. gcd(0, v) = v, because everything divides zero, and v is the largest number that divides v. Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically defined, but it is convenient to set gcd(0, 0) = 0.
  2. If u and v are both even, then gcd(u, v) = 2·gcd(u/2, v/2), because 2 is a common divisor.
  3. If u is even and v is odd, then gcd(u, v) = gcd(u/2, v), because 2 is not a common divisor. Similarly, if u is odd and v is even, then gcd(u, v) = gcd(u, v/2).
  4. If u and v are both odd, and uv, then gcd(u, v) = gcd((uv)/2, v). If both are odd and u < v, then gcd(u, v) = gcd((vu)/2, u). These are combinations of one step of the simple Euclidean algorithm, which uses subtraction at each step, and an application of step 3 above. The division by 2 results in an integer because the difference of two odd numbers is even.[3]
  5. Repeat steps 2–4 until u = v, or (one more step) until u = 0. In either case, the GCD is 2kv, where k is the number of common factors of 2 found in step 2.

 

所以相比较欧几里德算法,该算法在二进制的计算机上可以更多发挥位操作的威力,而避免了模运算带来的处理器性能损失。wikipedia上说This is particularly critical on embedded platforms that have no direct processor support for division。不过我想,今天的智能手机估计不存在这麽弱的处理器,但如果还是比较低端的工控芯片...but but,谁会在那种设备上运行大数的公约数计算呢。

 

依据Stein算法的理论描述,这里给出三个算法。

第一个函数并没有利用位运算操作符,但是思想还是Stein算法的思想,第二个函数在第一个函数的基础上把运算改成位运算。这两个函数是自己实现的,再来看看第三个函数摘自wikipedia,显然第三个算法更多利用了求公约数的原理和性质。怎麽说呢,跳!

   1:  #include <stdio.h>
   2:  #include <stdlib.h>
   3:  typedef unsigned long long uint64;
   4:   
   5:  uint64 get_greatest_common_divisor(uint64 a,uint64 b)
   6:  {
   7:      uint64 x=a;
   8:      uint64 y=b;
   9:      int k=1;
  10:   
  11:      while(x!=y)
  12:      {
  13:          if(y==0)
  14:          {
  15:              return x*k;
  16:          }
  17:          else if(x==0)
  18:          {
  19:              return y*k;
  20:          }
  21:   
  22:          if(y%2==0 && x%2==0)
  23:          {
  24:              x=x/2;
  25:              y=y/2;
  26:              k=k*2;
  27:          }
  28:          else if(x%2!=0 && y%2!=0)
  29:          {
  30:              if(x>y)
  31:              {
  32:                  x=(x-y)/2;
  33:              }
  34:              else
  35:              {
  36:                  y=(y-x)/2;
  37:              }
  38:          }
  39:          else if(x%2==0)
  40:          {
  41:              x=x/2;
  42:          }
  43:          else if(y%2==0)
  44:          {
  45:              y=y/2;
  46:          }
  47:      }
  48:   
  49:      return x*k;
  50:  }
  51:   
  52:  uint64 get_greatest_common_divisor2(uint64 a,uint64 b)
  53:  {
  54:      uint64 x=a;
  55:      uint64 y=b;
  56:      int k=1;
  57:      int d=0;
  58:      while(x!=y)
  59:      {
  60:          if(y==0)
  61:          {
  62:              return x*k;
  63:          }
  64:          else if(x==0)
  65:          {
  66:              return y*k;
  67:          }
  68:   
  69:          if((x&1)==0 && (y&1)==0)
  70:          {
  71:              x=x>>1;
  72:              y=y>>1;
  73:              k=k<<1;
  74:          }
  75:          else if((x&1)!=0 && (y&1)!=0)
  76:          {
  77:              if(x>y)
  78:              {
  79:                  x=(x-y)>>1;
  80:              }
  81:              else
  82:              {
  83:                  y=(y-x)>>1;
  84:              }
  85:          }
  86:          else if((x&1)==0)
  87:          {
  88:              x=x>>1;
  89:          }
  90:          else if((y&1)==0)
  91:          {
  92:              y=y>>1;
  93:          }
  94:      }
  95:   
  96:      return x*k;
  97:  }
  98:   
  99:  /*
 100:  given by wikipedia
 101:  http://en.wikipedia.org/wiki/Binary_GCD_algorithm
 102:  */
 103:   
 104:  uint64 gcd(uint64 u, uint64 v)
 105:  {
 106:   int shift;
 107:   
 108:   /* GCD(0,x) := x */
 109:   if (u == 0 || v == 0)
 110:     return u | v;
 111:   
 112:   /* Let shift := lg K, where K is the greatest power of 2
 113:      dividing both u and v. */
 114:   for (shift = 0; ((u | v) & 1) == 0; ++shift) {
 115:       u >>= 1;
 116:       v >>= 1;
 117:   }
 118:   
 119:   while ((u & 1) == 0)
 120:     u >>= 1;
 121:   
 122:   /* From here on, u is always odd. */
 123:   do {
 124:       while ((v & 1) == 0)  /* Loop X */
 125:         v >>= 1;
 126:   
 127:    /* Now u and v are both odd. Swap if necessary so u <= v,
 128:       then set v = v - u (which is even). For bignums, the
 129:       swapping is just pointer movement, and the subtraction
 130:       can be done in-place. */
 131:    if (u > v) {
 132:       uint64 t = v; v = u; u = t;}  // Swap u and v.
 133:    v = v - u;                       // Here v >= u.
 134:   } while (v != 0);
 135:   
 136:   return u << shift;
 137:  }

 

114~117算出u,v的因数2的数量,也即是所要求的2的最大幂。该循环将停止在u或者v不能被2整除也即不在含有2的因数或不再是偶数为止,很容易推理通过这一次简单的循环就可以求出u,v含有公因数2的个数。

经过114~117,u,v将至多存一个偶数项或者都为奇数。

如果u为偶数,119到120行将u减半直至为奇数。然后进入123的循环。

123~134循环始终保证u<=v,并不断进行算法第4步的操作也即是v=v-u,v=v/2直至奇数(124~125行)。最终循环停止在v==0上。

可以看出最后一个算法的实现中,循环停止的条件并没有用到u==v这个条件,而是让v减到0,所以实现3的运行步数要比前面两个多一步?

这个留作下回分解吧。

posted @ 2012-06-23 19:00  Dance With Automation  Views(361)  Comments(1)    收藏  举报