zoukankan      html  css  js  c++  java
  • 关于编程珠玑中习题2.3的一点思考

        这两天看到编程珠玑第二章,关于习题2.3中说到杂耍算法执行gcdi,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.

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

          

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

    1 同余

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

    2 同余类

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

           0 mod m1 mod m,(m-1mod m

    3 完全剩余系

           一组数y1,y2,…ys称为是模m的完全剩余系,如果对任意的a有且仅有一个yja对模m的剩余,即a同余于yjm

           由此可见0,1,2m-1是一个完全剩余系。因此,如果m个数是两两不同余的,那么这m个数便是完全剩余系。


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

           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,2n-1本身是一个完全剩余系,即它们两两互不同余。设此序列为Xi0<=i<=n-1),可得下式:

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

    由于in是互质的,所以

     

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

     

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

      

    有了上面的结论,那么如果in互质,下面的赋值过程便能完成所有值的赋值(设数组为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}所有元素放到了最终位置上,每次完成一个元素的放置。

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

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

          

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

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


    三个算法的代码:

    1 /*
    2 programming pearls: chapter2
    3
    4 Answer for Rotating a one-dimensional vector of n elements left by i positions.
    5
    6 method1: process 杂耍算法
    7 method2: process2 交换算法
    8 method3: process1 反转算法
    9  */
    10
    11 #include <iostream>
    12
    13  using namespace std;
    14
    15  const int num = 10;
    16  int a[num] = {0,1,2,3,4,5,6,7,8,9};
    17
    18  //answer 1
    19 /*
    20 parameter 1: the vector to be rotated
    21 par 2: the num of steps to rotate towards left
    22  */
    23
    24  //assume m and n are not zero
    25  int gcd(int m, int n)
    26 {
    27 while(m != n)
    28 {
    29 if(m > n)
    30 m -= n;
    31 else
    32 n -= m;
    33 }
    34 return m;
    35 }
    36
    37  //jungle code, method 1
    38  void process(int* a, int i)
    39 {
    40 int n,m;
    41 for(int j=0; j < gcd(i,num); j++)
    42 {
    43 n = a[j];
    44 for(m=j+i; m!=j; m=(m+i)%num)
    45 {
    46 a[(m-i+num)%num] = a[m];
    47 }
    48 m = (m-i+num)%num;
    49 a[m] = n;
    50 }
    51 }
    52
    53
    54  void reverse(int m, int n)
    55 {
    56 int mid = (m+n)/2;
    57 int temp;
    58  // int j;
    59  
    60 for(int i=m, j=n; i <= mid; i++,j--)
    61 {
    62 temp = a[i];
    63 a[i] = a[j];
    64 a[j] = temp;
    65 }
    66 }
    67
    68  //methond 3 反转算法
    69  void process1(int* a, int i)
    70 {
    71 reverse(0,i-1);
    72 reverse(i,num-1);
    73 reverse(0,num-1);
    74 }
    75
    76
    77  //method 2 交换算法
    78  //swap a[i..i+m-1] with a[j..j+m-1]
    79  void Swap(int*a, int i, int j, int m)
    80 {
    81 for(int l=0; l < m; l++)
    82 {
    83 int temp = a[i+l];
    84 a[i+l] = a[j+l];
    85 a[j+l] = temp;
    86 }
    87 }
    88
    89  //n remains the beginning of the rightmost section to be
    90  // swapped. use variables i and j to denote the lengths of the
    91  // sections still to be swapped.
    92  //loop invariant:
    93  // m n-i n n+j p-1
    94  // |already swapped|swap with b[n..n+j-1]|swap with b[n-i..n-1]|already swapped|
    95  void process2(int* a, int l)
    96 {
    97 if(l==0 || l==num)
    98 {
    99 return;
    100 }
    101
    102 int n = l;
    103 int j = num-l;
    104 int i = l;
    105 while(i != j)//当交换的两边长度相等时终止,最后再将相等的两边交换即可
    106   {
    107 if(i > j)
    108 {
    109 Swap(a,n-i,n,j);
    110 i -= j;
    111 }
    112 else if(i < j)
    113 {
    114 Swap(a, n-i, n+j-i,i);
    115 j -= i;
    116 }
    117 }
    118 Swap(a,n-i,n,i);
    119 }
    120
    121  int main()
    122 {
    123 process2(a,8);
    124 //process1(a,9);
    125  
    126 for(int i=0; i < num; i++)
    127 cout<<a[i]<<" ";
    128 cout<<endl;
    129
    130 int r;
    131 cin>>r;
    132 return 0;
    133 }
  • 相关阅读:
    (十)android项目中,通用布局的对话框的实现。
    devexpress控件layoutview特效之一旋转木马的实现
    devexpress设置皮肤、字体以及折叠菜单、伸缩Panel的实现
    devexpress设置系统全局字体(含工具栏字体)
    devexpress表格控件gridcontrol实现纵向标头
    中文繁简互换以及获取中文字符串首字母
    devexpress显示缓冲滚动条与实现类似QQ消息推送效果
    gridcontrol显示行号,总行,打印,导出Excel,设置标头及内容居中方法
    devexpress表格控件gridcontrol特殊应用(一)——实现禁用特定行(附源代码)
    devexpress表格控件gridcontrol设置隔行变色、焦点行颜色、设置(改变)显示值、固定列不移动(附源码)
  • 原文地址:https://www.cnblogs.com/HappyAngel/p/1936905.html
Copyright © 2011-2022 走看看