青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

非有序全排列生成算法

作者:goal00001111(高粱)

 

我曾經(jīng)寫過一篇《有序全排列生成算法》,介紹了五種生成有序全排列的方法,在該文的末尾,我計劃再寫一篇姊妹篇《非有序全排列生成算法》,由于各種原因,一直遲遲未動筆,前幾天學習數(shù)據(jù)結(jié)構(gòu)“棧”的時候,碰到一個有趣的問題“列車出棧序列”,其中有一種解法需要用到非有序全排列,所以決定先寫好本文,再總結(jié)該問題。

生成非有序全排列的算法很多,有普通遞歸算法,循環(huán)移位法,鄰位對換法,需要中介數(shù)的遞增進位排列生成算法,遞減進位排列生成算法和循環(huán)左移排列生成算法等。

我們先看最簡單的普通遞歸算法,它的原理是:

如果用P表示n個元素的排列,而Pi表示不包含元素i的排列,(i)Pi表示在排列Pi前加上前綴i的排列,那么,n個元素的排列可遞歸定義為:

如果n=1,則排列P只有一個元素i

如果n>1,則排列P由排列(i)Pi構(gòu)成(i=12....n-1)。

根據(jù)定義,容易看出如果已經(jīng)生成了k-1個元素的排列,那么,k個元素的排列可以在每個k-1個元素的排列Pi前添加元素i而生成。

例如2個元素的排列是 1 22 1,對每個元素而言,p12 33 2

在每個排列前加上1即生成1 2 31 3 2兩個新排列,p2p3則是1 33 11 22 1,按同樣方法可生成新排列2 1 32 3 13 1 23 2 1

主要代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:普通遞歸算法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(int n)

{

    int *a = new int[n];//用來存儲n個自然數(shù)

    for (int i=0; i<n; i++) //存儲全排列的元素值

        a[i] = i + 1;

   

    Recursion(a, n, n-1); //調(diào)用遞歸函數(shù)     

    delete []a;

}

 

/*

函數(shù)名稱:Recursion

函數(shù)功能:遞歸輸出n個數(shù)的所有全排列

輸入變量:int a[]:存儲了123...nn個自然數(shù)的數(shù)組

          int n:數(shù)組a[]的長度

          int k:正在處理的k個元素所組成的排列

輸出變量:無

*/

void Recursion(int a[], int n, int k)

{

    if (k == 0) //排列只有一個元素a[k],直接輸出

        Print(a, n);

    else

    {

        int temp;

        for (int i=0; i<=k; i++) //窮舉,依次讓第k個元素與前面的元素交換

        {

            temp = a[i];

            a[i] = a[k];

            a[k] = temp;

           

            Recursion(a, n, k-1); //遞歸生成k-1個元素的全排列

           

            temp = a[i]; //再換回來

            a[i] = a[k];

            a[k] = temp;

        }

    }

}

 

void Print(int a[], int n)

{

    for (int i=0; i<n; i++)

        cout << a[i];

    cout << endl;

}

 

循環(huán)移位法是一種很容易理解的非有序全排列算法,它的原理是:如果已經(jīng)生成了k-1個元素的排列,則在每個排列后添加元素k使之成為k個元素的排列,然后將每個排列循環(huán)左移(右移),每移動一次就產(chǎn)生一個新的排列。

例如2個元素的排列是1 22 1。在1 2 后加上3成為新排列1 2 3,將它循環(huán)左移可再生成新排列2 3 13 1 2

同樣2 1 可生成新排列2 1 31 3 23 2 1

代碼也和簡單,使用了遞歸窮舉思想,我生成了一個循環(huán)左移的算法,有興趣的讀者可以自己生成一個循環(huán)右移的算法。

/*

函數(shù)名稱:Permutation

函數(shù)功能:全排列循環(huán)移位法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(int n)

{

    unsigned int *a = new unsigned int[n];//用來存儲n個自然數(shù)

    for (int i=0; i<n; i++) //存儲全排列的元素值,并計算全排列的數(shù)量

    {

        a[i] = i + 1;

    }

   

    Recursion(a, n, 1);

   

    delete []a;

}

/*

函數(shù)名稱:Recursion

函數(shù)功能:循環(huán)左移遞歸輸出n個數(shù)的所有全排列

輸入變量:int a[]:存儲了123...nn個自然數(shù)的數(shù)組

          int n:數(shù)組a[]的長度

          int k:正在處理的k個元素所組成的排列

輸出變量:無

 

*/

void Recursion(unsigned int a[], int n, int k)

{

    if (k > n)

        Print(a, n);

    else

    {

        int temp;

        for (int i=0; i<k; i++)//循環(huán)左移

        {

            temp = a[0];

            for (int j=1; j<k; j++)

                a[j-1] = a[j];

            a[k-1] = temp;

            

            Recursion(a, n, k+1);

        }

    }

}

 

一個能夠快速生成全排列的算法叫做鄰位對換法,它之所以較快,是因為鄰位對換法中下一個排列總是上一個排列某相鄰兩位對換得到的,只需一步,就可以得到一個新的全排列,而且絕不重復,但是由于每將n從一端移動到另一端后,就需要遍歷排列2次,來尋找最大的可移數(shù)m,所以速度得到了限制。它的原理是:

[n]的全排列可由[n-1]的全排列生成:

給定[n-1]的一個排列п,n 由最右端依次插入排列п ,即得到n[n]的排列: 

p1 p2…pn-1n

p1 p2…npn-1

np1 p2…pn-1

對上述過程,一般地,對i,將前一步所得的每一排列重復i次,然后將i由第一排的最后往前移,至最前列,正好走了i,下一個接著將i放在下一排列的最前面,然后依次往后移,一直下去即得i元排列。

考慮{1,2n}的一個排列,其上每一個整數(shù)都給了一個方向,如果它的箭頭所指的方向的鄰點小于它本身,我們稱整數(shù)k是可移的。

顯然1永遠不可移,n除了以下兩種情形外,它都是可移的:

(1) n是第一個數(shù),且其方向指向左側(cè)

(2) n是最后一個數(shù),且其方向指向右側(cè)

于是,我們可按如下算法產(chǎn)生所有排列:

1,開始時:存在排列123…n,除1外,所有元素均可移,即方向都指向左側(cè)。

2,當最大元素n可移動時,將其從一端依次移動到另一端,即可得到n-1個全排列;當n移動到某一端后,不能再移動,此時尋找最大的可移數(shù)m,將m與其箭頭所指的鄰數(shù)互換位置,這樣就又得到一個新的全排列;將所得新排列中所有比m大的數(shù)p的方向調(diào)整,即改為相反方向,這樣使得n又成了可移數(shù)。

3,重復第2步直到所有的元素都不能移動為止。

4個元素的排列為例,首先生成全排列1 2 3 4

找到最大的可移數(shù)4,將4與其箭頭所指的鄰數(shù)互換位置,可以生成3個新排列:

1 2 4 3   1 4 2 3   4 1 2 3

因為沒有比4更大的數(shù)p,所以無需調(diào)整p的方向。最大數(shù)4到了最左邊后,由于其方向指向左側(cè),所以4不能再移動。

接下來尋找最大的可移數(shù)3,將3與其箭頭所指的鄰數(shù)2互換位置,可以得到新排列:4 1 3 2

然后將所得排列中比3大的數(shù)4的方向調(diào)整,使得4可以移動,重新成為最大的可移數(shù),將4與其箭頭所指的鄰數(shù)互換位置,可以生成3個新排列:

1 4 3 2   1 3 4 2   1 3 2 4

此時最大數(shù)4到了最右邊,又因為其方向指向右側(cè),所以4不能再移動;于是我們尋找最大的可移數(shù)3,將3與其箭頭所指的鄰數(shù)1互換位置,可以得到新排列:3 1 2 4

然后將所得排列中比3大的數(shù)4的方向調(diào)整,使得4可以移動,重新成為最大的可移數(shù),將4與其箭頭所指的鄰數(shù)互換位置,可以生成3個新排列:

3 1 4 2   3 4 1 2   4 3 1 2

如此循環(huán),直到所有的數(shù)都不能移動,即可求出全部排列。最后得到的一個全排列為:2 1 3 4,此時2指向左側(cè),134均指向右側(cè)。

根據(jù)上述算法分析,使用一個輔助數(shù)組來存儲各個元素的指向,我們可以得到代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:排列鄰位對換法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(int n)

{

    int  *a = new int[n];  //用來存儲n個自然數(shù)

    bool *p = new bool[n]; //用來存儲n個元素的指向:向左為false,向右為true

    for (int i=0; i<n; i++) //存儲全排列的元素值,并計算全排列的數(shù)量

    {

        a[i] = i + 1;

        p[i] = false; //開始均指向左側(cè)

    }

   

    do

    {

        Print(a, n); //輸出第一個全排列

        if (n == a[n-1])//n在最右側(cè),將其逐次與左側(cè)的元素交換,得到n - 1個新的排列

        {

            for (int i=n-1; i>0; i--)

            {

                int  temp = a[i]; a[i] = a[i-1]; a[i-1] = temp;

                bool flag = p[i]; p[i] = p[i-1]; p[i-1] = flag;

                Print(a, n);

            }

        }

        else //n在最左側(cè),將其逐次與右側(cè)的元素交換,得到n - 1個新的排列

        {

            for (int i=1; i<n; i++)

            {

                int temp = a[i]; a[i] = a[i-1]; a[i-1] = temp;

                bool flag = p[i]; p[i] = p[i-1]; p[i-1] = flag;

                Print(a, n);

            }

        }

    } while (Move(a, p, n));

   

    delete []a;

    delete []p;

}

 

/*

函數(shù)名稱:Move

函數(shù)功能:尋找最大可移數(shù),可移數(shù)m,將m與其箭頭所指的鄰數(shù)互換位置,

          并將所得新排列中所有比m大的數(shù)p的方向調(diào)整

輸入變量:int a[]:存儲了123...nn個自然數(shù)的數(shù)組

          bool p[]:存儲了n個元素的指向的數(shù)組:向左為false,向右為true

          int n:數(shù)組a[]的長度

輸出變量:排列中存在最大可移數(shù),則做了相關操作后返回真,否則直接返回假

*/

bool Move(int a[], bool p[], int n)

{

    int max = 1;

    int pos = -1;

   

    for (int i=0; i<n; i++)

    {

        if (a[i] < max)

            continue;

        if ((p[i] && i < n-1 && a[i] > a[i+1]) || //指向右側(cè) 

            (!p[i] && i > 0 && a[i] > a[i-1]))    //指向左側(cè)

        {

            max = a[i];

            pos = i;

        }

    }

   

    if (pos == -1) //都不能移動

        return false;

  

    //與其箭頭所指的鄰數(shù)互換位置

    if (p[pos]) //指向右側(cè)

    {

        int  temp = a[pos]; a[pos] = a[pos+1]; a[pos+1] = temp;

        bool flag = p[pos]; p[pos] = p[pos+1]; p[pos+1] = flag;

    }

    else //指向左側(cè)

    {

        int  temp = a[pos]; a[pos] = a[pos-1]; a[pos-1] = temp;

        bool flag = p[pos]; p[pos] = p[pos-1]; p[pos-1] = flag;

    }

   

    //將所得排列中所有比max大的數(shù)p的方向調(diào)整

    for (int i=0; i<n; i++)   

    {

        if (a[i] > max)

            p[i] = !p[i];

    }

   

    return true;

}

 

遞增進位排列生成算法是需要設置中介數(shù)的一種算法,它的映射和還原方法如下:

映射方法:將原排列按照從92的順序,依次查看其右側(cè)比其小的數(shù)字有幾個,個數(shù)就是中介數(shù)的一位。例如對于原排列8396475219的右側(cè)比9小的數(shù)字有6個,8的右側(cè)比8小的數(shù)字有7個,7的右側(cè)比7小的數(shù)字有3個,……2的右側(cè)比2小的數(shù)字有1個。最后得到遞增進制中介數(shù)67342221。(此中介數(shù)加上100的遞增進制數(shù)4020得到新的中介數(shù)67351311

還原方法:我們設新中介數(shù)的位置號從左向右依次是98765432。在還原前,畫9個空格。對于每一個在位置x的中介數(shù)y,從空格的右側(cè)向左數(shù)y個未被占用的空格。在第y+1個未占用的空格中填上數(shù)字x。重復這個過程直到中介數(shù)中所有的位都被數(shù)完。最后在余下的最后一個空格里填上1,完成新排列的生成。以新中介數(shù)67351311為例,我給出了詳細的恢復步驟。其中紅色數(shù)字代表新填上的數(shù)字。最后得到新排列869427351

新中介數(shù)當前位

新排列數(shù)

備注

初始

                 

在還原前,畫9個空格

6

    9            

從右向左數(shù)6個空格,第7個空格里填“9

7

8   9            

從右向左數(shù)7個空格,第8個空格里填“8

3

8   9     7      

從右向左數(shù)3個空格,第4個空格里填“7

5

8 6 9     7      9

從右向左數(shù)5個空格,第6個空格里填“6

1

8 6 9     7   5  9

從右向左數(shù)1個空格,第2個空格里填“5

3

8 6 9 4   7   5  7

從右向左數(shù)3個空格,第4個空格里填“4

1

8 6 9 4   7 3 5  7

從右向左數(shù)1個空格,第2個空格里填“3

1

8 6 9 4 2 7 3 5  7

從右向左數(shù)1個空格,第2個空格里填“2

最后補位

8 6 9 4 2 7 3 5 1 

余下的空格填“1

 

    我們設原中介數(shù)為0,然后讓中介數(shù)加上遞增序號1,根據(jù)上文將的方法得到新的中介數(shù),最后還原新中介數(shù),得到一個全排列。重復上述過程就可以得到所有所有的全排列。

主要代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:遞增進位排列生成算法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(unsigned int n)

{

    unsigned int *p = new unsigned int[n];//用來存儲各個全排列的值,p[i]=i!

    p[0] = 1;

    for (int i=1; i<n; i++)

    {

        p[i] = (i + 1) * p[i-1]; //cout << p[i] << "  ";

    }

 

    int *b = new int[n];      //用來存儲新的全排列

    int *yShe = new int[n-1]; //將原中介數(shù)加上序號i的遞增進位制數(shù)得到新的映射

    int *diZ = new int[n-1];  //用來存儲序號i的遞增進位制數(shù),設所有元素的初值為0

    for (int i=0; i<n-1; i++)

    {

        diZ[i] = yShe[i] = 0;

    }

   

    diZ[n-2] = 1; //存儲序號1,表示每次遞增1

    for (int c=0; c<p[n-1]; c++)

    {

        DiZYingShe(yShe, diZ, n); //每次遞增1后得到得到新的映射

       

        for (int i=0; i<n; i++) //新的全排列空格初始化

            b[i] = 0;

       

        int num = n;

        for (int j=0; j<n-1; j++) //獲取前n-1個數(shù)字

        {

            int s = 0;

            int k = n - 1;

            while (s < yShe[j])

            {

                if (b[k] == 0) //該處未填數(shù)字

                    s++;

                k--;

            }

            while (b[k] != 0) //跳過已填數(shù)字

                k--;

            b[k] = num--;

        }

        for (int i=0; i<n; i++)//填充最后一個數(shù)字1

        {

            if (b[i] == 0)

            {

                b[i] = 1;

                break;

            }

        }

       

        Print(b, n);

    }

   

    delete []b;

    delete []p;

    delete []diZ;

    delete []yShe;

}

 

/*

函數(shù)名稱:DiZYingShe

函數(shù)功能:遞增進位排列生成算法的加法運算:yShe[] += dZJ[]

輸入變量:int yShe[]:原遞增進制中介數(shù);

          int diZ[]:遞增進制數(shù)加數(shù)

          int len:最大自然數(shù)n的值

輸出變量:int yShe[]:新的增進制中介數(shù)

*/

void DiZYingShe(int yShe[], int diZ[], int n)

{

    int pos = n - 2;

    int r = 0;

    while (pos >= 0)

    {

        yShe[pos] += diZ[pos] + r;

        if (yShe[pos] >= n - pos)

        {

            yShe[pos] -= n - pos;

            r = 1;

        }

        else

            r = 0;

        pos--;

    }

}

 

遞減進位制排列生成算法和遞增進位制排列生成算法的原理很相似,都需要一個先映射一個中介數(shù),我們設原中介數(shù)為0,讓中介數(shù)加上遞增序號1后再還原中介數(shù),就得到一個全排列。

    遞減進位制和遞增進位制的映射方法,以及還原方法都剛好相反。

映射方法:遞減進位制的映射方法剛好和遞增進位制相反,即按照從92的順序,依次查看其右側(cè)比其小數(shù)字的個數(shù)。但是,生成中介數(shù)的順序不再是從左向右,而是從右向左。生成的遞減進制中介數(shù)剛好是遞增進位排列生成算法得到中介數(shù)的鏡像。例如839647521的遞減進制中介數(shù)就是12224376。(此中介數(shù)加上 100的遞減進制數(shù)131后得到新中介數(shù)12224527

還原方法:遞減進位制中介數(shù)的還原方法也剛好和遞增進位制中介數(shù)相反。遞增進位制還原方法是按照從中介數(shù)最高位(左側(cè))到最低位(右側(cè))的順序來填數(shù)。而遞減僅位置還原方法則從中介數(shù)的最低位向最高位進行依次計數(shù)填空。例如對于新中介數(shù)12224527,我給出了詳細的還原步驟。紅色代表當前正在填充的空格。最終得到新排列 397645821

新中介數(shù)當前位

新排列數(shù)

備注

初始

                 

在還原前,畫9個空格

7

  9              

從右向左數(shù)7個空格,第8個空格里填“9

2

  9         8    

從右向左數(shù)2個空格,第3個空格里填“8

5

  9 7       8    

從右向左數(shù)5個空格,第6個空格里填“7

4

  9 7 6     8    9

從右向左數(shù)4個空格,第5個空格里填“6

2

  9 7 6   5 8    9

從右向左數(shù)2個空格,第3個空格里填“5

2

  9 7 6 4 5 8    7

從右向左數(shù)2個空格,第3個空格里填“4

2

3 9 7 6 4 5 8    7

從右向左數(shù)2個空格,第3個空格里填“3

1

3 9 7 6 4 5 8 2  7

從右向左數(shù)1個空格,第2個空格里填“2

最后補位

3 9 7 6 4 5 8 2 1 

余下的空格填“1

 

主要代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:遞減進位排列生成算法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(unsigned int n)

{

    unsigned int *p = new unsigned int[n];//用來存儲各個全排列的值,p[i]=i!

    p[0] = 1;

    for (int i=1; i<n; i++)

    {

        p[i] = (i + 1) * p[i-1]; //cout << p[i] << "  ";

    }

 

    int *b = new int[n];//用來存儲新的全排列

    int *yShe = new int[n-1]; //將原中介數(shù)加上序號i的遞減進位制數(shù)得到新的映射

    int *diJ = new int[n-1];

    for (int i=0; i<n-1; i++)

    {

        diJ[i] = yShe[i] = 0;

    }

   

    diJ[n-2] = 1;

    for (int c=0; c<p[n-1]; c++)

    {

        DiJYingShe(yShe, diJ, n);  //每次遞增1

       

        for (int i=0; i<n; i++) //新的全排列空格初始化

            b[i] = 0;

       

        int num = n;

        for (int j=n-2; j>=0; j--) //獲取前n-1個數(shù)字

        {

            int s = 0;

            int k = n - 1;

            while (s < yShe[j])

            {

                if (b[k] == 0) //該處未填數(shù)字

                    s++;

                k--;

            }

            while (b[k] != 0) //跳過已填數(shù)字

                k--;

            b[k] = num--;

        }

        for (int i=0; i<n; i++)//填充最后一個數(shù)字1

        {

            if (b[i] == 0)

            {

                b[i] = 1;

                break;

            }

        }

       

    Print(b, n);

    }

   

    delete []b;

    delete []p;

    delete []diJ;

    delete []yShe;

}

 

/*

函數(shù)名稱:DiJYingShe

函數(shù)功能:遞減進位排列生成算法的加法運算:yShe[] += dZJ[]

輸入變量:int yShe[]:原遞減進制中介數(shù);

          int diJ[]:遞減進制數(shù)加數(shù)

          int n:最大自然數(shù)n的值

輸出變量:int yShe[]:新的遞減進制中介數(shù)

*/

void DiJYingShe(int yShe[], int diJ[], int n)

{

    int pos = n - 2;

    int r = 0;

    while (pos >= 0)

    {

        yShe[pos] += diJ[pos] + r;

        if (yShe[pos] >= pos + 2)

        {

            yShe[pos] -= pos + 2;

            r = 1;

        }

        else

            r = 0;

        pos--;

    }

}

 

循環(huán)左移排列生成算法

映射方法:循環(huán)左移排列生成算法與遞減進位排列生成算法非常相似,同樣是在原始排列中找到比當前數(shù)字小的數(shù)字個數(shù)。只不過循環(huán)左移使用的是左循環(huán)搜索法,而不是遞減進位法使用的右側(cè)搜索。所謂左循環(huán)搜索法是指從起始數(shù)字開始向左搜索,如果到了左邊界還沒有發(fā)現(xiàn)終止數(shù)字,則從右邊界開始繼續(xù)尋找,直到終止數(shù)字被發(fā)現(xiàn)。

 
圖中展示了839647521種以開始數(shù)字為6

結(jié)束數(shù)字為5和開始數(shù)字為7,結(jié)束數(shù)字為6

左循環(huán)搜索區(qū)間,注意開始和結(jié)束數(shù)字是不包括

在區(qū)間內(nèi)的。

具體到循環(huán)左移的排列生成法的映射方法,

就是要為每一個數(shù)字確定這樣一個區(qū)間。方法是

以從92的順序依次產(chǎn)生中介數(shù)中的數(shù)字,中介數(shù)

從右向左產(chǎn)生(即原排列的9產(chǎn)生的數(shù)字放到中介數(shù)右數(shù)第一位,8產(chǎn)生的數(shù)字放到中介數(shù)右數(shù)第二位,以此類推。這樣才能得到遞減進位制數(shù))。對于9,產(chǎn)生的中介數(shù)字依然是9的右邊比9小的數(shù)字的個數(shù)。但是從8開始一直到2中的每一個數(shù)i,都是要做一個以i+1為開始數(shù)字,i為結(jié)束數(shù)字的左循環(huán)區(qū)間,并看這個左循環(huán)區(qū)間中比i小的數(shù)字的個數(shù)。例如對于排列8396475219的右側(cè)比9小的數(shù)字有6個;98的左循環(huán)區(qū)間比8小的數(shù)字有1個;87的左循環(huán)區(qū)間比7小的數(shù)字有3 個;76的左循環(huán)區(qū)間比6小的數(shù)字有1個(如上面圖下半部所示);65的左循環(huán)區(qū)間比5小的右3個數(shù)字(如上圖上半部分所示);……;32的左循環(huán)區(qū)間里比2小的數(shù)字有1個。所以得到遞減中介數(shù)10031316(此中結(jié)束加上100的遞減進制數(shù)131得新中介數(shù)10031447

還原方法:循環(huán)左移的還原方法很自然。首先畫9個空格。當拿到新的中介數(shù)后,從中介數(shù)的最右邊向左邊開始計數(shù)逐個計數(shù)。計數(shù)的方法是,對于中介數(shù)最右邊的數(shù)x9,從空格的最右端起數(shù)x9個空格,在第x9+1個空格上填上9。然后對于中介數(shù)的每一位xi,沿著左循環(huán)的方向數(shù)xi個未占用空格,在第xi+1個空格里填上i,即可完成還原。我給出了將中介數(shù)10031447還原的步驟,其中底紋代表為了定位下一個數(shù)字而數(shù)過的空格。紅色代表被填充的空格。最終得到結(jié)果592138647

新中介數(shù)當前位

新排列數(shù)

備注

初始

                 

在還原前,畫9個空格

7

  9              

從右向左數(shù)7個空格,第8個空格提填“9

4

  9       8      

9開始左循環(huán)數(shù)4個空格,第5個空格提填“8

4

  9       8     7

8開始左循環(huán)數(shù)4個空格,第5個空格提填“7

1

  9       8 6   79

7開始左循環(huán)數(shù)1個空格,第2個空格提填“6

3

5 9       8 6   79

6開始左循環(huán)數(shù)3個空格,第4個空格提填“5

0

5 9       8 6 4 77

5開始左循環(huán)數(shù)0個空格,第1個空格提填“4

0

5 9     3 8 6 4 77

4開始左循環(huán)數(shù)0個空格,第1個空格提填“3

1

5 9 2   3 8 6 4 77

3開始左循環(huán)數(shù)1個空格,第2個空格提填“2

最后補位

5 9 2 1 3 8 6 4 7 

余下的空格填“1

 

與遞減進位排列生成算法一樣,循環(huán)左移排列生成算法也用到了遞減進位排列生成算法的加法運算函數(shù)。主要代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:循環(huán)左移排列生成算法:輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(unsigned int n)

{

    unsigned int *p = new unsigned int[n];//用來存儲各個全排列的值,p[i]=i!

    p[0] = 1;

    for (int i=1; i<n; i++)

    {

        p[i] = (i + 1) * p[i-1];

    }

 

    int *b = new int[n];//用來存儲新的全排列

    int *yShe = new int[n-1]; //將原中介數(shù)加上序號i的遞減進位制數(shù)得到新的映射

    int *diJ = new int[n-1];

    for (int i=0; i<n-1; i++)

    {

        diJ[i] = yShe[i] = 0;

    }

   

    diJ[n-2] = 1;

    for (int c=0; c<p[n-1]; c++)

    {

        DiJYingShe(yShe, diJ, n);  //每次遞增1

       

        for (int i=0; i<n; i++) //新的全排列空格初始化

            b[i] = 0;

       

        int num = n;

        int pos = n - 1;

        for (int j=n-2; j>=0; j--) //獲取前n-1個數(shù)字

        {

            int s = 0;

            int k = pos;

            while (k >= 0 && s < yShe[j])

            {

                if (b[k] == 0) //該處未填數(shù)字

                    s++;

                k--;

            }

           

            //跳過已填數(shù)字

            while (k >= 0 && b[k] != 0)

                k--;

            if (b[k] != 0)//如果到了最左側(cè)還沒有找到適合的位置,從最右側(cè)繼續(xù)分析

            {

                k = n - 1;

                while (k > pos && b[k] != 0)//跳過非空位:b[j] == 0表示j位置是空位

                    k--;

            }

           

            //如果到了最左側(cè)還沒有找到適合的位置,從最右側(cè)繼續(xù)累積s

            if (s < yShe[j])

            {

                k = n - 1;

                while (k >= 0 && s < yShe[j])

                {

                    if (b[k] == 0)

                        s++;

                    k--;

                }

                while (k >= 0 && b[k] != 0) //跳過已填數(shù)字

                    k--;

            }

            b[k] = num--;

            pos = k;

        }

       

        for (int i=0; i<n; i++)//填充最后一個數(shù)字1

        {

            if (b[i] == 0)

            {

                b[i] = 1;

                break;

            }

        }

       

        Print(b, n);

    }

   

    delete []b;

    delete []p;

    delete []diJ;

    delete []yShe;

}  

 

最后介紹一種準有序全排列生成算法:顛倒的協(xié)詞典順序算法。

這是Donald E.Knuth在《計算機程序設計藝術(第4卷 第2冊)》上介紹的一種方法。

該算法的原理為:設P1n的一個全排列:p = p1p2...pn

1)從排列的左端開始,找出第一個比右邊數(shù)字小的數(shù)字的序號j,即j = min{i | pi < pi+1},如果j == n-1,表示已經(jīng)輸出了所有的全排列。

2)在pj的左邊的數(shù)字中,找出所有比pj小的數(shù)中最大的數(shù)字pk,即 k = max{i | pi > pj}(左邊的數(shù)從左至右是遞減的,因此k是所有小于pj的數(shù)字中序號最大者)

3)對換pjpk

4)再將p1p2...pj-1倒轉(zhuǎn)得到新的排列

n = 3為例,我們可以得到全排列123213132312231321.

仔細觀察這些序列,如果我們從右到左地反射這些序列,就可以得到321312231213132123

它剛好是字典序全排列的顛倒。所以如果我們逆序地輸出序列,是可以得到一個有序全排列的。

代碼如下:

/*

函數(shù)名稱:Permutation

函數(shù)功能:顛倒的協(xié)詞典順序算法:逆序輸出n個數(shù)的所有全排列

輸入變量:int n123...nn個自然數(shù)

輸出變量:無

*/

void Permutation(unsigned int n)

{

    int *a = new int[n];//用來存儲n個自然數(shù)

    for (int i=0; i<n; i++) //存儲全排列的元素值,并計算全排列的數(shù)量

    {

        a[i] = i + 1;

    }

   

    int temp, left, right;

    while (1)

    {

        Print(a, n);

        for (right=1; right<n; right++)//找出第一個比右邊數(shù)字小的數(shù)字的序號right-1

        {

            if (a[right-1] < a[right])

                break;

        }

        if (right == n) //表示已經(jīng)輸出所有全排列

            break;

        //找到右邊界左邊數(shù)組中比a[right]小的最大的元素,這個就是要取代a[right]的元素

        for (left=0; a[left] >= a[right]; left++)

            ;

        temp = a[left]; a[left] = a[right]; a[right] = temp; //交換a[right]a[left]

       

        left = 0;

        right--; //右邊界減1,保證此時左右邊界之間的元素是一個遞減排列

        while (left < right)//逆置左右邊界之間的元素,使其按增序排列

        {

            temp = a[left]; a[left] = a[right]; a[right] = temp;

            left++;

            right--;

        }

    }

   

    delete []a;

}

 

到這里我就把我目前所知的幾種非有序全排列算法都介紹完了,以下是上述七種方法的測試結(jié)果:(為了方便測試,我只要求輸出最后一個全排列)

n = 10 時,普通遞歸算法用時0秒;全排列循環(huán)移位法用時0秒;排列鄰位對換法用時0秒;遞增進位排列生成算法用時3秒;遞減進位排列生成算法用時3秒;循環(huán)左移排列生成算法用時4秒;顛倒的協(xié)詞典順序算法用時0秒。

n = 11 時,普通遞歸算法用時2秒;全排列循環(huán)移位法用時5秒;排列鄰位對換法用時2秒;遞增進位排列生成算法用時42秒;遞減進位排列生成算法用時39秒;循環(huán)左移排列生成算法用時54秒;顛倒的協(xié)詞典順序算法用時2秒。

n = 12時,普通遞歸算法用時29秒;排列鄰位對換法用時25秒;顛倒的協(xié)詞典順序算法用時23秒。

全排列的應用非常廣泛,我們在很多地方都會遇到它,下次我將總結(jié)一下“列車出棧系列問題”,在該問題中,我們可以看到全排列的身影。

 

參考文獻:

1.《組合數(shù)學——排列數(shù)生成算法詳解》speedfirst Blog

http://www.newsmth.net/pc/pcdoc.php?userid=speedfirst&pid=0&tag=0&order=&tid=18452

2.《全排列的生成算法》 visame的專欄

http://blog.csdn.net/visame/archive/2008/05/18/2455396.aspx

 

附錄:

《康托展開》:http://blog.csdn.net/goal00001111/archive/2008/11/18/3326662.aspx

《遞增進位制和遞減進位制數(shù)》:

http://blog.csdn.net/goal00001111/archive/2008/11/18/3326765.aspx

《有序全排列生成算法集錦》

http://blog.csdn.net/goal00001111/archive/2008/11/18/3326642.aspx

《非有序全排列生成算法集錦》

http://blog.csdn.net/goal00001111/archive/2009/01/20/3839683.aspx

Posted on 2009-01-20 16:08 夢想飛揚 閱讀(3443) 評論(2)  編輯 收藏 引用

Feedback

# re: 非有序全排列生成算法[未登錄]  回復  更多評論   

2009-12-05 19:01 by star
非常好的文章!
近幾天在研究全排列的問題,這文章對我有很大幫助哦!

# re: 非有序全排列生成算法  回復  更多評論   

2009-12-09 20:47 by LostCanvas
學習了 3Q
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美自拍丝袜亚洲| 国产精品最新自拍| 一本色道久久加勒比88综合| 久久国产成人| 久久久久欧美精品| 久久婷婷久久| 亚洲国产综合视频在线观看| 久久精品亚洲国产奇米99| 久久久久在线观看| 欧美a一区二区| 亚洲精品乱码视频| 亚洲综合99| 久久在线免费观看| 欧美剧在线观看| 国产精品三级视频| 亚洲电影在线播放| 正在播放欧美一区| 久久一区欧美| 99成人精品| 久久久久九九九九| 欧美日韩国产999| 国产欧美日韩不卡| 亚洲人被黑人高潮完整版| 亚洲男人的天堂在线观看| 久久夜色精品亚洲噜噜国产mv | 亚洲精品欧美一区二区三区| 欧美风情在线观看| 欧美在线视频网站| 久久久久一区二区| 在线视频精品一区| 久久亚洲私人国产精品va媚药 | 亚洲视屏一区| 久久影音先锋| 国产日韩免费| 亚洲性图久久| 欧美成人免费在线| 午夜一级久久| 国产精品久久久久影院色老大 | 性色av一区二区三区在线观看| 牛牛影视久久网| 国产综合久久久久久鬼色| 中国成人在线视频| 欧美国内亚洲| 久久精品导航| 国产一区二区三区久久悠悠色av| 亚洲视频在线一区| 亚洲第一视频网站| 美女国内精品自产拍在线播放| 国产乱码精品1区2区3区| 日韩午夜激情| 亚洲区在线播放| 蜜臀久久99精品久久久久久9| 国产一区二区三区精品欧美日韩一区二区三区| 亚洲精品欧美日韩| 亚洲精品网站在线播放gif| 免费成人高清| 亚洲精选成人| 亚洲激情小视频| 欧美大学生性色视频| 亚洲电影第1页| 欧美成人一区二区三区片免费| 久久久久国产一区二区| 伊人一区二区三区久久精品| 久久久精品国产免费观看同学| 亚洲女人小视频在线观看| 国产精品久久久久国产a级| 亚洲天堂免费在线观看视频| 日韩一级精品视频在线观看| 欧美视频免费看| 亚洲欧美国产制服动漫| 亚洲在线视频观看| 国产亚洲精品高潮| 久久精品一区二区三区不卡| 久久成人人人人精品欧| 黄色日韩网站视频| 欧美大片在线看| 欧美人成免费网站| 亚洲欧美日韩一区二区| 欧美一区三区二区在线观看| 久久久精品国产免大香伊| 亚洲视频综合| 国产一区二区三区不卡在线观看| 久久天天躁狠狠躁夜夜爽蜜月| 欧美在线一二三四区| 亚洲国产成人精品视频| 91久久久在线| 国产精品久久7| 久久亚洲精品视频| 欧美激情第10页| 香蕉久久一区二区不卡无毒影院| 欧美一区二区大片| 亚洲精品视频在线观看网站| 一本色道久久综合亚洲精品不卡| 国产麻豆综合| 亚洲国产裸拍裸体视频在线观看乱了中文 | 好吊妞这里只有精品| 欧美激情精品久久久久久黑人 | 欧美日韩不卡视频| 欧美一区二区三区在线播放| 久久婷婷久久| 午夜精品美女久久久久av福利| 久久视频在线免费观看| 亚洲一二三区精品| 老妇喷水一区二区三区| 亚洲午夜三级在线| 久久三级视频| 欧美中文字幕在线视频| 欧美日韩成人综合在线一区二区 | 亚洲午夜激情| 久久久女女女女999久久| 亚洲性夜色噜噜噜7777| 免费观看一区| 久久日韩粉嫩一区二区三区| 欧美日韩在线播放一区| 欧美顶级少妇做爰| 国语自产精品视频在线看一大j8| 夜夜狂射影院欧美极品| 亚洲人成网站在线观看播放| 久久精品亚洲精品| 亚洲欧美综合另类中字| 欧美精品一区在线| 亚洲大胆女人| 在线欧美视频| 久久久久综合网| 久久精品久久99精品久久| 欧美午夜大胆人体| 亚洲精选久久| 亚洲精品影视| 免费一级欧美在线大片| 免费欧美在线视频| 欧美亚洲午夜视频在线观看| 欧美二区在线| 老司机精品福利视频| 国产视频一区在线| 亚洲午夜精品久久| 亚洲视频导航| 欧美日韩一区国产| 一本色道久久综合亚洲精品婷婷| 在线视频中文亚洲| 欧美日韩亚洲不卡| 一区二区欧美日韩视频| 亚洲无毛电影| 国产精品剧情在线亚洲| 亚洲香蕉成视频在线观看| 亚洲男人影院| 国产美女精品免费电影| 欧美一级黄色网| 美女91精品| 日韩亚洲国产精品| 欧美午夜片在线观看| 亚洲无线观看| 久久精品国产成人| 伊人久久婷婷| 欧美国产三区| 亚洲视频一区二区| 久久激情五月丁香伊人| 在线观看欧美精品| 欧美国产亚洲精品久久久8v| 99精品国产热久久91蜜凸| 午夜精品亚洲一区二区三区嫩草| 国产小视频国产精品| 久久亚洲欧美| 亚洲视频1区2区| 免费观看成人鲁鲁鲁鲁鲁视频| 亚洲激情第一页| 国产精品久久77777| 久久久久久电影| 亚洲精品一区久久久久久| 欧美一级大片在线观看| 在线免费一区三区| 国产精品国产三级国产aⅴ浪潮| 欧美呦呦网站| 99re国产精品| 蜜桃av噜噜一区二区三区| 一本色道久久综合一区 | 亚洲国产美女| 欧美午夜精品理论片a级按摩| 欧美亚洲视频一区二区| 亚洲精品国产视频| 久久久五月天| 亚洲影视九九影院在线观看| 伊人一区二区三区久久精品| 欧美色大人视频| 美女精品视频一区| 亚洲欧美在线一区二区| 亚洲人成在线播放网站岛国| 久久精品99无色码中文字幕| 亚洲精选成人| 在线观看欧美| 国语自产偷拍精品视频偷| 欧美日韩一区二区高清| 久久一区精品| 欧美中文在线观看| 在线视频亚洲| 正在播放欧美视频| 亚洲精品视频在线观看免费| 欧美国产日韩一区二区| 久久综合成人精品亚洲另类欧美 | 日韩亚洲欧美一区| 亚洲国产一区二区三区青草影视|