排序算法總結(C語言版)
1. 插入排序
1.1 直接插入排序
1.2 Shell排序
2. 交換排序
2.1 冒泡排序
2.2 快速排序
3. 選擇排序
1.插入排序
1.1 直接插入排序
將已排好序的部分num[0]~num[i]后的一個元素num[i+1]插入到之前已排好序的部分中去。
代碼:
/* * 直接插入排序,由小到大 */ # define _CRT_SECURE_NO_WARNINGS #include <stdio.h> # define NUM 10 void InsertSort(int num[],int n) { int i, j; int temp; for (i = 1; i < n; i++) { temp = num[i]; for (j = i - 1; j >= 0 && num[j] > temp; j--) { num[j + 1] = num[j]; } num[j + 1] = temp; } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); InsertSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d", num[i]); }
1.2 Shell排序
先將整個待排記錄序列分隔成若干個子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,再對全體記錄進行一次直接插入排序。
子序列選擇:將相隔某個增量的記錄組成一個序列。
“增量序列”選擇注意事項:應使增量序列中的值沒有除1之外的公因子,並且最后一個增量值必須為1。
代碼:
/* * 希爾排序,由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 void ShellSort(int num[], int n) { int i, j, k; int temp; int inc; //增量序列為{5,2,1} for (inc = 5; inc >= 1; inc /= 2) { //如果增量為inc,則需要分成inc組進行直接插入排序 for (k = 0; k < inc; k++) { for (i = k + inc; i < n; i += inc) { temp = num[i]; for (j = i - inc; j >= 0 && num[j] > temp; j -= inc) { num[j + inc] = num[j]; } num[j + inc] = temp; } } } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); ShellSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
2.交換排序
2.1 冒泡排序
代碼:
/* * 冒泡排序,由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 void BubbleSort(int num[], int n) { int i, j; int temp; for (i = 0; i < n - 1; i++) { for (j = 0; j < n - i - 1; j++) { if (num[j] > num[j + 1]) { temp = num[j]; num[j] = num[j + 1]; num[j + 1] = temp; } } } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); BubbleSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
2.2 快速排序
快速排序(Quick Sort)是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以達到整個序列有序。
設要排序的數組是A[0],……,A[N-1],首先任意選取一個數據(通常選用數組的第一個數)作為樞軸,然后將所有比它小的數都放到它前面,所有比它大的數都放到它后面,這個過程稱為一趟快速排序。
代碼1:
/* * 快速排序,由小到大。 * 利用快速排序的基本思想:樞軸左邊的所有元素均不大於樞軸右邊的所有元素。 * 常規的做法是同時從后往前和從前往后遍歷,直至head和tail指向同一個元素。 * 此處的做法是只從前往后遍歷,將比樞軸元素小的元素移動到樞軸之前。 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 void QuickSort(int num[], int head, int tail) { int temp; int pivot = head; int i,j; //一次划分,從head+1開始,將比樞軸元素小的元素移動到樞軸之前。 for (i = head + 1; i <= tail; i++) { if (num[i] < num[pivot]) { temp = num[i]; for (j = i - 1; j >= pivot; j--) { num[j + 1] = num[j]; } num[pivot++] = temp; } } //一次划分之后,將分成的兩個序列分別進行快速排序。 if (head != pivot && head != pivot - 1) { QuickSort(num, head, pivot - 1); } if (pivot != tail && pivot + 1 != tail) { QuickSort(num, pivot + 1, tail); } } void sort(int num[], int n) { QuickSort(num, 0, n - 1); } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); sort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
代碼2:
/* * 快速排序,由小到大。 * 常規的做法:同時從后往前和從前往后遍歷,直至head和tail指向同一個元素。 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 /* * 一次划分函數。 * 函數功能:將序列num[head]~num[tail]利用num[head]作為樞軸,分成兩個子序列。 * 且左邊序列所有元素的值不大於右邊序列所有元素的值。 * 返回值:函數返回樞軸的位置。 */ int Partition(int num[], int head, int tail) { int temp = num[head]; while (head != tail) { while (num[tail] >= temp && head != tail) { tail--; } num[head] = num[tail]; while (num[head] <= temp && head != tail) { head++; } num[tail] = num[head]; } num[head] = temp; return head; } void QuickSort(int num[], int head, int tail) { int pivot; pivot = Partition(num, head, tail); if (head != pivot&&head != pivot - 1) { QuickSort(num, head, pivot - 1); } if (pivot != tail&&pivot + 1 != tail) { QuickSort(num, pivot + 1, tail); } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); QuickSort(num, 0, NUM - 1); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
3.選擇排序
3.1 直接選擇排序
代碼:
/* * 直接選擇排序,由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 void SelectSort(int num[], int n) { int i, j; int temp; for (i = 0; i < n - 1; i++) { for (j = i + 1; j < n; j++) { if (num[i] > num[j]) { temp = num[i]; num[i] = num[j]; num[j] = temp; } } } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); SelectSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d", num[i]); }
3.2 堆排序
堆:n個元素序列{k1,k2...ki...kn},當且僅當滿足下列關系時稱之為堆:
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)
若將和此次序列對應的一維數組(即以一維數組作此序列的存儲結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必為序列中n個元素的最小值(或最大值)。
若在輸出堆頂的最小值之后,使得剩余n-1個元素的序列重建一個堆,則得到n個元素中的次小值。如此反復執行,便能得到一個有序序列,這個過程稱之為堆排序。
問題:
1. 如何由一個無序序列建成一個堆?
2. 如何在輸出堆頂元素之后,調整剩余元素成為一個新的堆?
代碼:
/* * 堆排序,由小到大 * 由小到大排序時,建立的堆為大頂堆; * 由大到小排序時,建立的堆為小頂堆。 */ # define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #define NUM 10 /* * 初始條件:num[root]~num[end]中除了num[root]外均滿足堆的定義。 * 函數功能:調整num[root],使得num[root]~num[end]為大頂堆。 */ void HeapAdjust(int num[],int root,int end) { int i; int temp; for (i = root * 2 + 1; i <= end; i = 2 * i + 1) { if (i + 1 <= end && num[i + 1] > num[i]) { i++; //i指向左右子結點中的大者 } if (num[root] >= num[i]) { break; } else { temp = num[root]; num[root] = num[i]; num[i] = temp; root = i; } } } void HeapSort(int num[], int n) { int i; int temp; //將原始無序序列調整為堆。最后一個結點的雙親結點為最后一個非終端結點。故從i=(n-2)/2開始建堆。 for (i = (n - 2) / 2; i >= 0; i--) { HeapAdjust(num, i, n - 1); } //將堆頂元素(未排序中的最大值)和未排序的最后一個元素交換位置;之后重新建堆。 for (i = n - 1; i >= 1; i--) { temp = num[i]; num[i] = num[0]; num[0] = temp; HeapAdjust(num, 0, i - 1); } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); HeapSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
4.歸並排序
4.1二路歸並排序
歸並:將兩個(或兩個以上)有序表合並成一個新的有序表。
假設初始序列含有n個記錄,則可以看成是n個有序的子序列,每個子序列的長度為1,然而兩兩歸並,得到[n/2]([x]表示不小於x的最小整數)個長度為2或1的有序子序列;再兩兩歸並,.....,如此重復,直至得到一個長度為n的有序序列為止。這種排序算法稱為2路歸並排序。
代碼1:(遞歸形式的歸並排序)
/* * 歸並排序,由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <malloc.h> #define NUM 10 /* * 函數功能:將已排好序的序列sourceArr[head~mid]和sourceArr[mid+1~tail]歸並在一起, * 使整個序列有序。最終的有序序列存儲在數組targetArr中。 */ void Merge(int sourceArr[], int targetArr[], int head, int mid, int tail) { int i, j, k; for (i = head,j = mid + 1,k = head; i <= mid && j <= tail; k++) { if (sourceArr[i] <= sourceArr[j]) { targetArr[k] = sourceArr[i++]; } else { targetArr[k] = sourceArr[j++]; } } if (i <= mid) { for (; k <= tail; k++) { targetArr[k] = sourceArr[i++]; } } if (j <= tail) { for (; k <= tail; k++) { targetArr[k] = sourceArr[j++]; } } } /* * 歸並排序(遞歸): * 如果head!=tail,則對sourceArr[head~mid]和sourceArr[mid+1~tail]分別進行歸並排序, * 並將排序后的兩個序列歸並在一起,使整體有序。 */ void MergeSort(int sourceArr[], int targetArr[], int head, int tail) { int mid; int *tempArr; tempArr = (int *)malloc(sizeof(int)*(tail - head + 1)); if (head == tail) { targetArr[head] = sourceArr[head]; } else { mid = (head + tail) / 2; MergeSort(sourceArr, tempArr, head, mid); MergeSort(sourceArr, tempArr, mid + 1, tail); Merge(tempArr, targetArr, head, mid, tail); } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); MergeSort(num, num, 0, NUM-1); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
代碼2:(非遞歸形式的歸並排序)
/* * 歸並排序(非遞歸),由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <malloc.h> #define NUM 10 /* * 將2路有序序列歸並為1路。形參中只有sourceArr,沒有targetArr。 */ void Merge(int sourceArr[], int head, int mid, int tail) { int i, j, k; int *targetArr; targetArr = (int *)malloc(sizeof(int)*(tail - head + 1)); for (i = head, j = mid + 1, k = 0; i <= mid&&j <= tail; k++) { if (sourceArr[i] > sourceArr[j]) { targetArr[k] = sourceArr[j++]; } else { targetArr[k] = sourceArr[i++]; } } if (i <= mid) { for (; k < tail - head + 1; k++) { targetArr[k] = sourceArr[i++]; } } if (j <= tail) { for (; k < tail - head + 1; k++) { targetArr[k] = sourceArr[j++]; } } for (i = head,k = 0; i <= tail; i++,k++) { sourceArr[i] = targetArr[k]; } } void MergeSort(int num[],int n) { int interval; int head; for (interval = 2; interval <= n; interval *= 2) { for (head = 0; head + interval <= n; head += interval) { Merge(num, head, head + interval / 2 - 1, head + interval - 1); } Merge(num, head, head + interval / 2 - 1, n - 1);//處理head + interval > n的情況 } Merge(num, 0, interval / 2 - 1, n - 1);//處理interval > n的情況 } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); MergeSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
4.2 自然合並排序
自然合並排序是歸並排序算法的一種改進。
自然合並排序:對於初始給定的數組,通常存在多個長度大於1的已自然排好序的子數組段。例如,若數組a中元素為{4,8,3,7,1,5,6,2},則自然排好序的子數組段有{4,8},{3,7},{1,5,6},{2}。用一次對數組a的線性掃描就足以找出所有這些排好序的子數組段。然后將相鄰的排好序的子數組段兩兩合並,構成更大的排好序的子數組段({3,4,7,8},{1,2,5,6})。繼續合並相鄰排好序的子數組段,直至整個數組已排好序。
代碼:
/* * 自然合並排序,由小到大 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <malloc.h> #define NUM 10 int heads[NUM]; /* * 掃描數組,將已排好序的子數組段的段首加入數組heads中。 * 函數返回段首的總數。 */ int check(int num[], int n) { int num_of_head = 1; int i; heads[0] = 0; for (i = 1; i < n; i++) { if (num[i] < num[i - 1]) { heads[num_of_head++] = i; } } return num_of_head; } /* * 將2路有序序列歸並為1路。 */ void Merge(int sourceArr[], int head, int mid, int tail) { int i, j, k; int *targetArr; targetArr = (int *)malloc(sizeof(int)*(tail - head + 1)); for (i = head, j = mid + 1, k = 0; i <= mid&&j <= tail; k++) { if (sourceArr[i] > sourceArr[j]) { targetArr[k] = sourceArr[j++]; } else { targetArr[k] = sourceArr[i++]; } } for (; i <= mid; k++, i++) { targetArr[k] = sourceArr[i]; } for (; j <= tail; k++, j++) { targetArr[k] = sourceArr[j]; } for (i = head,k = 0; i <= tail; i++, k++) { sourceArr[i] = targetArr[k]; } } void MergeSort(int num[], int n) { int num_of_head; int i; while (1) { num_of_head = check(num, n); if (num_of_head == 1) { break; } else { for (i = 0; i < num_of_head - 1; i += 2) { if (i + 2 <= num_of_head - 1) { Merge(num, heads[i], heads[i + 1] - 1, heads[i + 2] - 1); } else { Merge(num, heads[i], heads[i + 1] - 1, n - 1); } } } } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); MergeSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
5.分布排序
5.1 基數排序
第一步
以LSD為例,假設原來有一串數值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根據個位數的數值,在走訪數值時將它們分配至編號0到9的桶子中:
0 |
|
1 |
81 |
2 |
22 |
3 |
73 93 43 |
4 |
14 |
5 |
55 65 |
6 |
|
7 |
|
8 |
28 |
9 |
39 |
第二步
接下來將這些桶子中的數值重新串接起來,成為以下的數列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再進行一次分配,這次是根據十位數來分配:
0 |
|
1 |
14 |
2 |
22 28 |
3 |
39 |
4 |
43 |
5 |
55 |
6 |
65 |
7 |
73 |
8 |
81 |
9 |
93 |
第三步
接下來將這些桶子中的數值重新串接起來,成為以下的數列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
這時候整個數列已經排序完畢;如果排序的對象有三位數以上,則持續進行以上的動作直至最高位數為止。
LSD的基數排序適用於位數小的數列,如果位數多的話,使用MSD的效率會比較好。MSD的方式與LSD相反,是由高位數為基底開始進行分配,但在分配之后並不馬上合並回一個數組中,而是在每個“桶子”中建立“子桶”,將每個桶子中的數值按照下一數位的值分配到“子桶”中。在進行完最低位數的分配后再合並回單一的數組中。
代碼1(LSD):
/* * 基數排序,由小到大。 * 假設待排序數最多只有3位數。 * 最低位優先基數排序(LSD) */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <malloc.h> #include <string.h> #define NUM 10 /* * 用於獲取整數num的個位、十位、或者百位 */ int getdigit(int num, int power) { int result; switch (power) { case 1:result = num % 10; break; //個位 case 2:result = num % 100 / 10; break; //十位 case 3:result = num / 100; break; //百位 } return result; } void RadixSort(int num[], int n) { int count[10] = {0}; int *bucket; int i; int key; int digit; bucket = (int *)malloc(sizeof(int) * n); //key從1到3表示,依次比較個位、十位、百位。 for (key = 1; key <= 3; key++) { //key=1時,count[0~9]分別表示個位數為0,1...,9的元素的數量 for (i = 0; i < n; i++) { digit = getdigit(num[i], key); count[digit]++; } //key=1時,count[i]表示個位數為0~i的元素的總數量 for (i = 1; i < 10; i++) { count[i] = count[i] + count[i - 1]; } //從后往前遍歷序列,將元素放入對應的子桶中。從后往前是為了保證穩定性。 for (i = n - 1; i >= 0; i--) { digit = getdigit(num[i], key); bucket[--count[digit]] = num[i]; } for (i = 0; i < n; i++) { num[i] = bucket[i]; } //count數組清零。 memset(count, 0, sizeof(int) * 10); } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); RadixSort(num, NUM); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
代碼2(MSD):
/* * 基數排序,由小到大。 * 假設元素最高位3位數。 * 最高位優先排序(MSD)。 */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <malloc.h> #include <string.h> #define NUM 10 int getdigit(int num, int power) { int result; switch (power) { case 1:result = num % 10; break; case 2:result = num % 100 / 10; break; case 3:result = num / 100; break; } return result; } void RadixSort(int num[], int head,int tail,int key) { int i,j; int digit; int *bucket; int count[10] = {0}; int count_backup[10]; int left, right; bucket = (int*)malloc(sizeof(int)*(tail - head + 1)); for (i = head; i <= tail; i++) { digit = getdigit(num[i], key); count[digit]++; } for (i = 1; i < 10; i++) { count[i] = count[i] + count[i - 1]; } memcpy(count_backup, count, sizeof(int) * 10); //備份count數組中的數據 for (i = tail; i >= head; i--) { digit = getdigit(num[i], key); bucket[--count[digit]] = num[i]; } for (i = head, j = 0; i <= tail; i++, j++) { num[i] = bucket[j]; } free(bucket); if(key != 1) { //對每個子桶中的數據分別按下一位進行基數排序。 key--; if (count_backup[0] != 0 && count_backup[0] != 1) { RadixSort(num, head, head + count_backup[0] - 1, key); } for (i = 0; i <= 8; i++) { left = head + count[i]; right = head + count[i + 1] - 1; if (left < right) { RadixSort(num, left, right, key); } } } } void main() { int num[NUM]; int i; for (i = 0; i < NUM; i++) scanf("%d", num + i); RadixSort(num, 0, NUM - 1, 3); for (i = 0; i < NUM; i++) printf("%-3d ", num[i]); }
pdf版下載:http://pan.baidu.com/s/1bn1XL3T
工程文件打包下載:http://pan.baidu.com/s/1pJqegyf (開發環境為visual studio 2013)