全排列算法

問題描述:

對于一個給定的序列 a = [a1, a2, a3, … , an],請設(shè)計一個算法,用于輸出這個序列的全部排列方式。
例如:a = [1, 2, 3]
輸出

123
132
213
231
321
312

基于遞歸方法:

思路講解:
(1) 一個元素的序列全排列是其本身
(2)對于一個n個元素的序列(沒有重復(fù)元素)而言,我們可以把其分解為n個子問題:即依次讓每個元素作為首元素,剩下的n-1個元素進行全排列。所以這是個遞歸問題。我們以三個元素的序列舉個例子:
[A0, A1, A2]的全排列等于下面三個全排列的子問題:
A0開頭,拼接上[A1,A2]序列的全排列
A1開頭,拼接上[A0,A2]序列的全排列
A2開頭,拼接上[A0,A1]序列的全排列
然后依次類推,[A1,A2]的全排列等于A1開頭,拼接上[A2]序列的全排列和A2開頭,拼接上[A1]序列的全排列……
代碼如下:

template <class T>
inline void Swap(T& a, T& b)
{
    T temp = a; a = b; b = temp;
}

template <class T>
void Permutation(T list[], int start, int end)
{
    // 只剩下一個元素,那么就是這個元素本身,并且該列表排序完成可以輸出
    if (start == end)
    {
        for (int i = 0; i < end; i++)
        {
            cout << list[i];
        }
        cout << endl;
    }
    // 否則,我們遍歷所有從start到end的元素作為第一個元素,剩余元素進行全排列
    else
    {
        for (int i = start; i < end; i++)
        {
            Swap(list[start], list[i]);
            Permutation(list, start + 1, end);
            Swap(list[start], list[i]);
        }
    }
}

int main()
{
    int my_list[3] = {1, 2, 3};
    Permutation(my_list, 0, 3);
}

輸出結(jié)果如下:

123
132
213
231
321
312
Program ended with exit code: 0

如果序列中有重復(fù)的元素,那么我們只需保證,在每個子問題中相同的重復(fù)元素只能做一次首元素就可以避免重復(fù)計算的情況。
代碼如下:

template <class T>
inline void Swap(T& a, T& b)
{
    T temp = a; a = b; b = temp;
}

template <class T>
bool whetherSwap(T list[], int start, int end)
{
    for (int i = start + 1; i < end; i++)
    {
        if (list[i] == list[start]) return false;
    }
    return true;
}

template <class T>
void Permutation(T list[], int start, int end)
{
    // 只剩下一個元素,那么就是這個元素本身,并且該列表排序完成可以輸出
    if (start == end)
    {
        for (int i = 0; i < end; i++)
        {
            cout << list[i];
        }
        cout << endl;
    }
    // 否則,我們遍歷所有從start到end的元素作為第一個元素,剩余元素進行全排列
    else
    {
        for (int i = start; i < end; i++)
        {
            // 如果list[i:end]子問題中,首元素list[i]和剩下部分list[i+1:end]中的元素有重復(fù)的,則不進行后面的交換和全排列
            // 否則進行后面的交換和全排列。這樣保證每個子問題中,重復(fù)元素只有一次作為首元素
            if (whetherSwap(list, i, end))
            {
                Swap(list[start], list[i]);
                Permutation(list, start + 1, end);
                Swap(list[start], list[i]);
            }
        }
    }
}

int main()
{
    int my_list[4] = {0, 0, 2, 0};
    Permutation(my_list, 0, 4);
}

輸出結(jié)果:

2000
0200
0020
0002
Program ended with exit code: 0

基于枚舉法(循環(huán)實現(xiàn))

我們先假設(shè)序列是沒有重復(fù)元素的。因此全排列的所有可能情況組成的序列大小各不相同?;诖?,我們想可以通過大小來枚舉所有的序列:
如果第i個排列是[A0, A1, A2, A3, ……],則第i+1個序列就是比這個序列[A0, A1, A2, A3, ……]大的所有序列中最小的那個。
算法如下(從小到大枚舉所有序列):
準備階段,為了初始數(shù)列是按照從小到大排列的,我們對序列進行排序(nLog(n)
(1)從后往前兩兩比較,找到第一個滿足a[i]<a[i+1]的兩個元素
(2)從a[i+1]開始往后找,找到一個大于a[i]中最小的一個元素,這個元素的下標記為j,交換a[i]和a[j]
(3)將a[i+1, a.length-1]的元素全部逆序

思路講解:
第一步,我們從后往前兩兩比較,找到第一個滿足前一個元素小于后一個元素的地方(其實這一步就保證了該地方后面是升序的。因為元素互異,所以是嚴格升序的。這個對于后面全部反轉(zhuǎn)逆序的理解很關(guān)鍵)。
第二步,從a[i+1]開始往后找,找到一個大于a[i]中最小的一個元素,這個元素的下標記為j,交換a[i]和a[j]。這時,因為原本后面是嚴格升序的,且找到的元素又是大于a[i]中最小的一個。所以被調(diào)換的這個元素a[j]肯定大于其后面的元素,并且后面的那個元素肯定小于或等于a[i]的(不然,a[j]就不是大于a[i]的元素中最小的一個)。因此,此處可以得出調(diào)換過后,從a[i+1]開始到末尾還是嚴格升序的。
第三步,由上面可知,后面元素還是嚴格升序排列的,所以我們需要將其反轉(zhuǎn)得到全排列所有序列中比上一個序列大且是最小的一個序列。

從上面我們可以知道,其實有重復(fù)元素是一樣的。因為在第二步中找大于a[i]中最小的一個,如果有重復(fù)元素在,總是會找到最后一個并記錄下它的index為j。所以交換過后依然保證后面是升序的。則保證了最后一步反轉(zhuǎn)的正確性。
代碼如下:

template <class T>
inline void Swap(T list[], int a, int b)
{
    T temp = list[a]; list[a] = list[b]; list[b] = temp;
}

template <class T>
void arrayReverse(T list[], int start, int end)
{
    int i = start;
    int j = end;
    while(j > i){
        Swap(list, i, j);
        i++;
        j--;
    }
}

template <class T>
bool Next(T list[], int len)
{
    bool exist_next = true;
    for (int i = len - 1; i > 0; i--)
    {
        // 從后往前兩兩比較,找到第一對前面元素大于后面元素的地方
        if (list[i - 1] < list[i])
        {
            int index_swap = i;
            for (int j = i + 1; j < len; j++)
            {
                if (list[j] > list[i - 1])
                {
                    index_swap = j;
                }
                else if (list[j] < list[i - 1])
                {
                    index_swap = j - 1;
                    break;
                }
            }
            Swap(list, i-1, index_swap);
            arrayReverse(list, i, len - 1);
            return exist_next;
        }
    }
    exist_next = false;
    return exist_next;
}

// 此函數(shù)需要傳入排序好的list(從小到大)
template <class T>
void Permutation(T list[], int len)
{
    for (int i = 0; i < len; i++)
    {
        cout << list[i];
    }
    cout << endl;
    
    while(Next(list, len))
    {
        for (int i = 0; i < len; i++)
        {
            cout << list[i];
        }
        cout << endl;
    }
}


int main()
{
    int my_list[3] = {0, 1, 2};
    Permutation(my_list, 3);
}

結(jié)果如下:

012
021
102
120
201
210
Program ended with exit code: 0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 問題:輸入一個字符串,打印出該字符串中字符的所有排列。例如輸入字符串a(chǎn)bc,則輸出由字符a,b,c所能排列出來的所...
    方法一君閱讀 612評論 0 0
  • 問題: 輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串a(chǎn)bc,則打印出由字符a,b,...
    光影墨辰閱讀 453評論 0 2
  • 各校歷年復(fù)試機試試題 清華、北大、華科試題詳細筆記部分,少筆記部分與少數(shù)leetcode【含個人整理筆記】 一、詳...
    AIM外星人閱讀 1,328評論 0 1
  • 這清明三天假期第一天,雨水竟也紛紛,真?zhèn)€應(yīng)了古人之景象——清明時節(jié)雨紛紛,路上行人欲斷魂。但回鄉(xiāng)祭祖掃墓的我,總也...
    鄧阿林閱讀 267評論 0 1
  • 體育:1,安全。2,體育記錄本。3,集體意識。4,體育考試:50米和跳繩占比40%,體育記錄本20%,體能測試30...
    芳芳Mevius閱讀 261評論 0 0

友情鏈接更多精彩內(nèi)容