《编程珠玑(第2版)》笔记——将一个n元一维向量向左旋转i个位置(第2章)

来源:互联网 发布:windows 启动修复文件 编辑:程序博客网 时间:2024/05/14 04:38

 

转载自:http://blog.csdn.net/gavinming/article/details/7225275

第2章 啊哈!算法


《编程珠玑(第2版)》的第2章,一开始就给出三个问题,其中问题B很有意思:将一个n元一维向量向左旋转i个位置。例如,当n=8且i=3时,向量abcdefgh旋转为defghabc。简单的代码使用一个n元的中间向量在n步就能够完成该工作,你能否仅使用数十个额外字节的存储空间,正比于n的时间内完成向量旋转。

其实就像问题中提到的,如果不考虑空间,这是个很简单的问题。但考虑到空间的时候,能否灵巧地解决该问题?


书中给出了2个比较精巧的方法:

1.求模置换的方法:

其实我们知道有个节约空间的方案:每次向左旋转一个位置(其时间正比于n),总共需要旋转i次。这个方案会消耗过多的运行时间。而求模置换的方法则是尽量让每个数一次移动到位。总体的思想是:以i为除数对n求模,将向量遍历完并一次移动到位。看下面两个例:

示例数组: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

(1)n=12且i=5:

n=12且i=5

(2)n=12且i=3:

n=12且i=3

上述两图展示了两种不同的情况,示例(1)的n=12和i=5最大公约数是1,因此不断求模能够无重复完整遍历数组;而示例(2)中n=12且i=3,有最大公约数3,因此移动3个位置就会出现重复,因此需要移动到下一位再次求模共3次:

[cpp] view plaincopyprint?
  1. //求公约数 
  2. unsigned int Gcd(unsigned int a, unsignedint b) 
  3.     unsigned int temp; 
  4.     while (b != 0) 
  5.     { 
  6.         temp = a % b; 
  7.         a = b; 
  8.         b = temp; 
  9.     } 
  10.  
  11.     return a; 
  12.  
  13. void modShift(int array[],int n,int rotdist) 
  14.     unsigned int gcd = Gcd(n, rotdist); 
  15.  
  16.     for (int i = 0; i < gcd; i ++) 
  17.     { 
  18.         int temp = array[i]; 
  19.         int j = i; 
  20.         int k; 
  21.         while(1) 
  22.         { 
  23.             int k = j +  rotdist; 
  24.             if (k >= n) 
  25.             { 
  26.                 k -= n; 
  27.             } 
  28.              
  29.             if (k == i) 
  30.             { 
  31.                 break
  32.             } 
  33.  
  34.             array[j] = array[k]; 
  35.             j = k; 
  36.         } 
  37.         array[j] = temp; 
  38.     } 
  39.  

2.分段递归交换的方法:

旋转向量x其实就是交换向量ab的两段,得到ba(a代表x中的前i个元素)。假设a比b短,将b分为b1和b2两段,使b2有跟a相同的长度,然后交换a和b2,也就是ab1b2交换得到b2b1a,a的位置已经是最终的位置,现在的问题集中到交换b2b1这两段,又回到了原来的问题。不断递归下去,到b1和b2的长度长度相等交换即可。代码如下

[cpp] view plaincopyprint?
  1. //swapShift 
  2. //swap x[a .. a+offset-1] and x[b .. b+offset-1] 
  3. void swap(int array[],int a,int b,int offset) 
  4.     int temp; 
  5.     for (int i = 0; i < offset; i++) 
  6.     { 
  7.         temp = array[a + i]; 
  8.         array[a + i] = array[b + i]; 
  9.         array[b + i] = temp; 
  10.     } 
  11.  
  12.  
  13. void swapShift(int *array,int n,int rotdist) 
  14.     int p = rotdist; 
  15.     int i = p; 
  16.     int j = n - p; 
  17.  
  18.     while (i != j) 
  19.     { 
  20.         if (i > j) 
  21.         { 
  22.             swap(array, p - i, p, j); 
  23.             i -=j; 
  24.         } 
  25.         else 
  26.         { 
  27.             swap(array, p - i, p + j - i, i); 
  28.             j -= i; 
  29.         } 
  30.     } 
  31.     swap(array, p - i, p, i); 

3.求逆法(翻手算法)

利用向量原理:把x向量分成ab两部分,a是前i个元素,b是后n-i个元素,首先对a求逆,得到a-1b,然后对b求逆得到a-1b-1,然后对整体求逆得到(a-1b-1)-1=ba。

翻手

非常容易理解,代码如下:

[cpp] view plaincopyprint?
  1. //Reverse 
  2. void reverse(int array[],int low,int high) 
  3.     int temp = 0; 
  4.     for(int i = low; i <= (high + low) / 2; i++) 
  5.     { 
  6.         temp = array[i]; 
  7.         array[i] = array[high - (i - low)]; 
  8.         array[high - (i - low)] = temp; 
  9.     } 
  10.  
  11. void reverseShift(int *array,int n,int rotdist) 
  12.     reverse(array, 0, rotdist - 1); 
  13.     reverse(array, rotdist, n - 1); 
  14.     reverse(array, 0, n - 1); 

翻手算法代码非常简短,非常容易理解,而且针对字符串的求逆也不用自己写函数,在时间和空间上都很高效。


总结:

一个简单的问题,在不同的角度,可以找到不同的方法,主要要做到发散思维,找到高效又容易理解的方法。

原创粉丝点击