字符串全排列算法

問題

輸入一個字符串,打印出該字符串中字符的所有排列。例如輸入字符串abc,則輸出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。
思路
這是典型的遞歸求解問題,遞歸算法有四個特性:
必須有可達到的終止條件,否則程序陷入死循環
子問題在規模上比原問題小
子問題可通過再次遞歸調用求解
子問題的解應能組合成整個問題的解

對於字符串的排列問題:
如果能生成n-1個元素的全排列,就能生成n個元素的全排列。對於只有一個元素的集合,可以直接生成全排列。所以全排列的遞歸終止條件很明確,只有一個元素時。我們可以分析一下全排列的過程:
首先,我們固定第一個字符a,求後面兩個字符bc的排列
當兩個字符bc排列求好之後,我們把第一個字符a和後面的b交換,得到bac,接着我們固定第一個字符b,求後面兩個字符ac的排列
現在是把c放在第一個位置的時候了,但是記住前面我們已經把原先的第一個字符a和後面的b做了交換,爲了保證這次c仍是和原先處在第一個位置的a交換,我們在拿c和第一個字符交換之前,先要把b和a交換回來。在交換b和a之後,再拿c和處於第一位置的a進行交換,得到cba。我們再次固定第一個字符c,求後面兩個字符b、a的排列
既然我們已經知道怎麼求三個字符的排列,那麼固定第一個字符之後求後面兩個字符的排列,就是典型的遞歸思路了

也就是固定一個字符串之後,之後再將問題變小,只需求出後面子串的排列個數就可以得出結果,當然第一時間想到的就是遞歸的算法了。

下面這張圖很清楚的給出了遞歸的過程:
在這裏插入圖片描述
很明顯,遞歸的出口,就是隻剩一個字符的時候,遞歸的循環過程,就是從每個子串的第二個字符開始依次與第一個字符交換,然後繼續處理子串。

還有一個問題要注意,就是如果字符串中有重複的字符串

由於全排列就是從第一個數字起,每個數分別與它後面的數字交換,我們先嚐試加個這樣的判斷——如果一個數與後面的數字相同那麼這兩個數就不交換 了。例如abb,第一個數與後面兩個數交換得bab,bba。然後abb中第二個數和第三個數相同,就不用交換了。但是對bab,第二個數和第三個數不 同,則需要交換,得到bba。由於這裏的bba和開始第一個數與第三個數交換的結果相同了,因此這個方法不行。

換種思維,對abb,第一個數a與第二個數b交換得到bab,然後考慮第一個數與第三個數交換,此時由於第三個數等於第二個數,所以第一個數就不再用與第三個數交換了。再考慮bab,它的第二個數與第三個數交換可以解決bba。此時全排列生成完畢!

這樣,我們得到在全排列中去掉重複的規則:
去重的全排列就是從第一個數字起,每個數分別與它後面非重複出現的數字交換
所以代碼如下

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<string> res;
void swap(string& s,int a,int b){
    char pre=s[a];
    s[a]=s[b];
    s[b]=pre;
}
void permutation_process(string s,int begin,int end){
    int k;
    if(begin==end-1){
        res.push_back(s);
    }
    else{
        for(k=begin;k<end;k++){
            swap(s,k,begin);
            permutation_process(s,begin+1,end);
            swap(s,k,begin);
        }
    }
}
int main(){
    string s;
    cin>>s;
    int l=s.size();
    permutation_process(s,0,l);
    int i;
    for(i=0;i<res.size();i++)
    cout<<res[i]<<endl;
    return 0;    
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章