博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
数组循环移位算法(左旋字符串)【总结】
阅读量:4599 次
发布时间:2019-06-09

本文共 3131 字,大约阅读时间需要 10 分钟。

问题:定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。

解法一(循环换位算法)不考虑时间和空间的限制。设移动的位数为k。则循环k次,每次移动1位。这样的空间复杂度是O(1),时间复杂度是O(n*k)。如果k小于n,则O(n^2)。

void RightShift(char *arr, int N, int k) {
  k %= N ;  //缩小k的范围 while(k--) {
char t = arr[N-1]; for(int i = N-1; i > 0; i--) arr[i] = arr[i-1]; arr[0] = t; } }

虽然这个算法可以实现数组的循环右移,但是算法复杂度为OK * N),不符合题目的要求,需要继续往下探索。

但是在实际操作过程中K未必小于N,也就是说有肯那个时间复杂度超过N^2。通过观察可以可知,循环移动K位和循环移动K%N是一样的,这就将时间复杂度降下来了。

解法二:(三次反转算法)假设原数组序列为abcd1234,要求变换成的数组序列为1234abcd,即循环右移了4位。比较之后,不难看出,其中有两段的顺序是不变的:1234和abcd,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。变换的过程通过以下步骤完成:

  1. 逆序排列abcd:abcd1234 → dcba1234;
  2. 逆序排列1234:dcba1234 → dcba4321;
  3. 全部逆序:dcba4321 → 1234abcd。

Reverse(char *arr, int b, int e) {
for(; b < e; b++, e--) {
char temp = arr[e]; arr[e] = arr[b]; arr[b] = temp; } } RightShift(char *arr, int N, int k) {
k %= N; Reverse(arr, 0, k-1); Reverse(arr, k, N-1); Reverse(arr, 0, N-1); }

解法三:(排列循环算法)对于给定数组A[0..N-1]向后循环换位N-K位运算,可分解为恰好gcd(K,N-K)个循环置换,且0,...,gcd(K,N-K)-1中的每个数恰属于一个循环置换。其中gcd(x,y)表示x和y的最大公因数。

  我们从头开始分析这个问题,对于数组A[0..n-1],要将其向后循环移动k位元素。因为每个元素右移n位后又回到了原来的位置上,所以右移k位等于右移k mod n位。考虑每个元素右移k位后的最终位置,比如对于A[0],右移k位后在k mod n位置上,原来在k mod n位置上的元素右移k位后到了2*k mod n的位置上,把如此因为A[0]的移动而受到连环影响必须移动的位置列出来,就是下面这样一个位置序列:0,k,2*k,...,(t-1)k。其中每一项都是在模n的意义下的位置。t*k mod n 的结果是0。t是使得t*k mod n的结果为0的最小正整数。

  这个位置序列实质上是模n加法群中由元素k生成的一个循环子群。由群论中的结论(该结论的证明见最后)知,循环子群(k)的周期为n / gcd(k,n),元数为n / gcd(k,n),其陪集个数为gcd(k,n)。换句话说,A[0..n-1]循环右移k位的结果是循环子群(k)以及它的所有陪集循环右移一位。例如,将A[0..5] = {1,2,3,4,5,6}循环右移4位,这里n = 6, k = 4, gcd(k, n) = 2。A[0]的最终位置是4,A[4]的最终位置是2,A[2]的最终位置是0,这样,位置0,4,2便是由k=4生成的循环群,周期为6 / gcd(4,6) = 6 / 2 = 3,这样的循环子群共有gcd(4,6) = 2个。

// 数组的循环移位 #include 
int gcd(int m, int n) {
int r; while(r = m % n) {
m = n; n = r; } return n; } void shiftArray(int A[], int n, int k) {
// 因为左移的代码比右移的代码好实现的多,而右移k位 // 等价于左移-k位,-k = n - k。以下代码是通过左移-k位来实现右移k位 k = n - (k % n); for(int i = 0, cnt = gcd(n, k); i < cnt; i++) {
int t = A[i], p = i, j = (k+i) % n; while(j != i) {
A[p] = A[j]; p = j; j = (k + p) % n; } A[p] = t; } } void printArray(int A[], int n) {
for(int i = 0; i < n; i++) {
printf("%-3d", A[i]); if((i+1)%10 == 0) printf("/n"); } } int main() {
int A[] = {
1,2,3,4,5,6, 7}; shiftArray(A, 7, 4); printArray(A, 7); return 0; }

另一种实现方法:

char* string_cyclicshift_v2( char* string, int i ) {
char ch; int exchange; int len; exchange = 0; len = strlen( string ); i = i%len; if ( 0 == i ) return string; int start_pos=0; while ( exchange

上述所用到的那个群论结论的证明:

结论:设G是一个循环群,其中一个生成元素为a,若r和n的最大公约数为d,则a^r的周期为n / d。

 

在模n加法群中,1是一个生成元素,任意元素k=1*k,所以任意元素k生成的循环子群(k)的周期为n / gcd(k,n)。因为gcd(k,n)=gcd(k,n-k),所以也可以写成n / gcd(k, n-k)。

解法四:(部分逆转)将整个数组分成两部分,然后将较小的一部分与另一部分对换,对于剩下的数组继续执行当前算法,直到数组长度相等两段对换.

  原始字符串为:abcdefgh,循环右移三位的结果为defghabc。其操作过程可以分解如下:

  abc defgh-->def abc gh --> defgh c ab--> defgha c b --> defghabc

参考网址

转载于:https://www.cnblogs.com/jiaolong/archive/2011/10/09/2203854.html

你可能感兴趣的文章
SQLAlchemy
查看>>
BZOJ 1303: [CQOI2009]中位数图 问题转化_扫描_思维
查看>>
SP1026 FAVDICE - Favorite Dice 数学期望
查看>>
NodeJS、NPM安装配置步骤(windows版本)
查看>>
今日内容的回顾12
查看>>
js中字符串常用熟悉和方法
查看>>
【矩阵+十进制快速幂】[NOI2013]矩阵游戏
查看>>
Java一个简单的文件工具集
查看>>
蓝牙BLE扫描成功,log中打印出扫描到的设备
查看>>
React(v16.8.4)生命周期详解
查看>>
一般处理应用页中绑定方法代码段
查看>>
React组件Components的两种表示方式
查看>>
无限鼠标没反应了
查看>>
CSU - 1356 Catch(dfs染色两种写法,和hdu4751比较)
查看>>
zabbix监控php-fpm的性能
查看>>
温故知新 div + css笔记
查看>>
针对降质模型中的模糊SR
查看>>
ios开发学习笔记001-C语言基础知识
查看>>
POJ1142Smith Numbers一道简单的数学题
查看>>
UIButton(改变Title和image位置)
查看>>