循环移动字符串

        最近钻研算法,找到一个不错的文章《程序员编程艺术》,作个笔记。

        题目:对给定字符串实现循环左移或右移,比如“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;
}

 

点赞