全排序:
從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),該瓶老鼠葯是有毒的)