关于编程珠玑中习题2.3的一点思考

出处:http://www.cnblogs.com/HappyAngel/archive/2011/01/16/1936905.html

    这两天看到编程珠玑第二章,关于习题2.3中说到杂耍算法执行gcd(i,n)次后即可停止,这里我想了很久为什么?书中提到的Swap Sections解决了我的疑惑,在明白为什么的时候真的 “啊哈”了一下,原来这样,感觉证明非常巧妙,不敢独享,所以复述如下。

problem:

   rotate a one-dimensional vector of n elements left by i positions. For instance, with n=8 and i=3, the vector abcdefgh is rotated to defghabc.

   这个问题可以有三个巧妙的算法来解决(关于这三个算法的代码,我自己实现了一下,附在文后),这在Swap Sections都提到了,包括神奇的反转算法、递归交换算法以及下面这个杂耍算法:

one solution is:

   move x[0] to the temporary t, then move x[i] to x[0], x[2i] to x[i], and so on, until we come back to taking an element from x[0], at which point we instead take the element from t and stop the process.

If that process didn't move all the elements , then we start over at x[1], and continue until we move all the elements.

   这个算法在执行gcd(i,n)次后就停止了,为什么?

先来了解一点数论知识(一下内容摘自《初等数论》,潘承洞著):

1 同余:

  设m不等于0, 若m|a-b,即 a-b=km, 则称m为模,a同余于b模m,以及b是a对模m的剩余。记作 a≡b(mod m)。

2 同余类

  对给定的模m,有且恰有m个不同的模m的同余类,他们是

  0 mod m,1 mod m,…,(m-1)mod m。

3 完全剩余系

  一组数y1,y2,…,ys称为是模m的完全剩余系,如果对任意的a有且仅有一个yj是a对模m的剩余,即a同余于yj模m。由此可见0,1,2,…,m-1是一个完全剩余系。因此,如果m个数是两两不同余的,那么这m个数便是完全剩余系。

 

基于以上知识,我们可以证明这样一个事实,即如果i和n互质的话,那么序列:

0 i mod n 2i mod n 3i mod n …. (n-1)*i mod n

就包括了集合{0,1,2 … n-1}的所有元素。(下一个元素(n)*i mod n 又是0)

为什么?

证明:

   由于0,1,2,…,n-1本身是一个完全剩余系,即它们两两互不同余。设此序列为Xi(0<=i<=n-1),可得下式:

Xi≠Xj (mod n), 注:这里由于不能打出不同余字符因此用不等于替代

由于i与n是互质的,所以 Xi*i ≠i*Xj (mod n),这里由于不能打出不同余字符因此用不等于替代

因此i*Xi是m个互不同余数,那么可断定它们是完全剩余系。得证。

有了上面的结论,那么如果i和n互质,下面的赋值过程便能完成所有值的赋值(设数组为X[0..n-1],长度为n):

   t = X[0]

   X[0] = X[i mod n]

   X[i mod n] = X[2i mod n]

   …….

   X[(n-2)*i mod n] = X[(n-1)*i mod n]

   X[ (n-1)*i mod n] = t。

   因为以上操作已经把包括{0,1,…,n-1}所有元素放到了最终位置上,每次完成一个元素的放置。

   根据以上我们得到了一个中间结论,如果i和n互质,我们就可以一次完成。那么如果i和n不是互质的呢?

   自然的想法是利用我们已经得到的结论,让i和n互质,即让i’ = i/(gcd(i,n)),n’ = n/(gcd(i,n))。这样便构造了一对互质的数, i’和n’。这意味着把整个数组的每g=gcd(i,n)个元素组成块,如下所示:

   这样,根据已得结论,我们可以一次获得最终答案,因为i’和n’互质,由于我们的单位是块元素,所以,必须要g次来完成块的移动,每次相当于把g中的一个元素移到最终位置上。所以总共需要g次移动,算法终止。□

   整个证明过程最巧妙的地方在于对i和n进行处理的时候,以及处理之后转换成块元素的这个地方,感觉很巧妙,这样的证明绝对秒杀什么陪集数目的说法,回味无穷。

三个算法的代码:

/*
     programming pearls: chapter2
 
     Answer for Rotating a one-dimensional vector of n elements left by i positions.
 
     method1: process 杂耍算法
     method2: process2 交换算法
     method3: process1 反转算法
  */
 
 #include <iostream>
 
  using namespace std;
 
  const int num = 10;
  int a[num] = {0,1,2,3,4,5,6,7,8,9};
 
  //answer 1
 /*
     parameter 1: the vector to be rotated
     par 2: the num of steps to rotate towards left 
  */
 
  //assume m and n are not zero
  int gcd(int m, int n)
 {
     while(m != n)
     {
         if(m > n)
             m -= n;
         else
             n -= m;
     }
     return m;
 }
 
  //jungle code, method 1
  void process(int* a, int i)
 {
     int n,m;
     for(int j=0; j < gcd(i,num); j++)
     {
         n = a[j];
         for(m=j+i; m!=j; m=(m+i)%num)
         {
             a[(m-i+num)%num] = a[m];
         }
         m = (m-i+num)%num;
          a[m] = n;
     }
 }
 
 
  void reverse(int m, int n)
 {
     int mid = (m+n)/2;
     int temp;
  //    int j;
  
     for(int i=m, j=n; i <= mid; i++,j--)
     {
         temp = a[i];
         a[i] = a[j];
         a[j] = temp;
     }
 }
 
  //methond 3  反转算法
  void process1(int* a, int i)
 {
     reverse(0,i-1);
     reverse(i,num-1);
     reverse(0,num-1);
 }
 
 
  //method 2  交换算法
  //swap a[i..i+m-1] with a[j..j+m-1]
  void Swap(int*a, int i, int j, int m)
 {
     for(int l=0; l < m; l++)
     {
         int temp = a[i+l];
         a[i+l] = a[j+l];
         a[j+l] = temp;
     }
 }
 
  //n remains the beginning of the rightmost section to be
  // swapped. use variables i and j to denote the lengths of the
  // sections still to be swapped.
  //loop invariant:
  //    m                n-i                   n                     n+j           p-1 
  //    |already swapped|swap with b[n..n+j-1]|swap with b[n-i..n-1]|already swapped|
  void process2(int* a, int l)
 {
     if(l==0 || l==num)
     {
         return;
     }
 
     int n = l;
     int j = num-l;
     int i = l;
     while(i != j)//当交换的两边长度相等时终止,最后再将相等的两边交换即可
      {
         if(i > j)
         {
             Swap(a,n-i,n,j);
             i -= j;
         }
         else if(i < j)
         {
             Swap(a, n-i, n+j-i,i);
             j -= i;
         }
     }
     Swap(a,n-i,n,i);
 }
 
  int main()
 {
     process2(a,8);
     //process1(a,9);
  
     for(int i=0; i < num; i++)
         cout<<a[i]<<" ";
     cout<<endl;
     
     int r;
     cin>>r;
     return 0;
 }


 

你可能感兴趣的:(关于编程珠玑中习题2.3的一点思考)