全排列數的生成
阿新 • • 發佈:2019-01-05
這學期好忙,整個人都變懶了。。coursera上的課程作業只來得及更新到github上,希望自己以後看著註釋還能記得怎麼做。。。得空把上學期的一些作業放這裡。
【問題描述】輸入整數N( 1 <= N <= 10 ),生成從1~N所有整數的全排列。
【輸入形式】輸入整數N。【輸出形式】輸出有N!行,每行都是從1~N所有整數的一個全排列,各整數之間以空格分隔。各行上的全排列不重複。輸出各行遵循“小數優先”原則, 在各全排列中,較小的數儘量靠前輸出。如果將每行上的輸出看成一個數字,則所有輸出構成升序數列。具體格式見輸出樣例。
【樣例輸入1】1
【樣例輸出1】1
【樣例說明1】輸入整數N=1,其全排列只有一種。
【樣例輸入2】3
【樣例輸出2】
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
【樣例說明2】輸入整數N=3,要求整數1、2、3的所有全排列, 共有N!=6行。且先輸出1開頭的所有排列數,再輸出2開頭的所有排列數,最後輸出3開頭的所有排列數。在以1開頭的所有全排列中同樣遵循此原則。
【樣例輸入3】10
【樣例輸出3】
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 10 9
1 2 3 4 5 6 7 9 8 10
1 2 3 4 5 6 7 9 10 8
1 2 3 4 5 6 7 10 8 9
1 2 3 4 5 6 7 10 9 8
1 2 3 4 5 6 8 7 9 10
1 2 3 4 5 6 8 7 10 9
1 2 3 4 5 6 8 9 7 10
1 2 3 4 5 6 8 9 10 7
……………………
【樣例說明3】輸入整數N=10,要求整數1、2、3、……、10的所有全排列。上例顯示了輸出的前10行。
【執行時限】要求每次執行時間限制在20秒之內。超出該時間則認為程式錯誤。提示:當N增大時,執行時間將急劇增加。在程式設計時要注意儘量優化演算法,提高執行效率。
perm在遞迴深度大於陣列長度時認為一次重新排列完成,把這個陣列輸出到螢幕#include <stdio.h> #include <stdlib.h> void rearr(int *arr,int a,int b) { int i; int tmp = arr[b]; for (i = b; i > a; i--) arr[i] = arr[i - 1]; arr[a] = tmp; } void derearr(int *arr, int a, int b) { int i; int tmp = arr[a]; for (i = a; i <b; i++) arr[i] = arr[i + 1]; arr[b] = tmp; } void perm(int *arr,int d,int N) { int i; if (d > N) { for (i = 0; i <= N; i++) printf("%d ", arr[i]); printf("\n"); } else { for (i = d; i <= N; i++) { rearr(arr,d,i); perm(arr,d + 1, N); derearr(arr,d,i); } } } int main() { int N; scanf("%d", &N); int *arr = (int *)malloc(N*sizeof(int)); int i; for (i = 0; i < N; i++) arr[i] = i + 1; perm(arr,0,N-1); free(arr); return 0; }
而如果不是,則將遞迴深度為界,重新排列陣列。對於全排列而言,這意味著將遞迴深度上的數與每個後面的數(包括它自身)都交換一次。
包括與其自身交換是一個技巧,可以直接生成與目前排序相同的排列,這樣就能涵蓋所有的排列。而與每個數都交換,遞迴下去就能保證每個數在每個位置上都出現過。
例如,遞迴深度為一,目前數列為[1,2,3,4,5],那麼本次呼叫會產生五個新的下一級遞迴,分別將第一個元素"1"與"2" "3" "4" "5"交換。
由於本題要求“小數優先”原則,在把第一個元素與後面交換時有一個技巧:我們發現這個元素後面的數是已經從小到大排好序的,利用這個特點
在用於交換的函式rearr中,我們可以將那個數插在我們要交換的位置上,而那個數到交換位置之間的所有數向前挪一位,這樣就保證了小數依舊優先。
交換完成後derearr是這個過程的逆過程,把陣列還原以便於其他遞迴呼叫時後面的元素不變。