说起字符串的左旋和右旋问题,想必大家都不陌生,这是一个在初学C语言过程中经常遇到的一个问题,解题的思路可以说很多,每一个人的看待问题的角度都不同,所以就可以得到不同的解题思路。下面我就列举几种方法:
先从最容易想到的说起:
以字符串abcdef为例,若是左旋问题,首先我们可以拿出首个字符a,将其与后面的每一个字符交换一次,得到新的字符串bcdefa,然后进行交换得到cdefab,循环执行,一直到次数等于你给定的次数。(右旋与此类似)
代码如下所示:
void LRevolve_Words(char *Pword, int k, int len) //左旋,k为指定的左旋字符的个数
{
int i = len;
char temp;
while (k--)
{
for ( i = 0; i < len - 1 ; i++) //依次交换
{
temp = *(Pword + i);
*(Pword + i) = *(Pword + i + 1);
*(Pword + i + 1) = temp;
}
}
}
void RRevolve_Words(char *Pword, int k, int len) //右旋
{
int i = len;
char temp;
while (k--)
{
for ( i = len ; i > 1 ; i--)
{
temp = *(Pword + i - 1);
*(Pword + i - 1) = *(Pword + i - 2);
*(Pword + i - 2) = temp;
}
}
}
上面的方法是最简单的,也容易想到,实现起来简单,代码容易理解,但是最主要的缺点就是效率不高,如果不考虑时间和空间的限制,假设移动的位为k,则要循环k次,每次循环移动1位,这样的空间复杂度是0(1),时间复杂度是0(n*k),如果k大于n,则0(n^2)。因此,如果K>N,右移K - N之后的数组序列跟右移K位的结果是一样的。
下面介绍一种比较巧妙地方法:(个人认为也是最好的方法)
采用先部分再整体的方法(或者先整体再部分,效果差不多):具体实现是先反转前k个字符,然后再反转后n - k个字符,最后再反转整个字符串。这种方法比上面一种方法高效。时间复杂度是0(n),空间复杂度为0(1)。
这种方法也叫三步反转法。(以左旋为例,右旋与之类似)
#include<stdio.h>
#include <string.h>
#include<assert.h>
#define Max_Words 20
void Reverse_String(char * Pword, char * qword)
{
char *pa = Pword;
char *pb = qword;
char temp;
assert(Pword);
assert(qword);
while (pa<pb)
{
temp = *pa;
*pa = *pb;
*pb = temp;
pa++;
pb--;
}
}
int main()
{
char arr[Max_Words];
int len = 0;
int k = 0;
char *pStart = NULL;
char *pEnd = NULL;
printf("请输入您要旋转的字符串内容\n");
scanf("%s", arr);
len = strlen(arr) - 1;
printf("请输入您要旋转的字符个数\n");
scanf("%d", &k);
pStart = &arr[0];
pEnd = arr + len;
Reverse_String(pStart, pStart + k - 1); //把要左旋的k个字符先逆序翻转
Reverse_String(pStart + k, pEnd); //把k+1后的字符逆序翻转
Reverse_String(pStart, pEnd); //整个字符串逆序翻转
printf("旋转之后的字符串为:%s\n", arr);
return 0;
}
接着再介绍一种不是一下子可以想到的方法。就是用递归,当然不是说这方法很难,只是我们不经常用这方法,或者说如果不提醒你,你是无法一下子想到的。(一般会提示你请用递归方法解决)
当然递归也有不同的递归方式:
方式一:
每次向右移动K位,最后递归,把一个规模为N的问题化解为规模为M(M<N)的问题。
举例来说,设字符串总长度为L,左侧要旋转的部分长度为s1,那么当从左向右循环交换长度为s1的小段,直到最后,由于剩余的部分长度为s2(s2==L%s1)而不能直接交换。
该问题可以递归转化成规模为s1+s2的,方向相反(从右向左)的同一个问题。随着递归的进行,左右反复回荡,直到某一次满足条件L%s1==0而交换结束。可以说是第一种方法更好地展示。
方式二:
先说左旋,每次都把一个元素拿出来,然后将把一个元素先拿出来,放在一个临时变量temp之中,并把这个元素改成‘\0’,然后进行递归,直到达到指定的次数,递归结束,之后进行递归的返回,返回后依次拿出之前保存的temp赋值给最后面元素之后的一个空间,(当然这里需要额外的空间,定义的数组要比字符串内容大)。
void LRevolve_Words(char *Pword , int k , int len) //len为数组内容的大小再说右旋,与左旋稍有不同,在递归的过程中首先要将每一个字符向后面移动一位,然后取出最后面的一个元素,并放入temp之中,然后再改成'\0',然后进行递归,递归返回的时候,将前面元素赋值成后面要旋转的元素。
{
char temp;
assert(Pword);
if (k)
{
temp = *Pword;
*Pword = '\0'; //将首字符改成‘\0’
LRevolve_Words(Pword + 1, k - 1 , len);
*(Pword + len ) = temp;
}
*(Pword + len + k) = '\0';
}
void RRevolve_Words(char *Pword , int k , int len){ char temp; int i; assert(Pword); if (k) { temp = Pword[len - 1]; for ( i = 0; i < len ; i++) { Pword[len - i ] = Pword[len - i - 1]; } Pword[len] = '\0'; RRevolve_Words(Pword + 1, k - 1 , len - 1); *Pword = temp; } }
其实还有许多其他的方法,总之没有最好只有更好,大家也可以多多去探索!