產生下一個排列數的算法


全排序:
從n個不同元素中任取m(m≤n)個元素,按照一定的順序排列起來,叫做從n個不同元素中取出m個元素的一個排列。當m=n時所有的排列情況叫全排列。例如n=3,全排序為:123、132、213、231、312、321共6種。

字典序法:
對給定的字符集中的字符規定了一個先后關系,在此基礎上規定兩個全排列的先后是:從左到右逐個比較對應的字符大小。字符集{1,2,3},較小的數字較先,這樣按字典序生成的全排列即:123、132、213、231、312、321。

1.現在假設輸入全排序中的一串數字,要求得到它在字典序全排列中對應的下一個排列數。比如:輸入123輸出132,輸入12435輸出12453。

算法思想:

1.從數列的右邊向左尋找連續遞增序列, 例如對於:1,3,5,4,2,其中5-3-2即為遞增序列。

2.從上述序列中找一個比它前面的數(3)大的最小數(4),並將且交換這兩個數。於是1,3,5,4,2->1,4,5,3,2,此時交換后的依然是遞增序列。

3.新的遞增序列逆序,即:1,4,5,3,2 => 1,4,2,3,5

#include <iostream>
using namespace std;

void swap(int * a, int * b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

//產生下一個排列數
//存在則返回1
//不存在返回0
int next(int a[], int n)
{
    int i,j,k;
    // 從右向左尋找非遞減序列,例如對於序列1,3,5,4,2,將會找到數字5的位置
    for(k = n - 1; k > 0 && a[k - 1] >= a[k]; k--);
    if(k == 0) return 0; //例:對於5,4,3,2,1的情形,他的下一個不存在,返回0
    //從非遞減序列李尋找比它前面的一個數(3)大的最小數,即數字4
    for(i = n - 1; a[i] <= a[k - 1]; i--);
    //交換3和4
    swap(&a[k - 1], &a[i]);
    //將新的非遞減序列逆序
    i = k;
    j = n - 1;
    while(i < j) swap(&a[i++], &a[j--]);
    return 1;
}

void pnt(int a[], int n)
{
    int i;
    for(i = 0; i < n; ++i) printf("%d ", a[i]);
    printf("\n");
}

int main()
{
    int a[] = {1,2,3,4};
    int size = sizeof(a) / sizeof(a[0]);
    pnt(a, size);
    while(next(a, size))
        pnt(a, size);
    return 0;
}

2.全排序的遞歸實現

全排序是將一組數按一定順序排列,如果這組數有n個,那么全排列有n!個。

如果只有兩個數2和3,那么全排序是23和32;如果再添加一個數1,那么全排序為123,132,213,231,312,321。所以全排序就是將數組中的所有數分別和第一個數交換,然后再添加剩下n-1個數的全排列即可,典型的遞歸思想。

算法思想:

1.將字符串中第一個字符與后面的第k個字符交換(初始值k=2)

2.計算除去第一個字符的串的全排序

3.將第一個字符和第k個字符交換(恢復原來的字符串),然后k+=1,回到第一步,直至k=length.

#include <iostream>  
using namespace std;

void Swap(char *a, char *b)  
{  
    char temp = *a;  
    *a = *b;  
    *b = temp;  
}  
//k表示當前選取到第幾個數,m表示共有多少數.  
void AllRange(char *pszStr, int k, int m)  
{  
    if (k == m)  
    {   
        printf("%s\n", pszStr);  
    }  
    else  
    {  
        for (int i = k; i <= m; i++)
        {  
            Swap(pszStr + k, pszStr + i);  
            AllRange(pszStr, k + 1, m);  
            Swap(pszStr + k, pszStr + i);  
        }  
    }  
}  

int main()  
{  
    char szTextStr[] = "123";  
    AllRange(szTextStr, 0, strlen(szTextStr) - 1); 
    return 0;  
}

上面的程序沒有考慮到重復數字,如122將會輸出:122,122,212,221,221,212。而實際應輸出的是122,212,221,對於相同的數,只需要和這些相同數中的第一個交換就可以了,比如12324,在2324中只需要和第一個2交換即可得到2開頭的全部全排列了。用編程的話描述就是第i個數與第j個數交換時,要求[i,j)中沒有與第j個數相等的數。

//在pszStr數組中,[nBegin,nEnd)中是否有數字與下標為nEnd的數字相等  
bool IsSwap(char *pszStr, int nBegin, int nEnd)  
{  
    for (int i = nBegin; i < nEnd; i++)  
        if (pszStr[i] == pszStr[nEnd])  
            return false;  
    return true;  
}  
//k表示當前選取到第幾個數,m表示共有多少數.  
void AllRange(char *pszStr, int k, int m)  
{  
    if (k == m)  
    {  
        printf("%s\n", pszStr);  
    }  
    else  
    {  
        for (int i = k; i <= m; i++) //第i個數分別與它后面的數字交換就能得到新的排列  
        {  
            if (IsSwap(pszStr, k, i))  
            {  
                Swap(pszStr + k, pszStr + i);  
                AllRange(pszStr, k + 1, m);  
                Swap(pszStr + k, pszStr + i);  
            }  
        }  
    }  
}

3.全排序的非遞歸實現

全排序的非遞歸實現,就是先要考慮如何計算字符串的下一個排列(問題1)。只要對字符串反復求下一個排序,最終即可得到全排序。這里注意:輸入的串需要預先由小到大排序(構建全排序的第一個串)。

int QsortCmp(const void *pa, const void *pb)  
{  
    return *(int*)pa - *(int*)pb;  
}  

int main()
{
    int a[] = {3,1,2};
    int size = sizeof(a) / sizeof(a[0]);
    qsort(a, size, sizeof(a[0]), QsortCmp);
    pnt(a, size);
    while(next(a, size))
        pnt(a, size);
    return 0;
}

PS:

把一個問題映射到二進制數,有很多好玩兒的題目:
有1000瓶老鼠葯和10只老鼠,其中有一瓶老鼠葯是有毒的,老鼠喝了有毒的老鼠葯2天就會掛掉,如何在兩天之內找到哪瓶老鼠葯是有毒的?(tips:老鼠列成一排,每只老鼠看成一個bit位,1~1000給對應bit位是1的老鼠喝,最后掛掉的老鼠是 葯瓶編號的 bit位1(其它是0),該瓶老鼠葯是有毒的)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM