最近钻研算法,找到一个不错的文章《程序员编程艺术》,作个笔记。
题目:对给定字符串实现循环左移或右移,比如“abcdefg”,循环左移2位变成“cdefgab”。
方法是比较多的,可以逐位移动,时间复杂度可优化至O(n^2);也可以用两个指针按给定位数逐小段翻转(这个方法后面的不完整尾段比较麻烦),时间复杂度是O(2n);当然也可以浪费一定的辅助数组空间,来达到时间复杂度O(n);这些方法就不详细介绍,从时间和空间复杂度上来看比较高效和方便的两个算法是:
一、三次翻转
比如说“abcdefghij”循环右移3位,那么将字符串分为“abcdefg”和“hij”两段(左移3位就分为“abc”和“defghij”),然后分别对两段短字符串进行翻转,变成“gfedcba”和“jih”,最后对整个字符串两做一次翻转,变为“hijabcdefg”。实现循环右移3位。这种算法的时间复杂度为O(2n)。
具体右移算法如下(未作优化):
#include <stdio.h>
#include <string.h>
//对给定长度字符串实现翻转
void myreverse(char *str,int n)
{
char *p1=str,*p2=str+n-1;
char tmp;
while(p1<p2)
{
tmp=*p1;
*p1=*p2;
*p2=tmp;
++p1;
--p2;
}
}
int main(void)
{
int m,n;
char str[]="abcdefghij";
n=strlen(str);
printf("%s\n",str);
scanf("%d",&m); //m:循环右移移动位数
if(m<=0)
return -1;
m%=n;
myreverse(str,n-m); //左移:myreverse(str,m);
myreverse(str+n-m,m); //左移:myreverse(str+m,n-m);
myreverse(str,n);
printf("%s\n",str);
return 0;
}
二、STL的rotate算法
大致上的意思是,对一个给定长度为n的字符串,令其循环移动m位,m和n的最大公约数是以m的单位距离的循环链数量,只需将每个循环链上的元素移动一个位置就可以实现循环移动m位的目的,这个过程共交换了n次。
举个栗子,“abcdefghij”共10个元素,要循环左移4位,那么它们的最大公约数是2,即有两条循环链,分别是“aeicg”和“bfjdh”,将它们分别在各自的链内左移一位(相对于整个字符串而言是4位),就可以得到“efghijabcd”。
具体循环左移算法如下:
#include <stdio.h>
#include <string.h>
//求最大公约数,即循环链数,使用碾转相除法(欧几里得算法)
int gcd(int i,int j)
{
int tmp;
while((i%=j) != 0)
{
tmp=i;
i=j;
j=tmp;
}
return j;
}
//循环左移
void myrotate(char *str,int m)
{
int n=strlen(str);
int i,j,k=gcd(n,m);
char tmp;
for(i=0;i<k;++i) //循环链切换
{
tmp=str[i];
for(j=0;j<n/k-1;++j) //循环链内元素切换
{
str[(i+j*m)%n]=str[(i+(j+1)*m)%n]; //循环左移
//循环右移:str[(i+(n/k-j)*m)%n]=str[(i+(n/k-j-1)*m)%n];
}
str[(i+j*m)%n]=tmp;
}
}
int main(void)
{
int m;
char str[]="abcdefghij";
printf("The initial string is:%s\n",str);
scanf("%d",&m); //m:循环左移位数
myrotate(str,m);
printf("The final string is:%s\n",str);
return 0;
}