左旋转字符串

转自:程序员编程艺术第一章、左旋转字符串


主要为本人收藏学习用


大家开始可能会有这样的潜在假设,K<N。事实上,很多时候也的确是这样的。但严格来说,我们不能用这样的“惯性思维”来思考问题。
尤其在编程的时候,全面地考虑问题是很重要的,K可能是一个远大于N的整数,在这个时候,上面的解法是需要改进的。
仔细观察循环右移的特点,不难发现:每个元素右移N位后都会回到自己的位置上。因此,如果K > N,右移K-N之后的数组序列跟右移K位的结果是一样的。

进而可得出一条通用的规律:
右移K位之后的情形,跟右移K’= K % N位之后的情形一样



还有就是 K' > N/2,的话  

     左移K' 等于 右移 N-K';




2.1、在此,本人再奉献另外一种思路,即为本思路二
abc defghi,要abc移动至最后
abc defghi->def abcghi->def ghiabc

定义俩指针,p1指向ch[0],p2指向ch[m];
一下过程循环m次,交换p1和p2所指元素,然后p1++, p2++;。

第一步,交换abc 和def ,
abc defghi->def abcghi
第二步,交换abc 和 ghi,
def abcghi->def ghiabc

整个过程,看起来,就是abc 一步一步 向后移动
abc defghi
def abcghi
def ghi abc  
  //最后的 复杂度是O(m+n)  

以下是朋友颜沙针对上述过程给出的图解:

2.2、各位读者注意了:

    由上述例子九个元素的序列abcdefghi,您已经看到,m=3时,p2恰好指到了数组最后一个元素,于是,上述思路没有问题。但如果上面例子中i 的后面还有元素列?

    即,如果是要左旋十个元素的序列:abcdefghij,ok,下面,就举这个例子,对abcdefghij序列进行左旋转操作:

如果abcdef ghij要变成defghij abc:
  abcdef ghij
1. def abc ghij
2. def ghi abc j      //接下来,j 步步前移
3. def ghi ab jc
4. def ghi a j bc
5. def ghi j abc 

 下面,再针对上述过程,画个图清晰说明下,如下所示:

  ok,咱们来好好彻底总结一下此思路二(就4点,请仔细阅读)

1、首先让p1=ch[0]p2=ch[m],即让p1p2相隔m的距离;

2、判断p2+m-1是否越界,如果没有越界转到3,否则转到4(abcdefgh这8个字母的字符串,以4左旋,那么初始时p2指向e,p2+4越界了,但事实上p2至p2+m-1是m个字符,可以再做一个交换)

3、不断交换*p1*p2,然后p1++p2++,循环m次,然后转到2

4、此时p2+m-1 已经越界,在此只需处理尾巴。过程如下:

   4.1 通过n-p2得到p2与尾部之间元素个数r,即我们要前移的元素个数。

   4.2 以下过程执行r次:

       ch[p2]<->ch[p2-1],ch[p2-1]<->ch[p2-2]....ch[p1+1]<->ch[p1]p1++p2++

(特别感谢tctop组成员big的指正,tctop组的修订wiki页面为:http://tctop.wikispaces.com/

 

    所以,之前最初的那个左旋转九个元素abcdefghi的思路在末尾会出现问题的(如果p2后面有元素就不能这么变,例如,如果是处理十个元素,abcdefghij 列?对的,就是这个意思),解决办法有两个:

方法一(即如上述思路总结所述):
def ghi abc jk
当p1指向a,p2指向j时,由于p2+m越界,那么此时p1,p2不要变
这里p1之后(abcjk)就是尾巴,处理尾巴只需将j,k移到abc之前,得到最终序列,代码编写如下:

  1. //copyright@July、颜沙  
  2. //最终代码,July,updated again,2011.04.17。  
  3. #include <iostream>  
  4. #include <string>  
  5. using namespace std;  
  6.   
  7. void rotate(string &str, int m)  
  8. {  
  9.       
  10.     if (str.length() == 0 || m <= 0)  
  11.         return;  
  12.       
  13.     int n = str.length();  
  14.       
  15.     if (m % n <= 0)  
  16.         return;  
  17.       
  18.     int p1 = 0, p2 = m;  
  19.     int k = (n - m) - n % m;  
  20.       
  21.     // 交换p1,p2指向的元素,然后移动p1,p2  
  22.     while (k --)   
  23.     {  
  24.         swap(str[p1], str[p2]);  
  25.         p1++;  
  26.         p2++;  
  27.     }  
  28.       
  29.     // 重点,都在下述几行。  
  30.     // 处理尾部,r为尾部左移次数  
  31.     int r = n - p2;  
  32.     while (r--)  
  33.     {  
  34.         int i = p2;  
  35.         while (i > p1)  
  36.         {  
  37.             swap(str[i], str[i-1]);  
  38.             i--;  
  39.         }  
  40.         p2++;  
  41.         p1++;  
  42.     }  
  43.     //比如一个例子,abcdefghijk  
  44.     //                    p1    p2  
  45.     //当执行到这里时,defghi a b c j k  
  46.     //p2+m出界 了,  
  47.     //r=n-p2=2,所以以下过程,要执行循环俩次。  
  48.       
  49.     //第一次:j 步步前移,abcjk->abjck->ajbck->jabck  
  50.     //然后,p1++,p2++,p1指a,p2指k。  
  51.     //               p1    p2  
  52.     //第二次:defghi j a b c k  
  53.     //同理,此后,k步步前移,abck->abkc->akbc->kabc。  
  54. }  
  55.   
  56. int main()     
  57. {     
  58.     string ch="abcdefghijk";     
  59.     rotate(ch,3);     
  60.     cout<<ch<<endl;     
  61.     return 0;        
  62. }    
   方法二:
def ghi abc jk
当p1指向a,p2指向j时,那么交换p1和p2,
此时为:
def ghi jbc ak

p1++,p2++,p1指向b,p2指向k,继续上面步骤得:
def ghi jkc ab

p1++,p2不动,p1指向c,p2指向b,p1和p2之间(cab)也就是尾巴,
那么处理尾巴(cab)需要循环左移一定次数(而后的具体操作步骤已在下述程序的注释中已详细给出)。

    根据方案二,不难写出下述代码(已测试正确):

  1. #include <iostream>  
  2. #include <string>  
  3. using namespace std;  
  4.   
  5. //颜沙,思路二之方案二,  
  6. //July、updated,2011.04.16。  
  7. void rotate(string &str, int m)  
  8. {  
  9.     if (str.length() == 0 || m < 0)  
  10.         return;  
  11.   
  12.     //初始化p1,p2  
  13.     int p1 = 0, p2 = m;     
  14.     int n = str.length();  
  15.   
  16.     // 处理m大于n  
  17.     if (m % n == 0)  
  18.         return;  
  19.       
  20.     // 循环直至p2到达字符串末尾  
  21.     while(true)  
  22.     {    
  23.         swap(str[p1], str[p2]);  
  24.         p1++;  
  25.         if (p2 < n - 1)  
  26.             p2++;  
  27.         else  
  28.             break;  
  29.     }  
  30.       
  31.     // 处理尾部,r为尾部循环左移次数  
  32.     int r = m - n % m;  // r = 1.  
  33.     while (r--)  //外循环执行一次  
  34.     {  
  35.         int i = p1;  
  36.         char temp = str[p1];  
  37.         while (i < p2)  //内循环执行俩次  
  38.         {  
  39.             str[i] = str[i+1];  
  40.             i++;  
  41.         }     
  42.         str[p2] = temp;  
  43.     }  
  44.     //举一个例子  
  45.     //abcdefghijk  
  46.     //当执行到这里的时候,defghiabcjk  
  47.     //      p1    p2  
  48.     //defghi a b c j k,a 与 j交换,jbcak,然后,p1++,p2++  
  49.     //        p1    p2  
  50.     //       j b c a k,b 与 k交换,jkcab,然后,p1++,p2不动,  
  51.       
  52.     //r = m - n % m= 3-11%3=1,即循环移位1次。  
  53.     //          p1  p2  
  54.     //       j k c a b  
  55.     //p1所指元素c实现保存在temp里,  
  56.     //然后执行此条语句:str[i] = str[i+1]; 即a跑到c的位置处,a_b  
  57.     //i++,再次执行:str[i] = str[i+1],ab_  
  58.     //最后,保存好的c 填入,为abc,所以,最终序列为defghi jk abc。  
  59.     //July、updated,2011.04.17晚,送走了她。  
  60. }  
  61.   
  62. int main()  
  63. {  
  64.     string ch="abcdefghijk";  
  65.     rotate(ch,3);  
  66.     cout<<ch<<endl;  
  67.     return 0;     
  68. }  

注意:上文中都是假设m<n,且如果鲁棒点的话令m=m%n,这样m允许大于n。另外,各位要记得处理指针为空的情况。      

 还可以看下这段代码:

  1. /* 
  2.  * myinvert2.cpp 
  3.  * 
  4.  *  Created on: 2011-5-11 
  5.  *      Author: BigPotato 
  6.  */  
  7. #include<iostream>  
  8. #include<string>  
  9. #define positiveMod(m,n) ((m) % (n) + (n)) % (n)  
  10.   
  11. /* 
  12.  *左旋字符串str,m为负数时表示右旋abs(m)个字母 
  13.  */  
  14. void rotate(std::string &str, int m) {  
  15.     if (str.length() == 0)  
  16.         return;  
  17.     int n = str.length();  
  18.     //处理大于str长度及m为负数的情况,positiveMod可以取得m为负数时对n取余得到正数  
  19.     m = positiveMod(m,n);  
  20.     if (m == 0)  
  21.         return;  
  22.     //    if (m % n <= 0)  
  23.     //        return;  
  24.     int p1 = 0, p2 = m;  
  25.     int round;  
  26.     //p2当前所指和之后的m-1个字母共m个字母,就可以和p2前面的m个字母交换。  
  27.     while (p2 + m - 1 < n) {  
  28.         round = m;  
  29.         while (round--) {  
  30.             std::swap(str[p1], str[p2]);  
  31.             p1++;  
  32.             p2++;  
  33.         }  
  34.     }  
  35.     //剩下的不足m个字母逐个交换  
  36.     int r = n - p2;  
  37.     while (r--) {  
  38.         int i = p2;  
  39.         while (i > p1) {  
  40.             std::swap(str[i], str[i - 1]);  
  41.             i--;  
  42.         }  
  43.         p2++;  
  44.         p1++;  
  45.     }  
  46. }  
  47.   
  48. //测试  
  49. int main(int argc, char **argv) {  
  50.     //    std::cout << ((-15) % 7 + 7) % 7 << std::endl;  
  51.     //    std::cout << (-15) % 7 << std::endl;  
  52.     std::string ch = "abcdefg";  
  53.     int len = ch.length();  
  54.     for (int m = -2 * len; m <= len * 2; m++) {  
  55.         //由于传给rotate的是string的引用,所以这里每次调用都用了一个新的字符串  
  56.         std::string s = "abcdefg";  
  57.         rotate(s, m);  
  58.         std::cout << positiveMod(m,len) << ": " << s << std::endl;  
  59.     }  
  60.    
  61.     return 0;  
  62. }  

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值