出处: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进行处理的时候,以及处理之后转换成块元素的这个地方,感觉很巧妙,这样的证明绝对秒杀什么陪集数目的说法,回味无穷。
三个算法的代码:
目录(?)[+]
我的第一篇文章:编程珠玑(一):前言 && 位图排序,从发布以来到目前为止已经被浏览了超过一千次。
有几个朋友进行了回复,都是给予支持和鼓励的。在此,对这些朋友表示感谢!
学习本来就是一件需要耐心和毅力的事情,各种滋味只有同道中人才能理解。
同时,学习也是一件能够带给快乐和成就感的事情:
当你搞懂一个算法、理解一个原理时候,其中的喜悦不亚于盛夏的冷饮,寒冬的暖炉。
多谢朋友们,我会继续努力的,为了自己也为了对某个人的承诺!
请将一个具有n个元素的一维向量x向左旋转i个位置。例如,假设n=8, i=3,那么向量abcdefgh旋转之后得到向量defghabc。
不足:
这个方案使用了i个额外的位置,这使得它太浪费空间了。
3、说实话,这个算法理解起来还是有一定的难度的,想要把原理搞懂还是需要下点功夫的。
下面就让我们一起来分析一下这个算法吧,准备好了吗,我们开始了!
两个整数的最大公约数是能够同时整除它们的最大的正整数。辗转相除法基于如下原理:
两个整数的最大公约数等于其中较小的数和两数的差的最大公约数。
我们来看一下这个原理的证明:
设两数为a、b(b r=a mod b,r为a除以b以后的余数,辗转相除法即是要证明gcd(a,b)=gcd(b,r)。
证明步骤如下:
1)令c=gcd(a,b),则设a=mc,b=nc
2)r = a mod b,所以r = a - k*b = mc - k*nc = (m - kn) * c。即,r = (m - kn) * c
3)由r = (m - kn) * c 可知:c也是r的因数
4)可以肯定m - kn与n互质(why?)
假设他们不互质,必然存在大于1的整数d,使得m-kn = xd, n = yd。那么,m = xd + kyd = (x + ky)d
那么,a = mc = (x + ky)dc , b = nc = ydc 。=> a,b的最大公约数为dc,而不是c。
5)既然m - kn与n互质,所以c = gcd(r,b)。
结论,gcd(a,b)=gcd(b,r)
(辗转相除法更详细的描述请参照百度百科:http://baike.baidu.com/view/255668.htm。)
杂耍算法中如下两点我无法理解:
1.为什么程序要循环执行gcd(i,n)次
2.这个算法是通过什么样的机制就让所有位置上的元素都移动到了预期的位置
程序的走向我是明白的,但是核心算法始终不得其解。
最终还是需要借助网络,搜到了园子内一位朋友写的文章(关于编程珠玑中习题2.3的一点思考)
看完以后,有种豁然开朗的感觉,在此多谢这个朋友了。
不过,那篇文章中有些概念的细节讲的不是太清楚,我也是借助网络才有了更清晰的了解。
再次,我来总结个精简吧的吧,写的不好还请各位包涵!
先从几个概念开始:
了解了以上三个概念后,我们能够得出如下的结论:
如果i和n互质,那么序列:
0 i mod n 2i mod n 3i mod n …… (n-1)*i mod n
就包括了集合{0,1,2,……n-1}的所有元素
我们为什么会有这样的结论呢,下面来证明一下
前提条件
对于模n来说,序列0,1,2,……,n-1本身就是一个完全剩余类(即他们之间两两互不同余)
证明步骤
1)从此序列中任取两个数字xi,xj(0 <= i,j <= n-1),则有Xi≠Xj (mod n), 注:这里由于不能打出不同余字符因此用不等于替代
2)由于i和n是互质的,所以 xi * i ≠ xj * i(mod n)
=》这就说明,xi从0开始一直取值到n-1,得到的序列0 * i,1 * i,2 *i,……(n-1)*n就是一个完全剩余类。即集合0,1,2,……n-1}
有了以上的结论,如果i和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 ~ n-1的所有位置都被移动过了
请注意第二个操作,X[0] = X[i mod n]。
该操作决定了整体的导向,该操作将i mod n位置的值移动到了最开始的位置。
由于i,2i,……之间的偏移量是相同的,所以整个操作实际上就是讲序列向左移动i个位置(超过了开始位置的接到最右边去)
那么如果i和n不互质呢?
自然的想法是利用我们已经得到的结论,让i和n互质,即让i’ = i/(gcd(i,n)),n’ = n/(gcd(i,n))。
这样便构造了一对互质的数, i’和n’。这意味着把整个数组的每g=gcd(i,n)个元素组成块。
由于我们的单位是块元素,每次相当于把g中的一个元素移到最终位置上,所以总共需要g次移动。