1. 程式人生 > >09-排序1 排序   (25分)

09-排序1 排序   (25分)

給定N個(長整型範圍內的)整數,要求輸出從小到大排序後的結果。

本題旨在測試各種不同的排序演算法在各種資料情況下的表現。各組測試資料特點如下:

資料1:只有1個元素; 資料2:11個不相同的整數,測試基本正確性; 資料3:103個隨機整數; 資料4:104個隨機整數; 資料5:105個隨機整數; 資料6:105個順序整數; 資料7:105個逆序整數; 資料8:105個基本有序的整數; 資料9:105個隨機正整數,每個數字不超過1000。

輸入格式:

輸入第一行給出正整數NN105),隨後一行給出N個(長整型範圍內的)整數,其間以空格分隔。

輸出格式:

在一行中輸出從小到大排序後的結果,數字間以1個空格分隔,行末不得有多餘空格。

輸入樣例:

11
4 981 10 -17 0 -20 29 50 8 43 -5

輸出樣例:

-20 -17 -5 0 4 8 10 29 43 50 981
#include <stdio.h>
#include <stdlib.h>
#define MAX 100005
typedef long ElementType;
void Swap( ElementType *a, ElementType *b ) {
	ElementType Tmp = *a;
	*a = *b;
	*b = Tmp;
}
//氣泡排序; 順序O(N), 逆序O(N^2); 穩定
void Bubble_Sort( ElementType A[], int N ) {
	int P, i, flag;
	for( P = N - 1; P > 0; P-- ) {  //執行N - 1趟冒泡
		flag = 0;
		for( i = 0; i < P; i++ )	//一趟冒泡,比較P次
			if( A[i] > A[i + 1] ){
				Swap(&A[i], &A[i + 1]);
				flag = 1;	//標記發生了交換
			}
		if( flag == 0 ) break;	//上一趟冒泡中無交換,說明已排好序
	}
}
//插入排序(與堆下濾有異曲同工之處); 順序O(N), 逆序O(N^2); 穩定
void Insertion_Sort( ElementType A[], int N ) {
	int P, i;
	ElementType Tmp;
	for( P = 1; P < N; P++ ) {
		Tmp = A[P];		//取出未排序序列中的第一個元素
		for( i = P; i > 0 && A[i - 1] > Tmp; i-- )
			A[i] = A[i - 1];	//依次與已排序序列中的元素比較並右移
		A[i] = Tmp;		//插入到合適的位置
	}
}
//希爾排序 - 希爾增量; O(N^2); 不穩定
void Shell_Sort( ElementType A[], int N ) {
	int D, P, i;
	ElementType Tmp;
	for( D = N / 2; D > 0; D /= 2 )		//希爾增量,每次減半,直到為0
		for( P = D; P < N; P++ ) {    //插入排序,所有1改成D,條件 i > 0 改成 i >= D
			Tmp = A[P];
			for( i = P; i >= D && A[i - D] > Tmp; i -= D )
				A[i] = A[i - D];
			A[i] = Tmp;
		}
}
//希爾排序 - 用Sedgewick增量序列; 猜想:平均O( N^(7/6) ), 最壞O( N^(4/3) ); 不穩定
void Shell_Sort_Sedgewick( ElementType A[], int N ) {
	int Si, D, P, i;
	ElementType Tmp;
	//這裡只列出一小部分增量,公式:選取 9 * 4^i - 9 * 2^i + 1 或 4^i - 3 * 2^i + 1 中的項組合而成
	int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
	for( Si = 0; Sedgewick[Si] >= N; Si++ )
		;	//初始增量Sedgewick[Si]不能超過待排序列的長度
	for( D = Sedgewick[Si]; D > 0; D = Sedgewick[++Si] )
		for( P = D; P < N; P++) {    //插入排序
			Tmp = A[P];
			for( i = P; i >= D && A[i - D] > Tmp; i -= D )
				A[i] = A[i - D];
			A[i] = Tmp;
		}
}
//選擇排序; O(N^2); 不穩定
void Selection_Sort( ElementType A[], int N ) {
	int P, i, idx;
	for( P = 0; P < N - 1; P++ )	{	//N-1不用選擇,因為前面已經排好序
		idx = P;		//選擇P這個位置,從它後面的元素中找一個最小的代替它
		for( i = P + 1; i < N; i++ )
			if( A[idx] > A[i] )
				idx = i;	//記錄最小元素的下標,用此方法減少交換次數
		if( idx != P )	//如果找到比A[P]小的元素A[idx],則交換
			Swap(&A[idx], &A[P]);
	}
}
//堆排序; O(N*logN); 不穩定
void PercDown( ElementType A[], int p, int N ) {
	//將N個元素的陣列中以A[p]為根的子堆調整為最大堆
	int Parent, Child;
	ElementType X;
	X = A[p];	//取出根結點存放的值
	//(Parent * 2 + 1) < N 檢測Parent有沒有孩子
	for( Parent = p; (Parent * 2 + 1) < N; Parent = Child ) {
		Child = Parent * 2 + 1;
		if( Child != N - 1 && A[Child] < A[Child + 1] )
			Child++;	//Child指向左右子結點較大者
		if( X >= A[Child] ) break;
		else	//下濾
			A[Parent] = A[Child];
	}
	A[Parent] = X;
}
void Heap_Sort( ElementType A[], int N ) {
	int i;
	//建立最大堆,由於下標從0開始,最後一個元素的父結點是 N / 2 - 1
	for( i = N / 2 - 1; i >= 0; i--)	
		PercDown(A, i, N);
	//刪除堆頂
	for( i = N - 1; i > 0; i-- ) {
		Swap(&A[0], &A[i]);		
		PercDown(A, 0, i);
	}
}
//歸併排序 - 遞迴實現; O(N*logN); 穩定
//L = 左邊起始位置,R = 右邊起始位置,RightEnd = 右邊終點位置
void Merge( ElementType A[], ElementType TmpA[], int L, int R, int RightEnd ) {
	//將有序的A[L]~A[R-1]和A[R]~A[RightEnd]歸併成一個有序序列
	int LeftEnd, NumElements, Tmp;
	int i;
	LeftEnd = R - 1;	//左邊終點位置
	Tmp = L;	//有序序列的起始位置
	NumElements = RightEnd - L + 1;		//元素總個數
	while( L <= LeftEnd && R <= RightEnd ) {
		if( A[L] <= A[R] )
			TmpA[Tmp++] = A[L++];	//將左邊元素複製到TmpA
		else
			TmpA[Tmp++] = A[R++];	//將右邊元素複製到TmpA
	}
	while( L <= LeftEnd )
		TmpA[Tmp++] = A[L++];	//直接複製左邊剩下的
	while( R <= RightEnd )
		TmpA[Tmp++] = A[R++];	//直接複製右邊剩下的
	for( i = 0; i < NumElements; i++, RightEnd-- )
		A[RightEnd] = TmpA[RightEnd];	//將有序的TmpA複製回A
}
void Msort( ElementType A[], ElementType TmpA[], int L, int RightEnd ) {
	//核心遞迴排序函式
	int Center;
	if( L < RightEnd ) {
		Center = (L + RightEnd) / 2;
		Msort( A, TmpA, L, Center );				//遞迴解決左邊
		Msort( A, TmpA, Center + 1, RightEnd );		//遞迴解決右邊
		Merge( A, TmpA, L, Center + 1, RightEnd );	//合併兩段有序序列
	}
}
void Merge_Sort_Recursion( ElementType A[], int N ) {
	//在這裡開闢TmpA陣列,只需開闢一次
	ElementType *TmpA;
	TmpA = (ElementType *)malloc(N * sizeof(ElementType));
	if( TmpA != NULL ) {
		Msort( A, TmpA, 0, N - 1 );
		free( TmpA );
	}
	else printf("空間不足");
}
//歸併排序 - 迴圈實現; O(N*logN); 穩定
//Merge1相比Merge少了 將TmpA中的元素導回A 的步驟
void Merge1( ElementType A[], ElementType TmpA[], int L, int R, int RightEnd ) {
	//將有序的A[L]~A[R-1]和A[R]~A[RightEnd]歸併成一個有序序列
	int LeftEnd, NumElements, Tmp;
	int i;
	LeftEnd = R - 1;	//左邊終點位置
	Tmp = L;	//有序序列的起始位置
	NumElements = RightEnd - L + 1;		//元素總個數
	while( L <= LeftEnd && R <= RightEnd ) {
		if( A[L] <= A[R] )
			TmpA[Tmp++] = A[L++];	//將左邊元素複製到TmpA
		else
			TmpA[Tmp++] = A[R++];	//將右邊元素複製到TmpA
	}
	while( L <= LeftEnd )
		TmpA[Tmp++] = A[L++];	//直接複製左邊剩下的
	while( R <= RightEnd )
		TmpA[Tmp++] = A[R++];	//直接複製右邊剩下的
}
void Merge_pass( ElementType A[], ElementType TmpA[], int N, int length ) {
	//兩兩歸併相鄰有序子列
	int i, j;
	for( i = 0; i <= N - 2 * length; i += 2 * length )
		Merge1( A, TmpA, i, i + length, i + 2 * length - 1 );
	if( i + length < N )	//歸併最後2個子列
		Merge1( A, TmpA, i, i + length, N - 1 );
	else  //最後只剩1個子列
		for( j = i; j < N; j++ )
			TmpA[j] = A[j];
}
void Merge_Sort_Loop( ElementType A[], int N ) {
	int length;
	ElementType *TmpA;
	length = 1;		//初始化子序列長度
	TmpA = (ElementType *)malloc(N * sizeof(ElementType));
	if ( TmpA != NULL ) {
		while( length < N ) {
			Merge_pass( A, TmpA, N, length );
			length *= 2;
			Merge_pass( TmpA, A, N, length );
			length *= 2;
		}
		free( TmpA );
	}
	else printf("空間不足");
}
int main(){
	ElementType A[MAX];
	int N;
	scanf("%d", &N);
	for(int i = 0; i < N; i++)
		scanf("%ld", &A[i]);
	//Bubble_Sort(A, N);
	//Insertion_Sort(A, N);
	//Shell_Sort(A, N);
	//Shell_Sort_Sedgewick(A, N);
	//Selection_Sort(A, N);
	//Heap_Sort(A, N);
	//Merge_Sort_Recursion(A, N);
	Merge_Sort_Loop(A, N);
	printf("%d", A[0]);
	for(int i = 1; i < N; i++)
		printf(" %d", A[i]);
	system("pause");
	return 0;
}

氣泡排序


插入排序


希爾排序——希爾增量


希爾排序——sedgewick增量


選擇排序


堆排序


歸併排序(遞迴)


歸併排序(迴圈)


從用時上來看,氣泡排序和選擇排序處理大量資料的時候執行超時(其中選擇排序處理10^5數量級的資料時全都超時,冒泡在處理基本有序的資料時速度還行),插入排序也花了很久勉強通過,而希爾排序,堆排序,歸併排序都表現良好

另外感謝 Alumin 提醒,增加了sedgewick增量的取值

int Sedgewick[] = {146305, 64769, 36289, 16001, 8929, 3905, 2161, 929, 505, 209, 109, 41, 19, 5, 1, 0};

執行結果明顯加快

從此題來看,速度排序:歸併-迴圈(51) > 希爾-sedgewick(59) > 堆(60) > 歸併-遞迴(64) > 希爾(86) > 插入(7025) >冒泡(3個超時) > 選擇(5個超時)