数组循环移位
2013-05-25 20:16 夜与周公 阅读(298) 评论(0) 编辑 收藏 举报从本节开依据网上的一些资料学习总结常见的一些算法问题(CSDN程序员编程艺术),第一个总结的是“数组循环移位”算法。
算法问题:设计一个算法,把一个含有N个元素的数据,循环左移(右移)K个位置,要求时间复杂度O(N),且只允许使用两个附件的变量。
算法示例:abc1234循环左移3位abc1234->1234abc。
最先想到的办法,每次想做移动一个位置,循环K次,算法的时间复杂度O(K*N):
void LeftShift(char* arr, int N, int k) { while(k--) { char temp = arr[0]; for (int i =1;i != N;i++) arr[i-1] = arr[i]; a[N-1] = temp; } }
这种方法不满足题目要求,需要做进一步的修改。主要有两种思路:1)考虑如何一次到位,将元素正确的映射到需要移动到的位置;2)序列转折算法。
对于第一种思路,先把数组中的元素顺时针方向的圆钟,那么向做移动k个位置,等价于将圆钟逆时针旋转K个位置。在编程的时候,关键是如何找到这种映射关系。上例中向左移动三个位置,第3个位置元素将会移动到第0个位置(arr[0] = arr[3]),第6个位置元素替换到第3个位置,第9个位置的元素(圆钟的第9个元素对应数组中第9%7=2位置元素)。以此类推完成替换。但这种方法需要特别注意的是:可能在某处循环移动中仅做小循环移动(如某种情况:0<-3<-6<-0),为此我们一旦出现小循环,我们需要强制从一个新的位置开始再次循环。思路一实现代码:
void left_shift(char* arr, int k) { int N = strlen(arr); int beg_pos = 0; int current_pos = beg_pos; char temp = arr[beg_pos] ; for (int i =0; i != N; i++) { int next_pos = (current_pos+k)% N; if (next_pos != beg_pos) { arr[current_pos] = arr[next_pos]; current_pos = next_pos; } else { arr[current_pos] = temp; beg_pos++; temp = arr[beg_pos]; current_pos = beg_pos; } } } int main() { char str[] = "abcd123"; cout<<"Please Enter shift position:"; int k; cin>>k; cout<<"Before shift:"<<str; left_shift(str, k); cout<<"\nAfter shift:"<<str; system("pause"); return 0; }
思路二实现方法则相对比较巧妙,运用了序列转置公式。我们观察之前的例子可以发现:数组由两个部分组成,arr[0:k]部分与arr(k:N-1]部分,左移k个元素实际上就是这两个部分对调了顺序。假设原序列Z=XY,由X,Y两部分组成,其中X含有k个元素,则左移k个元素等价于Z' =YX。为了得到Z',需要使用序列转置公式:
因此算法的步骤如下:
1) 翻转序列X。前面例子中abc->cba
2) 翻转序列Y。前面例子1234->4321
3) 组合翻转后序列,并做翻转。组合后的序列cba4321->1234abc。
算法实现如下:
void reverse(char* arr, int beg_pos, int end_pos) { for(;beg_pos < end_pos;beg_pos++, end_pos--) { char temp = arr[beg_pos]; arr[beg_pos] = arr[end_pos]; arr[end_pos] = temp; } } void left_shift(char* arr, int k) { int N = strlen(arr); reverse(arr, 0, k-1); reverse(arr, k, N-1); reverse(arr, 0, N-1); } int main() { char str[] = "abcd123"; cout<<"Please Enter shift position:"; int k; cin>>k; cout<<"Before shift:"<<str; left_shift(str, k); cout<<"\nAfter shift:"<<str; system("pause"); return 0; }
比较两者方法,思路一比较容易想到,但是里面可能隐藏着“小循环”不易察觉,需要做额外的注意,方法一正确性证明比较困难一点。方法二,相对比较优雅,但难以想到这种解决方案。