问题描述:
B.将一个n元一维向量向左旋转i个位置。例如,当n=8且i=3时,向量abcdefgh旋转为defghabc. 简单的代码使用一个n元的中间向量在n步内完成该工作。你能否仅使用数十个额外字节的存储空间,在正比于n的时间内完成该向量的旋转?
问题解析:
1、以正比于n的时间(相当于n步内)完成该操作,那么就是每个元素的移动都差不多一步到位,如将第4位的d一步移动到第1位处,其他元素也也一样。
2、额外空间只有几十个字节,这里说明并不能将前i个保存到临时空间,这样当i很大时,会消耗过多的内存空间。
3、题目条件对运行时间和内存空间都有严格的限制,
解决方案:
作者给出的方案1:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include <cstdio>
#include <cstdlib> // srand, rand #include <cassert> // assert // 递归求最大公约数 int gcd( int a, int b) { assert(a > 0 && b >= 0); if (b != 0){ return gcd(b, a%b); } return a; } /************************************************************************/ // 函数名称:my_reverse // 函数目的:对输入的数组循环移位进行反转 // 函数参数:arr:要反转的数组 arraysize:数组长度 reversenum:反转个数 /************************************************************************/ void my_reverse( int arr[], int arraysize, int reversenum) { if (reversenum == 0 || reversenum == arraysize) { return; } for ( int i = 0; i < gcd(reversenum, arraysize); ++i){ int temp = arr[i]; int j = i; while ( 1){ int k = j + reversenum; if (k >= arraysize){ k -= arraysize; } if (k == i) { break; } arr[j] = arr[k]; j = k; } arr[j] = temp; } return; } int main() { int a[ 12] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; int arraysize = 12; // 数组长度 int reversenum = 5; // 反转个数 my_reverse(a, arraysize, reversenum); for ( int i = 0; i < 12; ++i) { printf( "%c\t", a[i]); } return 0; } |
1、这里使用了最大公约数的概念,那么最大公约数在算法中有哪些具体应用?为什么这里要使用最大公约数作为置换次数?
2、作者在给出解答是提到了“置换群”的概念? 怎么去理解它?
作者给出的方案2:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
#include <cstdio>
#include <cstdlib> // srand, rand #include <ctime> // time, clock_t #include <cassert> // assert /************************************************************************/ // 函数名称:my_swap // 函数目的:交换arr[index_a..index_a + swap_num -1]和arr[index_a..index_b + swap_num -1] // 函数参数:arr:要交换的数组 arraysize:数组长度 index_a\index_b:数组索引 // swap_num:要交换的数目 /************************************************************************/ void my_swap( int arr[], int arraysize, int index_a, int index_b, int swap_num) { assert(index_a >= 0 && (index_a+swap_num) <= index_b && (index_b+swap_num) <= arraysize); for ( int i = 0; i < swap_num; ++i){ int temp = arr[index_a + i]; arr[index_a + i] = arr[index_b + i]; arr[index_b + i] = temp; } return; } /************************************************************************/ // 函数名称:my_reverse // 函数目的:对输入的数组进行反转 // 函数参数:arr:要反转的数组 arraysize:数组长度 reversenum:反转个数 /************************************************************************/ void my_reverse2( int arr[], int arraysize, int reversenum) { if (reversenum == 0 || reversenum == arraysize) { return; } int i, j, p; i = p = reversenum; j = arraysize - p; while ( i != j){ if (i > j){ my_swap(arr, arraysize, p-i, p, j); i -= j; } else { my_swap(arr, arraysize, p-i, p+j-i, i); j -= i; } } my_swap(arr, arraysize, p - i, p, j); return; } int main() { int a[ 12] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; int arraysize = 12; // 数组长度 int reversenum = 7; // 反转个数 // my_reverse(a, arraysize, reversenum); my_reverse2(a, arraysize, reversenum); for ( int i = 0; i < 12; ++i) { printf( "%c\t", a[i]); } return 0; } |
心得与疑惑:
1、说实话我还是不太怎么理解这个算法,只是依照作者提示,实现了代码!
2、下面是实现过程的分析步骤,帮助理解该算法:
如果arrsize = 5, reversenum=5,那么该程序执行如下:
(1) p = 5, i =5, j = 7
swap(0, 7, 5) 结果: hijkl fg abcde
(2) p = 5, i =5, j = 2
swap(0, 5, 2) 结果: fg jkl hi abcde
(3) p = 5, i =3, j = 2
swap(2, 5, 2) 结果:fg hi l jk abcde
(4) p = 5, i =1, j = 2
swap(4, 6, 1) 结果:fghi k j labcde
(5) p = 5, i =1, j = 1 ---->退出循环
swap(4, 5, 1) 结果:fghi j k labcde
3、作者说该段求解算法 my_reverse2是同求最大公约数的欧几里得算法是同构的,这或许对理解该段代码提供一个很好的思路,如下:
1
2 3 4 5 6 7 8 9 10 |
int gcd(
int i,
int j)
{ assert(i > 0 && j > 0); while (i != j){ if (i > j) i -= j; else j -= i; } return i; } |
利用翻转求逆的性质,得到最终的解决方案,该方案是三个中最完美的一个方案。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <cstdio>
#include <cstdlib> // srand, rand #include <cassert> // assert /************************************************************************/ // 函数名称:myswap // 函数目的:反转arr索引在[index_a, index_b]之间的值 // 函数参数:arr:要交换的数组 arraysize:数组长度 index_a\index_b:数组索引 /************************************************************************/ void myswap( int* arr, int arraysize, int index_a, int index_b) { assert(index_a >= 0 && (index_b-index_a) >= 0 && index_b < arraysize); int i = index_a, j = index_b; for (; i <= j; i++, j--){ int temp = *(arr+i); *(arr+i) = *(arr + j); *(arr + j) = temp; } return; } /************************************************************************/ // 函数名称:my_reverse // 函数目的:对输入的数组循环移位进行反转 // 函数参数:arr:要反转的数组 arraysize:数组长度 reversenum:反转个数 /************************************************************************/ void my_reverse3( int* arr, int arraysize, int reversenum) { assert(reversenum <= arraysize); myswap(arr, arraysize, 0, reversenum- 1); myswap(arr, arraysize, reversenum, arraysize- 1); myswap(arr, arraysize, 0, arraysize- 1); return; } int main() { int a[ 12] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'}; int arraysize = 12; // 数组长度 int reversenum = 6; // 反转个数 my_reverse3(a, arraysize, reversenum); for ( int i = 0; i < 12; ++i) { printf( "%c\t", a[i]); } return 0; } |
心得与疑惑:
1、求逆:ab->a^r b-> a^r b^r ->(a^r b^r)^r 最为简洁,也最好理解,堪称完美!
2、应该把该代码作为一种常识。