首页 > 代码库 > 排序算法总结(C语言版)
排序算法总结(C语言版)
排序算法总结(C语言版)
1. 插入排序
1.1 直接插入排序
1.2 Shell排序
2. 交换排序
2.1 冒泡排序
2.2 快速排序
3. 选择排序
3.1 直接选择排序
3.2 堆排序
4. 归并排序
4.1 二路归并排序
4.2 自然合并排序
5. 分布排序
5.1 基数排序
1.插入排序
1.1 直接插入排序
将已排好序的部分num[0]~num[i]后的一个元素num[i+1]插入到之前已排好序的部分中去。
代码:
/* * 直接插入排序,由小到大 */# define _CRT_SECURE_NO_WARNINGS#include <stdio.h># define NUM 10void 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 10void 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 10void 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 10void 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 10void 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; i <= end; i *= 2) { 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开始建堆。 for (i = n / 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 10int 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 < n; 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 10int 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