09-排序1 排序 (25分)
阿新 • • 發佈:2018-12-24
給定N個(長整型範圍內的)整數,要求輸出從小到大排序後的結果。
本題旨在測試各種不同的排序演算法在各種資料情況下的表現。各組測試資料特點如下:
資料1:只有1個元素; 資料2:11個不相同的整數,測試基本正確性; 資料3:103個隨機整數; 資料4:104個隨機整數; 資料5:105個隨機整數; 資料6:105個順序整數; 資料7:105個逆序整數; 資料8:105個基本有序的整數; 資料9:105個隨機正整數,每個數字不超過1000。輸入格式:
輸入第一行給出正整數NN(≤105),隨後一行給出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個超時)