1. 程式人生 > >全排列數的生成

全排列數的生成

這學期好忙,整個人都變懶了。。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增大時,執行時間將急劇增加。在程式設計時要注意儘量優化演算法,提高執行效率。

#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;
}
perm在遞迴深度大於陣列長度時認為一次重新排列完成,把這個陣列輸出到螢幕

而如果不是,則將遞迴深度為界,重新排列陣列。對於全排列而言,這意味著將遞迴深度上的數與每個後面的數(包括它自身)都交換一次。

包括與其自身交換是一個技巧,可以直接生成與目前排序相同的排列,這樣就能涵蓋所有的排列。而與每個數都交換,遞迴下去就能保證每個數在每個位置上都出現過。

例如,遞迴深度為一,目前數列為[1,2,3,4,5],那麼本次呼叫會產生五個新的下一級遞迴,分別將第一個元素"1"與"2" "3" "4" "5"交換。

由於本題要求“小數優先”原則,在把第一個元素與後面交換時有一個技巧:我們發現這個元素後面的數是已經從小到大排好序的,利用這個特點

在用於交換的函式rearr中,我們可以將那個數插在我們要交換的位置上,而那個數到交換位置之間的所有數向前挪一位,這樣就保證了小數依舊優先。

交換完成後derearr是這個過程的逆過程,把陣列還原以便於其他遞迴呼叫時後面的元素不變。