旋轉字符串算法由淺入深


Author:bakari     Date:2012.9.8

昨天在寫一個旋轉字符串的函數時,寫着寫着發現有好多種方法,最簡單的莫過於替換然后覆蓋再插入。不要小看這種小的算法,其實這其中蘊含着很多容易忽略的編程的細節。下面就跟隨着我的文字來由淺入深進行鞏固和再學習。總結下來此問題的算法大約有五個,這是在分得很細的情況下,前面的兩個是自己想的,后面的三個參考了一個叫July的大神的思路。其實這些算法總體的思路大同小異,但這些細節問題也讓我的思維有了很大的開闊。下面就由淺入深一一分析:

 

思路一:

此思路是最容易想到的,就是進行簡單的替換,覆蓋和插入操作。不好描述,直接見代碼:其中需要注意的地方都已標注出來。

 

 1 /*   思路一:正常思路,循環左移
 2  *   注意K的處理,K有可能比N大,K 等價於 K %= N;
 3  *   算法的時間復雜度為O(N^2);
 4  */
 5 void RightShift(char * pArr, int N, int K)
 6 {
 7     assert(NULL != pArr);      //斷言判斷
 8     if(NULL == pArr)
 9         return;
10 
11     K %= N;          //K有可能比N大,考慮周到了
12     while(K--){
13         char pTemp = pArr[0];
14         for(int i = 0; i < N; i ++){
15             pArr[i] = pArr[i + 1];
16         }
17         pArr[N - 1] = pTemp;
18     }
19 }

 

當然你也可以C++的String庫來寫,建議以后編程多用C++的string庫,至少不會出現(char *)中出現的很多令人蛋疼的指針問題,不過各有各的好處,因人而異。

上面的思路最簡單,但時間復雜度卻不是很理想。下面是改進的算法,實現三次交換,而不是雙重循環。交換的時間復雜度是線性的。

 

思路二:

這個也是比較容易想到的,E.g:"abcd1234" ,將之分為兩部分,"abcd"和"1234",將兩者交換-->"dcba"和"4321",在對整體交換-->"1234abcd",OK!是不是很簡單,大部分人想到這里就應該會放棄了,包括我也是這樣,但解決問題的方式永遠不止一兩種,只有少部分人相信了這種話,所以,相信的現在都變大神了,大神July就是這樣的,下面的幾種思路保證讓你大開眼界,所以,以后思考問題應該多多抱着一種批判的思想,層層深入,如此方能鑿到金子。看思路二的代碼:

 

 1 /*    思路二:三次反轉
 2  *    e.g: "abcd1234"
 3  *    第一次反轉:"dcba",第二次反轉:"4321",第三次反轉:“1234abcd”
 4  *    算法的時間復雜度降到線性級為O(N);
 5  */
 6 void Reverse(char *pArr, int M, int N)    //反轉函數
 7  {
 8      //M、N代表字符串區域邊界上的兩個點
 9      while(M < N){
10         char pTemp = pArr[N];
11         pArr[N] = pArr[M];
12         pArr[M] = pTemp;
13         ++ M;
14         -- N;
15      }
16  }
17 //三次反轉
18  char * ThreeReverse(char * pArr, int N, int K)
19  {
20      K %= N;      //同樣對K進行處理
21      Reverse(pArr, 0, K - 1);
22      Reverse(pArr, N - K, N - 1);
23      Reverse(pArr, 0, N - 1);
24      return pArr;
25  }

 

上面N表示字符串的長度,K表示要循環移動的位數,注意對K的處理上,K有可能比N大,如果K == N,剛好回到原來的字符串,即沒有移動,所以,我們可以用K %= N來代替K,效果是一樣的。

 

思路三:

將所要旋轉的字符串當做一個整體,然后集體移動,如果是左循環,就進行右移動,右循環就左移動。舉個例子,E.g:“abcdefghijk”實行左循環,將“abc”移動最后,則有:

abcdefghijk” --> "defabcghijk" --> "defghiabcjk",到這里,就沒法再移動了,這個時候,剛好反過來,將"jk"前移 --> "defghijkabc",這其中會用到交換Swap函數,如下:

 

1 void Swap(char *pArr, int M, int N)    //交換函數
2 {
3     char pTemp = pArr[N];
4     pArr[N] = pArr[M];
5     pArr[M] = pTemp;
6 }

 

那么如何來控制待處理的串(如"abc")的移動呢?用兩個臨界指針不久解決了嗎,保證P2 - P1 = K即可,移動中要對P2進行判斷,如果(P2 + K - 1)超過了 N(串長),就停止。對於"abcdefghijk",停止時 P1-->'a' , P2 --> 'j',因為這個時候(P2 + K1 - 1)> N,控制P2的停止,這個地方有個小技巧,就是設一個變量 Index = (N - K) - (N % K),當Index == 0時,P2不在移動。這個很好理解,比判斷P2是否越界要好處理得多。見代碼:

 

 1 /*     思路三:將要循環左移的字符串當做一個整體(兩個指針控制),依次右移
 2   *     e.g:“abcdefghijk”,將abc移到最右邊-->"defghijkabc"
 3   *     第一次移動-->"defabcghijk",第二次移動-->"defghiabcjk"
 4   *     再將jk往前移K位-->"defghijkabc"
 5   *     算法的時間復雜度也是線性的
 6   */
 7 void pConReverseFirst(char *pArr, int N, int K)
 8 {
 9     assert(NULL != pArr);      //斷言判斷
10     if(NULL == pArr)
11         return;
12 
13     K %= N;
14     if(K == 0)
15         return;
16 
17     //將待處理的串往后移
18     int p1 = 0, p2 = K;
19     int pIndex = (N - K) - (N % K);    //小技巧:pIndex表示p2所能指示的最大區域
20 
21     while(pIndex --){
22         Swap(pArr, p1, p2);
23         ++ p1;
24         ++ p2;
25     }
26     
27     //將剩余的串往前移
28     int pR = N % K;   //計算剩余的單出來的數,將這些數統一向前移,pR也可以= N - p2;
29     while(pR --){
30         char pTemp = pArr[p2];
31         for(int i = p2; i > p1; i --)
32             pArr[i] = pArr[i - 1];
33         pArr[p1] = pTemp;
34         ++ p2;
35         ++ p1;
36     }
37 }

 

思路四:

前面部分的算法和思路三一樣,在后面剩余串的處理上,本思路是將待處理串中剩余的部分往后移,E.g:"abcdefghijk" -- > "defghiabcjk" -- > "defghi bc k" -- > "defghi j k c a b",將'c'往后移 -- > "defghijk abc"。見代碼:

 

 1 /*     思路四:和思路三一樣,只在后面多余數據的處理上不一樣,剛好和思路三相反
 2  *     只要 *p2 != '\0',就交換p1和p2;然后將前面多余的數單獨移到最后
 3  *     e.g:"defghiabcjk" --> "defghijkcab" --> "defghijkabc"
 4  *     同樣的時間復雜度為線性的
 5  */
 6  void pConReverseSecond(char * pArr, int N, int K)
 7  {
 8     assert(NULL != pArr);      //斷言判斷
 9     if(NULL == pArr)
10         return;
11 
12     K %= N;
13     if(K == 0)
14         return;
15 
16     int p1 = 0, p2 = K;
17     while(p2 < N){
18         Swap(pArr, p1,p2);
19         ++ p1;
20         ++ p2;
21     }
22 
23     int pR = K - (N % K);      //計算前面p1所指范圍內剩余的數,e.g:"defghijkcab"剩余'c'
24     while(pR --){
25         for(int i = p1; i < p2 - 1; i ++)
26             Swap(pArr, i, i + 1);
27     }
28  }

 

思路五:

和思路三前面部分的算法也是一樣的,后面的部分則采用遞歸處理。代碼中有說明,相見代碼:

 

 1 /*     思路五:遞歸求解,前面的思路和思路三是一樣的,只是對於后面的要遞歸處理
 2   *     e.g:"abcdefghijk" --> "defghiabcjk" 此時,對於"abcjk"
 3   *     N = K + N % K = 5; K = N % K = 2; 將"jk"左移 --> "ajkbc",此時,對於"ajk"
 4   *     N = K + N % K = 3; K = N % K = 1; 將'a' 右移 --> "jka";
 5   *     算法的時間復雜度也是線性的
 6   */
 7 void RecurReverse(char * pArr, int N, int K, int pHead, int pTail, bool pFlag)
 8 {
 9     /* pHead = 待處理的頭元素,pTail = 待處理的尾元素
10      * pFlag = 左循還是右循的標志
11      */
12          assert(NULL != pArr);      //斷言判斷
13     if(NULL == pArr)
14         return;
15 
16     K %= N;
17     if(pHead == pTail || K == 0)  //遞歸出口
18         return;
19 
20     //左循右移
21     if(pFlag == true){
22         int p1 = pHead, p2 = pHead + K;
23         int pLeft = N - K - (N % K);
24 
25         for(; pLeft > 0; -- pLeft, ++ p1, ++p2)
26             Swap(pArr, p1, p2);
27 
28         //遞歸,pFLag == FALSE
29         RecurReverse(pArr, K + N % K, N % K, p1, pTail, false);
30     }
31 
32     //右循左移
33     //p2指向最右邊第一個
34     else{
35         int p2 = pTail, p1 = pTail - K;
36         int pRight = N - K - (N % K);
37 
38         for(; pRight > 0; -- pRight, -- p1, -- p2)
39             Swap(pArr, p1, p2);
40         //遞歸,pFLag == TRUE
41         RecurReverse(pArr, K + N % K, N % K, p1, p2, true); //"ajk" , p1指向'a',p2指向'k'
42     }
43 }

 

OK,以上所有代碼都嚴格經過測試成立。

以上的算法思想,是非常低級的,一切沒有涉及數據結構的算法都是非常低級的算法,但這些算法或多或少在不同的程度上打開了我們的思維,對以后的學習會有很多的幫助。以上的代碼有好多種寫法,每個人的寫法都不一樣,關鍵是懂得這種思想,學會層層深入地思考問題。

轉載請注出處:http://www.cnblogs.com/bakari/archive/2012/09/09/2677155.html 謝謝!

 

 


我的公眾號 「Linux雲計算網絡」(id: cloud_dev),號內有 10T 書籍和視頻資源,后台回復 「1024」 即可領取,分享的內容包括但不限於 Linux、網絡、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎大家關注。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM