算法設計-全排列遞歸

排列:從n個元素中任取m個元素,並按照必定的順序進行排列,稱爲排列;ios

全排列:當n==m時,稱爲全排列;程序員

好比:集合{ 1,2,3}的全排列爲:面試

{ 1 2 3}算法

{ 1 3 2 }windows

{ 2 1 3 }數組

{ 2 3 1 }函數

{ 3 2 1 }spa

{ 3 1 2 }.net

咱們能夠將這個排列問題畫成圖形表示,即排列枚舉樹,好比下圖爲{1,2,3}的排列枚舉樹,此樹和咱們這裏介紹的算法徹底一致;設計

算法思路:

(1)n個元素的全排列=(n-1個元素的全排列)+(另外一個元素做爲前綴);

(2)出口:若是隻有一個元素的全排列,則說明已經排完,則輸出數組;

(3)不斷將每一個元素放做第一個元素,而後將這個元素做爲前綴,並將其他元素繼續全排列,等到出口,出口出去後還須要還原數組;

這裏先把集合中的元素理解爲不會出現重複了,那麼實現的方法(C++)以下:

成員管理,互評,文件共享,事務通知
#include <iostream>
using namespace std;

int sum = 0;//記錄有多少種組合

void Swap(char str[], int a, int b)
{
    char temp = str[a];
    str[a] = str[b];
    str[b] = temp;
}

void Perm(char str[], int begin, int end)
{
    if (begin == end)
    {
        for (int i = 0; i <= end; i++)
        {
            cout << str[i];
        }
        cout << endl;
        sum++;
        return;
    }
    else
    {
        for (int j = begin; j <= end; j++)
        {
            Swap(str, begin, j);
            Perm(str, begin + 1, end);
            Swap(str, j, begin);
        }
    }
}

int main()
{
    int n;
    char c[16];
    char tmp;


    cin >> n;
    tmp = getchar();    // 接受回車
    if (1 <= n && n <= 15) {
        for (int i = 0; i < n; i++) {
            c[i] = getchar();
        }
        Perm(c, 0, n - 1);
    }
    cout << sum;
    cout << endl;
    return 0;
}

實現後效果以下圖:

1

有重複元素的排列問題

而後如今的題目要求是排列中的元素是包含相同元素的,給定n以及待排的n個可能重複的元素。計算輸出n個元素的全部不一樣排列,所以上面那個算法顯然仍是不夠好,由於相同的元素都當成不一樣的元素,所以有了重複的排列在裏面

去掉重複符號的全排列:在交換以前能夠先判斷兩個符號是否相同,不相同才交換,這個時候須要一個判斷符號是否相同的函數。也就是下面的IsSwap();

對122,第一個數1與第二個數2交換獲得212,而後考慮第一個數1與第三個數2交換,此時因爲第三個數等於第二個數,因此第一個數再也不與第三個數交換。再考慮212,它的第二個數與第三個數交換能夠獲得解決221。

去掉重複的規則:去重的全排列就是從第一個數字起每一個數分別與它後面非重複出現的數字交換。

#include <iostream>
using namespace std;

int sum=0;//記錄有多少種組合

void Swap(char str[], int a, int b)
{
    char temp = str[a];
    str[a] = str[b];
    str[b] = temp;
}

bool IsSwap(char *pchar, int nBegin, int nEnd)
{
    for (int i = nBegin; i < nEnd; i++)
        if (pchar[i] == pchar[nEnd])
            return false;
    return true;
}

void Perm(char str[], int begin, int end)
{
    if (begin==end)
    {
        for (int i = 0; i <= end; i++)
        {
            cout << str[i];
        }
        cout << endl;
        sum++;
        return;
    }
    else
    {
        for (int j = begin; j <= end; j++)
        {
            if (IsSwap(str, begin, j))
            {
                Swap(str, begin, j);
                Perm(str, begin + 1, end);
                Swap(str, j, begin);
            }
        }
    }
}

int main()
{
    int n;
    char c[16];
    char tmp;


    cin >> n;
    tmp = getchar();    // 接受回車
    if (1 <= n && n <= 15) {
        for (int i = 0; i < n; i++) {
            c[i] = getchar();
        }
        Perm(c, 0, n - 1);
    }
    cout << sum;
    cout << endl;
    return 0;
}

2

 

非遞歸的實現

實現思路:

要考慮全排列的非遞歸實現,先來考慮如何計算字符串的下一個排列。如"1234"的下一個排列就是"1243"。只要對字符串反覆求出下一個排列,全排列的也就迎刃而解了。

如何計算字符串的下一個排列了?來考慮"926520"這個字符串,咱們從後向前找第一雙相鄰的遞增數字,"20"、"52"都是非遞增的,"26 "即知足要求,稱前一個數字2爲替換數,替換數的下標稱爲替換點,再從後面找一個比替換數大的最小數(這個數必然存在),0、2都不行,5能夠,將5和2交換獲得"956220",而後再將替換點後的字符串"6220"顛倒即獲得"950226"。

對於像"4321"這種已是最「大」的排列,採用STL中的處理方法,將字符串整個顛倒獲得最「小」的排列"1234"並返回false。

//全排列的非遞歸實現
#include <iostream>
using namespace std;
void Swap(char *a, char *b)
{
    char t = *a;
    *a = *b;
    *b = t;
}
//反轉區間
void Reverse(char *a, char *b)
{
    while (a < b)
        Swap(a++, b--);
}
//下一個排列
bool Next_permutation(char a[])
{
    char *pEnd = a + strlen(a);
    if (a == pEnd)
        return false;
    char *p, *q, *pFind;
    pEnd--;
    p = pEnd;
    while (p != a)
    {
        q = p;
        --p;
        if (*p < *q) //找降序的相鄰2數,前一個數即替換數
        {
            //從後向前找比替換點大的第一個數
            pFind = pEnd;
            while (*pFind <= *p)
                --pFind;
            //替換
            Swap(pFind, p);
            //替換點後的數所有反轉
            Reverse(q, pEnd);
            return true;
        }
    }
    Reverse(p, pEnd);//若是沒有下一個排列,所有反轉後返回true
    return false;
}
int QsortCmp(const void *pa, const void *pb)
{
    return *(char*)pa - *(char*)pb;
}
int main()
{
    int sum = 0;
    char szTextStr[16];
    cin >> szTextStr;
    char tmp = getchar();    // 接受回車
    
    //加上排序
    qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp);
    int i = 1;
    cout << endl << endl << endl;
    do{
        cout<<szTextStr<<endl;
        sum++;
    } while (Next_permutation(szTextStr));

    cout << sum<<endl;
    return 0;
}

 

1

總結:

排列在筆試面試中很熱門,在百度和迅雷的校園招聘以及程序員和軟件設計師的考試中都考到了,所以瞭解全排列算法對咱們都頗有好處。也是算法的一個基本思想。遞歸算法的思路比較直,而非遞歸的就比較難去想到使用這種方法來實現。

1.全排列就是從第一個數字起每一個數分別與它後面的數字交換。

2.去重的全排列就是從第一個數字起每一個數分別與它後面非重複出現的數字交換。

3.全排列的非遞歸就是由後向前找替換數和替換點,而後由後向前找第一個比替換數大的數與替換數交換,最後顛倒替換點後的全部數據。

 

參考文獻:http://blog.csdn.net/morewindows/article/details/7370155/

相關文章
相關標籤/搜索