关于编程珠玑中习题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 }

 

 

 

你可能感兴趣的:(编程珠玑)