1. 程式人生 > >字串的全排列和組合演算法(遞迴非遞迴)

字串的全排列和組合演算法(遞迴非遞迴)

全排列在筆試面試中很熱門,因為它難度適中,既可以考察遞迴實現,又能進一步考察非遞迴的實現,便於區分出考生的水平。所以在百度和迅雷的校園招聘以及程式設計師和軟體設計師的考試中都考到了,因此本文對全排列作下總結幫助大家更好的學習和理解。對本文有任何補充之處,歡迎大家指出。
首先來看看題目是如何要求的(百度迅雷校招筆試題)。
一、字串的排列
用C++寫一個函式, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

一、全排列的遞迴實現

為方便起見,用123來示例下。123的全排列有123、132、213、231、312、321這六種。首先考慮213和321這二個數是如何得出的。顯然這二個都是123中的1與後面兩數交換得到的。然後可以將123的第二個數和每三個數交換得到132。同理可以根據213和321來得231和312。因此可以知道——全排列就是從第一個數字起每個數分別與它後面的數字交換。找到這個規律後,遞迴的程式碼就很容易寫出來了:

  1. #include<iostream>
  2. usingnamespace std;  
  3. #include<assert.h>
  4. void Permutation(char* pStr, char* pBegin)  
  5. {  
  6.     assert(pStr && pBegin);  
  7.     if(*pBegin == '\0')  
  8.         printf("%s\n",pStr);  
  9.     else
  10.     {  
  11.         for(char* pCh = pBegin; *pCh != '\0'; pCh++)  
  12.         {  
  13.             swap(*pBegin,*pCh);  
  14.             Permutation(pStr, pBegin+1);  
  15.             swap(*pBegin,*pCh);  
  16.         }  
  17.     }  
  18. }  
  19. int main(void)  
  20. {  
  21.     char str[] = "abc";  
  22.     Permutation(str,str);  
  23.     return 0;  
  24. }  

另外一種寫法:

  1. //k表示當前選取到第幾個數,m表示共有多少個數
  2. void
     Permutation(char* pStr,int k,int m)  
  3. {  
  4.     assert(pStr);  
  5.     if(k == m)  
  6.     {  
  7.         staticint num = 1;  //區域性靜態變數,用來統計全排列的個數
  8.         printf("第%d個排列\t%s\n",num++,pStr);  
  9.     }  
  10.     else
  11.     {  
  12.         for(int i = k; i <= m; i++)  
  13.         {  
  14.             swap(*(pStr+k),*(pStr+i));  
  15.             Permutation(pStr, k + 1 , m);  
  16.             swap(*(pStr+k),*(pStr+i));  
  17.         }  
  18.     }  
  19. }  
  20. int main(void)  
  21. {  
  22.     char str[] = "abc";  
  23.     Permutation(str , 0 , strlen(str)-1);  
  24.     return 0;  
  25. }  
如果字串中有重複字元的話,上面的那個方法肯定不會符合要求的,因此現在要想辦法來去掉重複的數列。
二、去掉重複的全排列的遞迴實現
由於全排列就是從第一個數字起每個數分別與它後面的數字交換。我們先嚐試加個這樣的判斷——如果一個數與後面的數字相同那麼這二個數就不交換了。如122,第一個數與後面交換得212、221。然後122中第二數就不用與第三個數交換了,但對212,它第二個數與第三個數是不相同的,交換之後得到221。與由122中第一個數與第三個數交換所得的221重複了。所以這個方法不行。

換種思維,對122,第一個數1與第二個數2交換得到212,然後考慮第一個數1與第三個數2交換,此時由於第三個數等於第二個數,所以第一個數不再與第三個數交換。再考慮212,它的第二個數與第三個數交換可以得到解決221。此時全排列生成完畢。
這樣我們也得到了在全排列中去掉重複的規則——去重的全排列就是從第一個數字起每個數分別與它後面非重複出現的數字交換。下面給出完整程式碼:

  1. #include<iostream>
  2. usingnamespace std;  
  3. #include<assert.h>
  4. //在[nBegin,nEnd)區間中是否有字元與下標為pEnd的字元相等
  5. bool IsSwap(char* pBegin , char* pEnd)  
  6. {  
  7.     char *p;  
  8.     for(p = pBegin ; p < pEnd ; p++)  
  9.     {  
  10.         if(*p == *pEnd)  
  11.             returnfalse;  
  12.     }  
  13.     returntrue;  
  14. }  
  15. void Permutation(char* pStr , char *pBegin)  
  16. {  
  17.     assert(pStr);  
  18.     if(*pBegin == '\0')  
  19.     {  
  20.         staticint num = 1;  //區域性靜態變數,用來統計全排列的個數
  21.         printf("第%d個排列\t%s\n",num++,pStr);  
  22.     }  
  23.     else
  24.     {  
  25.         for(char *pCh = pBegin; *pCh != '\0'; pCh++)   //第pBegin個數分別與它後面的數字交換就能得到新的排列   
  26.         {  
  27.             if(IsSwap(pBegin , pCh))  
  28.             {  
  29.                 swap(*pBegin , *pCh);  
  30.                 Permutation(pStr , pBegin + 1);  
  31.                 swap(*pBegin , *pCh);  
  32.             }  
  33.         }  
  34.     }  
  35. }  
  36. int main(void)  
  37. {  
  38.     char str[] = "baa";  
  39.     Permutation(str , str);  
  40.     return 0;  
  41. }  
OK,到現在我們已經能熟練寫出遞迴的方法了,並且考慮了字串中的重複資料可能引發的重複數列問題。那麼如何使用非遞迴的方法來得到全排列了?

三、全排列的非遞迴實現
要考慮全排列的非遞迴實現,先來考慮如何計算字串的下一個排列。如"1234"的下一個排列就是"1243"。只要對字串反覆求出下一個排列,全排列的也就迎刃而解了。
如何計算字串的下一個排列了?來考慮"926520"這個字串,我們從後向前找第一雙相鄰的遞增數字,"20"、"52"都是非遞增的,"26 "即滿足要求,稱前一個數字2為替換數,替換數的下標稱為替換點,再從後面找一個比替換數大的最小數(這個數必然存在),0、2都不行,5可以,將5和2交換得到"956220",然後再將替換點後的字串"6220"顛倒即得到"950226"。
對於像“4321”這種已經是最“大”的排列,採用STL中的處理方法,將字串整個顛倒得到最“小”的排列"1234"並返回false。

這樣,只要一個迴圈再加上計算字串下一個排列的函式就可以輕鬆的實現非遞迴的全排列演算法。按上面思路並參考STL中的實現原始碼,不難寫成一份質量較高的程式碼。值得注意的是在迴圈前要對字串排序下,可以自己寫快速排序的程式碼(請參閱《白話經典演算法之六 快速排序 快速搞定》),也可以直接使用VC庫中的快速排序函式(請參閱《使用VC庫函式中的快速排序函式》)。下面列出完整程式碼:

  1. #include<iostream>
  2. #include<algorithm>
  3. #include<cstring>
  4. usingnamespace std;  
  5. #include<assert.h>
  6. //反轉區間
  7. void Reverse(char* pBegin , char* pEnd)  
  8. {  
  9.     while(pBegin < pEnd)  
  10. 相關推薦

    字串排列組合演算法

    全排列在筆試面試中很熱門,因為它難度適中,既可以考察遞迴實現,又能進一步考察非遞迴的實現,便於區分出考生的水平。所以在百度和迅雷的校園招聘以及程式設計師和軟體設計師的考試中都考到了,因此本文對全排列作下總結幫助大家更好的學習和理解。對本文有任何補充之處,歡迎大家指出。

    排列組合演算法的C#語言實現

    using System; namespace Util.Comp { public class CombinationPermutation { public static void Main() { //全排列使用方法

    [收集]字串排列組合

    今天學習了一下何海濤部落格中的第28題,字串的排列問題,實際上指的是字串的全排列問題(排列和全排列還是有區別的吧)。思考並研究了這題之後就考慮了一下不同條件下其他類似的題的解法的編寫,兩部分來自於何海濤,其他來自於網路,此處做搬運和收集工作。分別從四個方面考慮:一、字串的全

    演算法-把n個數的每一種排列情況都列出來(排列組合)-排列-字典序演算法一看就懂

    首先需要介紹字典序演算法 比如 236541想找到下一個比它大的數 他有3個步驟 1.從最右邊開始找到第一組 左小於右的數 41 54 65 36 23 這樣找,很顯然,我們找到36就找到了,後面的就不用找了。 2.找到之後立刻交換嗎?不是的。定位了這個3以後,再從右邊開始

    基於C#的排列組合演算法

    using System;using System.Collections.Generic; namespace Algorithms{    public class PermutationAndCombination<T>    {        /// &l

    字串匹配的三個演算法KMP+字典樹+AC自動機

    字串匹配的意思是給一個字串集合,和另一個字串集合,看這兩個集合交集是多少。 (1)若是都只有一個字串,那麼就看其中一個是否包含另外一個(一對一,KMP) https://blog.csdn.net/fkyyly/article/details/48007965 (2)若是父串集合(比較長

    排列的不同方式STL演算法

    #include<iostream> #include<cstdlib> #include<algorithm> #include<iomanip> #include<functional> #include<iterator>

    字串排列所有組合問題及打靶問題

    轉自:https://blog.csdn.net/a1937935900/article/details/77103955 問題1 :輸入一個字串,打印出該字串中字元的所有排列。例如輸入字串abc,則輸出由字元a、b、c所能排列出來的所有字串abc、acb、bac、bca、cab和cba。

    字串排列組合實現-Java版

    排列組合演算法用途廣泛, 需要掌握, 為降低門檻, 本文主要關注演算法的邏輯和簡易性, 未重視演算法效率. 結合網路書本上的實現和自己的需求, 這裡列有四個目標: 1. 所有元素的全排列: ab的全排列是ab, ba(順序相關); 2. 所有元素的全組合:

    回溯演算法 貪心演算法排列

    一:簡介 (1)回溯法 又稱試探法 回溯法的基本做法是深度優先搜尋,是一種組織得井井有條的、能避免不必要重複搜尋的窮舉式搜尋演算法;基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。 適用場景:當遇到某一類問題時,它的問題可以分解,但是又不能得出明確的動態

    字串排列組合

    全排列: 主要思想:將字串第一個字元依次與後面字元交換,然後進行遞迴交換 在存在重複字元時需要加一個判斷,判斷之前是否交換過此字元。 bool isSwap(string str, int begin, int end) { for (int i = be

    全網最簡潔排列源代碼

    全排列遞歸整體思路為#include<stdio.h>#include<string.h>void f(char* s,int k){ for(int i=k;i<strlen(s);i++){char t=s[k];s[k]=s[i];s[i]=t;f(s,k+1);cha

    排列組合實現

    .html 有意義 per more tro 包含 方法 循環 -s 記得@老趙之前在微博上吐槽說,“有的人真是毫無長進,六年前某同事不會寫程序輸出全排列,昨天發郵件還是問我該怎麽寫,這時間浪費到我都看不下去了。” 那時候就很好奇全排列到底是什

    ——以排列n皇后問題舉例

    筆記來自【晴神寶典】 一、遞迴 遞迴 就在於反覆呼叫自身函式,但是每次都把問題範圍縮小,直到範圍可以縮小到可以直接得到邊界資料的結果,然後在返回路上求出對應的解。以上可看出,遞迴很適合用來實現分治思想。 遞迴兩個很重要的組成組成: 1、遞迴邊界(出口); 2、遞迴式

    字串排列效能分析Java版

    具體的思路我就不寫了,借用網上的一張圖來表示: 我的程式碼如下: import java.util.*; public class Solution { public ArrayList<String> Permutation(String str

    整型陣列處理演算法十三求出用1,2,5這三個數不同個數組合為100的組合個數華為校園招聘題

    寫一個程式, 要求功能:求出用1,2,5這三個數不同個數組合的和為100的組合個數。 如:100個1是一個組合,5個1加19個5是一個組合。。。。 請用C++語言寫。        下面用2中方法來

    java字串排列問題經典

    *原題如下:用1、2、2、3、4、6這六個數字,用java寫一個main函式,打印出所有不同的排列, *如:612234、412346等,要求:”4”不能在第三位,”3”與”6”不能相連. * *1把問題歸結為圖結構的遍歷問題。實際上6個數字就是六個結點,

    劍指offer-字串排列有重複值

    一、問題描述 輸入一個字串,按字典序打印出該字串中字元的所有排列。例如輸入字串abc,則打印出由字元a,b,c所能排列出來的所有字串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。  輸入描述: 輸入一個字串,長度不超過9(可能有字元重複),字元只包

    劍指Offer.38 字串排列包含重複

    題目給定字串“abca”輸出其全部排列。分析:package 劍指Offer; import java.util.ArrayList; import java.util.List; public c

    java排列組合演算法M選N

    從M長度的容器中每次選取N個元素 舉例,從(a,b,c,d,e)中取兩個元素,則為(ab) (ac) (ad)(ae)(bc)(bd)(be)(cd)(ce)(de) import java.util.ArrayList; import java.uti