码迷,mamicode.com
首页 > 其他好文 > 详细

左旋转字符串

时间:2015-06-19 22:54:51      阅读:218      评论:0      收藏:0      [点我收藏+]

标签:

问题描述:字符串的左旋转操作定义为:把字符串前面n个字符移动到字符串的尾部;如取n=3,把字符串asdfghj左旋转3位得到字符串fghjasd。请实现字符串左旋转的函数,要求对长度为length的字符串操作的时间复杂度为O(length),空间复杂度为O(1)。 

实现字符串左旋转很简单,但是有时间复杂度和空间复杂度的要求。

思路一:暴力旋转法

对于前面的n个字符串,每次左旋一位,整个字符串也紧跟着移动向左移动一位,以此循环,直至左旋n个字符串。

假设字符串char S[length+1],额外开辟一个字符串变量char b,S=‘asdfghj‘,length = 7,n=3,m=length-n=4。该方法的示意图如下:

技术分享

该算法思路简单明了,但是时间复杂度比较高,为n*m,代码如下:

void left_mov(char s[],int n)
{
	int i,j;
	char b;
	for(i=0;i<n;i++)
	{
		b = s[0];
		j = 0;
		while(NULL != s[j+1])
		{
			s[j] = s[j+1];
			j++;
		}
		s[j] = b;
	}
	cout<<s;
}

思路二:一次到位法

对此,引出一些别的方法,如指针翻转法、递归转换法(详情参考July的博客 http://blog.csdn.net/v_JULY_v/article/details/6322882)。指针反转法,递归转换法,这些方法,都没有把字符串S中的元素进行一步到位的移动,这就使得移动的次数很多。假如对于字符串S中的每个元素,能够计算出字符串左旋m位之后,每个元素的具体位置,那样就可以一次性的移动到该位置,这样子就可以大大降低时间复杂度了,针对这个思路,提出了以下算法:

int num 作为循环停止的一个判断条件,当num=length-1的时候停止循环;int k 表示执行每一步step之后,字符串中空出的元素的下标为k,即下一个step就要填补这第k个元素的位置;int m = length-n 表示剩余的m个要左移的元素(当然了,前面那n个元素要补在这m个元素的后面,要不怎么叫做左移),为了便于区别,我们这里做一下定义,S_n表示由字符串S的前面n个元素组成的一个子串(S[0-(n-1)]),S_m表示由字符串S的后面m个元素组成的一个子串(S[n-length])。

刚开始的时候,将S[0]转移给b,即S[0] -> b,此时,令k = 0; num = 0; 知道了k之后,怎么求出下一个k,这是一个比较难的问题,也是这个方法的核心。因为k是当前字符串空出的元素的下标,那么,这个位置应该放什么元素呢?当k=0的时候,那么,S[k]将要存放的肯定是S_m的第一个元素,为什么呢,因为S的前面的n个元素都要被左旋转到S的后面了,那么旋转后,S[0]存放的肯定就是S_m的第一个元素;同理可以知道,当k=1的时候,S[1]存放的肯定就是S_m的第二个元素......可是,问题又来了,当超过S_m的容量的时候怎么办呢,因此,得到k之后,要做一下判断,判断S[k]将要存放的是S_m中的元素还是S_n中的元素,如果是S_m中的,那存放的又是S_m中的哪一个元素,如果是S_n中的,存放的又是S_n中的哪一个元素;仔细推敲一下,就可以得到以下的结论:

if(k<m)
{
s[k] = s[lenth-(m-k)];
k = lenth-(m-k);
}
else
{
s[k] = s[n-(lenth-k)];
k = n-(lenth-k);
}

由此,我们可以得到如下图的基本思路,蓝的的数字表示相应的step;转移过程表示字符串中元素的转移,S[0] -> b表示将S[0]中的字符串存放在b中,同理S[3] -> S[0]表示将S[3]存放在S[0]中:num从零开始,字符串S中更新(存入替代原来的)一个元素,就自加1;k的转移过程根据上面的方法来进行判断;step 7 进行完毕之后,num=6,表示S中已经有6个字符串进行更新了,那么此时要结束循环,把存放在b中的那个元素放在S[k]中,这就是step 8。

 技术分享

代码如下:

void left_mov1(char s[],int lenth, int n)
{
	int k,m;
	char b;
	n = n%lenth;//当n>length的时候,取模
	k = 0;
	b = s[0];
	m = lenth-n;
	int num = 0; 
	k = 0;
	while(num<(lenth-1))
	{   
		if(k<lenth-n)
		{
			s[k] = s[lenth-(m-k)];
			k = lenth-(m-k);	
		}
		else
		{
			s[k] = s[n-(lenth-k)];
			k = n-(lenth-k);	
		}
		num++;
	}
	s[k] = b; 
	cout<<s;
}

  

算法分析:k的取值顺序为0->3->6->2->5->1->4,把S中所有的元素都遍历更新了一遍,时间复杂度为O(length),空间复杂度为O(1),而且只需要进行n+1次转移操作,与指针翻转法、递归转换法这些方法相比,有一定的优势。加入对于所有的长度为length的字符串,进行左旋n位操作之后,那么k是不是可以遍历完S中所有的元素呢?这个就需要进行验证了,为此,在此做了一下测试,输入length 和 n,输出k的遍历顺序:

void left1(int lenth, int n)
{
	int k,m;
	char b;
	n = n%lenth;//当n>length的时候,取模
	k = 0;
	m = lenth-n;
	int num = 0; 
	k = 0;
	cout<<k<<" ";
	while(num<(lenth-1))
	{   
		if(k<lenth-n)
		{
			k = lenth-(m-k);	
		}
		else
		{
			k = n-(lenth-k);	
		}
		cout<<k<<" ";
		num++;
	}
	cout<<endl;
}
int main()
{
	int lenth,n;
	cout<<"lenth=";
	cin>>lenth;
	cout<<"n=";
	cin>>n;
	
	cout<<"遍历的顺序依次为:"<<endl;
	left1(lenth,n);
	return 0;
}

一些测试如下:

技术分享技术分享

技术分享技术分享

 问题出现了,果然就像之前所说的那样,k不一定能够遍历完S中所有的元素,出现了循环。为此,又查看了July的博客(http://blog.csdn.net/v_JULY_v/article/details/6322882)。

技术分享

第一个和第二个测试实验,可以遍历完所有的元素,对于第三个和第四个,不可以遍历完所有的元素,那是因为length和n的最大公约数不是1,它们两个不是互质的;为此,输入数据之后,要判断一下length和n的最大公约数gcd(length,n)。对于第三个测试实验,当n=3的时候,那么,猜测会出现3个环,因为0->3->6->9->12,这里只有五个元素,因此,还有两个这样子的环;为此,需要对算法进行改进,当回到原点的时候,要对k进行加1,目测这样子可以可以遍历完S中所有的元素。于是推出如下的改进方法:

int gcd(int m, int n)//返回值n是m和n的最大公约数
{
	int r;
	while(0 != m%n)
	{
		r = m%n;
		m = n;
		n = r;
	}
	return n;
}
void left3(int lenth, int n)
{
	int k,m,num;
	k = 0;
	m = lenth-n;
	n = n%lenth;
	int gcd_n= gcd(lenth,n);
    if(1 == gcd_n)
	{
		int k,m,num;
		k = 0;
		m = lenth-n;
		num = 0;
		cout<<k<<" ";
		while(num<lenth-1)
		{
			if(k<m)
			{
				k = lenth - (m-k);
				cout<<k<<" ";
			}
			else
			{
				k = n - (lenth-k);
				cout<<k<<" ";
			}
			num++;
		}
		cout<<endl;
	}
	else
	{
		for(int kk=0;kk<gcd_n;kk++)
		{
			k = kk;
			int num = 0;
			cout<<kk<<" ";
			while(num<(lenth/gcd_n-1))
			{   
				if(k<m)
				{
					k = lenth-(m-k);
					cout<<k<<" ";
				}
				else
				{
					k = n-(lenth-k);
					cout<<k<<" ";
				}
				num++;
			}
		}
		cout<<endl;
	}
}

  

一些实验结果:

技术分享技术分享

技术分享技术分享

第一行的结果是上一个版本的,第二行的结果是改进之后的版本的,这次终于没有问题了;

最后,改进的左旋字符串代码如下:

int gcd(int m, int n)//返回值n是m和n的最大公约数
{
	int r;
	while(0 != m%n)
	{
		r = m%n;
		m = n;
		n = r;
	}
	return n;
}
void left_mov5(char s[],int lenth, int n)
{
	int k,m;
	char b;
	n = n%lenth;//avoid that lenth < n
	k = 0;
	b = s[0];
	m = lenth-n;
	int gcd_n= gcd(lenth,n);
    if(1 == gcd_n)
	{
		int num = 0; 
		k = 0;
		while(num<(lenth-1))
		{   
			if(k<m)
			{
				s[k] = s[lenth-(m-k)];
				k = lenth-(m-k);
				
			}
			else
			{
				s[k] = s[n-(lenth-k)];
				k = n-(lenth-k);
				
			}
			num++;
		}
		s[k] = b; 
	}
	else 
	{
		for(int kk=0;kk<gcd_n;kk++)
		{
			k = kk;
			int num = 0;
			b = s[k];
			cout<<"b(";
			cout<<kk<<")"<<" ";
			while(num<(lenth/gcd_n-1))
			{   
				if(k<m)
				{
					cout<<k<<"(";
					s[k] = s[lenth-(m-k)];
					k = lenth-(m-k);
					cout<<k<<")"<<" ";
				}
				else
				{
					cout<<k<<"(";
					s[k] = s[n-(lenth-k)];
					k = n-(lenth-k);
					cout<<k<<")"<<" ";
				}
				num++;
			}
			s[k] = b; 
			cout<<k<<"(b) ";
		}
	}
	cout<<endl;
	cout<<"The left_mov string a is  :";
	cout<<s;
}
int main()
{
	const int Max = 100;
	int n,lenth=0;
	char a[40];
	cout<<"Please input the string a :";
	cin>>a;
	while(NULL != a[lenth]) lenth++;
	cout<<"The lenth of string a is : "<<lenth<<endl;;
	cout<<"Please input the number n :";
	cin>>n;
	left_mov5(a,lenth,n);
	cout<<endl;
	return 0;
}

一些实验结果:

技术分享

技术分享

技术分享

这次终于成功了,噢耶!

技术分享

左旋转字符串

标签:

原文地址:http://www.cnblogs.com/luoenhu/p/4588461.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!