排序算法:
/*
* sort.h
*
* Created on: 2012-6-5
* Author: Mzaort
*/
#ifndef SORT_H_
#define SORT_H_
void selectionSort(int *, int);
void bubbleSort(int *, int);
void insertionSort(int *, int);
void countingSort(int *, int, int);
void radixSort(int *, int);
void mergeSort(int *, int);
void quickSort(int *, int);
void bucketSort(int *, int, int, int);
void arrPrint(int *, int);
#endif /* SORT_H_ */
上面方法名就是对应的算法,不解释。
具体的实现为:
/*
============================================================================
Name : Sort.c
Author :
Version :
Copyright : Your copyright notice
Description : Hello World in C, Ansi-style
所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。当待排序记录的关键字都不相同时,
排序结果是惟一的,否则排序结果不惟一。 在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有
相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,
则称这种排序方法是不稳定的。 要注意的是,排序算法的稳定性是针对所有输入实例而言的。即在所有可能的输入
例中,只要有一个实例使得算法不满足稳定性要求,则该排序算法就是不稳定的。
============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include "sort.h"
#include <string.h>
/*
* 直接选择排序的过程是:首先在所有记录中选出序码最小的记录,把它与第1个记录交换,然后在其余的记录内
* 选出排序码最小的记录,与第2个记录交换......依次类推,直到所有记录排完为止。
无论文件初始状态如何,在第i趟排序中选出最小关键字的记录,需要做n-i次比较,因此,总的比
较次数为n(n-1)/2=O(n^2)。当初始文件为正序时,移动次数为0;文件初态为反序时,每趟排序均要执行
交换操作,总的移动次数取最大值3(n-1)。直接选择排序的平均时间复杂度为O(n^2)。直接选择排序是不稳定的。
* */
void selectionSort(int * arr, int len) {
int i, j;
for (i = 0; i < len - 1; i++) {
int min = i;
int tmp = arr[min];
for (j = i + 1; j < len; j++) {
if (tmp > arr[j]) {
min = j;
tmp = arr[min];
}
}
if (min != i) {
arr[min] = arr[i];
arr[i] = tmp;
}
}
}
void bubbleSort(int * arr, int len) {
int i, j;
for (i = 0; i < len - 1; i++) {
for (j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
/*
* 插入排序的基本思想是每步将一个待排序的记录按其排序码值的大小,插到前面已经
* 排好的文件中的适当位置,直到全部插入完为止。插入排序方法主要有直接插入排序和希尔排序。
*
* 插入排序的过程为:在插入第i个记录时,R1,R2,..Ri-1已经排好序,将第i个记录的排序码
* Ki依次和R1,R2,..,Ri-1的排序码逐个进行比较,找到适当的位置。使用直接插入排序,对于具
* 有n个记录的文件,要进行n-1趟排序。
*/
void insertionSort(int * arr, int len) {
int i, j;
for (i = 1; i < len; i++) {
int tmp = arr[i];
for (j = i - 1; j >= 0 && arr[j] > tmp; j--)
arr[j + 1] = arr[j];
arr[j + 1] = tmp;
}
}
/*
* 希尔(Shell)排序的基本思想是:先取一个小于n的整数d1作为第一个增量把文件的全部记录
* 分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,
* 取得第二个增量d2<d1重复上述的分组和排序,直至所取的增量di=1,即所有记录放在同一组中
* 进行直接插入排序为止。该方法实质上是一种分组插入方法。一般取d1=n/2,di+1=di/2。
* 如果结果为偶数,则加1,保证di为奇数。 希尔排序是不稳定的,希尔排序的执行时间依赖于增量
* 序列,其平均时间复杂度为O(n^1.3).
*/
void shellSort(int arr[], int len) {
int i, j, skip, tmp;
/*control skip to be odd*/
skip = len >> 1;
skip += 1 - (skip & 1);
while (skip > 0) {
for (j = skip; j < len; j++) {
tmp = arr[j];
i = j - skip;
while (i >= 0 && arr[i] > tmp) {
arr[i + skip] = arr[i];
i = i - skip;
}
arr[i + skip] = tmp;
}
if (skip == 1)
break;
skip = skip >> 1;
skip += 1 - (skip & 1);
}
}
/*如果给一个分布于区间[min, max)的随机序列排序,可以考虑使用计数排序,max-min 越小说
* 明分布越集中,此时使用计数排序效果就越好,计数排序是一种稳定的排序算法。一般而言,计
* 数排序的时间复杂度为O(n),空间复杂度O(n),从理论上来看,它比时间复杂度O(nlogn)的算
* 法明显快一些。
使用计数排序算法对一个正整数序列进行升序排序时,假设对于某个元素比它小的元素个数为 i(其
中0≤i<max,获取该信息无需借助比较运算),则排序后该元素就应该位于数组下标为i的位置。现
在的问题是,如果对于等于某个值的正整数不止一个该如何确定各自位置呢?这个问题在实现中容易解决。
为了能够正常排序,需要用max-min 个辅助空间记录不同正整数的个数,由于排序无法原地进行,还
需要开辟等大的辅助空间容纳排序后的所有正整数,一般而言,max-min 不大于待排序的正整数个数或
与之相当,可见空间复杂度为O(n + (max-min)) = O(n)。*/
void countingSort(int * arr, int len, int max) {
// int i, j, cur;
// int c[MAXRANGE] = {0};
//
// for(i = 0; i < len; i++) c[arr[i]] ++;
// cur = 0;
// for(i = 0; i < MAXRANGE; i++){
// for(j = 0; j < c[i]; j++)
// arr[cur++] = i;
// }
int i;
int nMax = max + 1;
int *c = (int *) calloc(nMax, sizeof(int));
int * arrtmp = (int *) malloc(sizeof(int) * len);
for (i = 0; i < len; i++)
c[arr[i]]++;
for (i = 1; i < nMax; i++)
c[i] += c[i - 1];
for (i = len - 1; i >= 0; i--) {
c[arr[i]]--;
arrtmp[c[arr[i]]] = arr[i];
}
memcpy(arr, arrtmp, len * sizeof(int));
free(arrtmp);
free(c);
}
int radixCountSort(int * arr, int len, int rad) {
int base = 10;
int i, result = 0;
int * arr2 = (int *) malloc(len * sizeof(int));
for (i = 0; i < len; i++) {
int tmp = arr[i] / rad;
if (tmp != 0)
result++;
arr2[i] = tmp % base;
}
if (!result)
return result;
int nMax = base;
int *c = (int *) calloc(nMax, sizeof(int));
int * arrtmp = (int *) malloc(sizeof(int) * len);
for (i = 0; i < len; i++)
c[arr2[i]]++;
for (i = 1; i < nMax; i++)
c[i] += c[i - 1];
for (i = len - 1; i >= 0; i--) {
c[arr2[i]]--;
arrtmp[c[arr2[i]]] = arr[i];
}
memcpy(arr, arrtmp, len * sizeof(int));
free(arrtmp);
free(arr2);
free(c);
return result;
}
void radixSort(int * arr, int len) {
int base = 10;
int start = 1;
while (radixCountSort(arr, len, start) != 0)
start *= base;
}
void merge(int * arr, int start, int middle, int end, int * tmp) {
int i, j, cur = start;
i = start;
j = middle + 1;
while (i <= middle && j <= end) {
if (arr[i] < arr[j]) {
tmp[cur++] = arr[i++];
} else {
tmp[cur++] = arr[j++];
}
}
while (i <= middle) {
tmp[cur++] = arr[i++];
}
while (j <= end) {
tmp[cur++] = arr[j++];
}
memcpy(arr + start, tmp + start, (end - start + 1) * sizeof(int));
}
void subMergeSort(int * arr, int start, int end, int * tmp) {
if (start < end) {
int middle = (start + end) / 2;
subMergeSort(arr, start, middle, tmp);
subMergeSort(arr, middle + 1, end, tmp);
merge(arr, start, middle, end, tmp);
}
}
/*
* 归并排序是将两个或两个以上的有序子表合并成一个新的有序表。初始时,把含有n个结点的待排序
* 序列看作由n个长度都为1的有序子表组成,将它们依次两两归并得到长度为2的若干有序子表,再对它
* 们两两合并。直到得到长度为n的有序表,排序结束。
归并排序是一种稳定的排序,可用顺序存储结构,也易于在链表上实现,对长度为n的文件,
需进行log2n趟二路归并,每趟归并的时间为O(n),故其时间复杂度无论是在最好情况下还是在最坏情况
下均是O(nlog2n)。归并排序需要一个辅助向量来暂存两个有序子文件归并的结果,故其辅助空间复杂度
为O(n),显然它不是就地排序。
* */
void mergeSort(int * arr, int len) {
int * tmp = (int *) malloc(sizeof(int) * len);
subMergeSort(arr, 0, len - 1, tmp);
free(tmp);
}
int partition(int * arr, int start, int end) {
int i, j, tmp;
int pivot = arr[end];
i = start - 1;
for (j = start; j < end; j++) {
if (arr[j] < pivot) {
i++;
tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
int result = i + 1;
tmp = arr[j];
arr[j] = arr[result];
arr[result] = tmp;
return result;
}
void subQuickSort(int * arr, int start, int end) {
if (start < end) {
int pivot = partition(arr, start, end);
subQuickSort(arr, start, pivot - 1);
subQuickSort(arr, pivot + 1, end);
}
}
/*
* 快速排序是对冒泡排序的一种改进。它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,
* 其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快
* 速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
* */
void quickSort(int * arr, int len) {
subQuickSort(arr, 0, len - 1);
}
void bucketSort(int * arr, int len, int bucketNum, int max) {
typedef struct node {
int key;
struct node * next;
} KeyNode;
int i, j;
KeyNode **bucket_table = (KeyNode **) malloc(bucketNum * sizeof(KeyNode *));
for (i = 0; i < bucketNum; i++) {
bucket_table[i] = (KeyNode *) malloc(sizeof(KeyNode));
bucket_table[i]->key = 0;
bucket_table[i]->next = NULL;
}
for (j = 0; j < len; j++) {
KeyNode *node = (KeyNode *) malloc(sizeof(KeyNode));
node->key = arr[j];
node->next = NULL;
//映射函数计算桶号
int index = node->key / (max / bucketNum + 1);
//初始化P成为桶中数据链表的头指针
KeyNode *p = bucket_table[index];
//该桶中还没有数据
if (p->key == 0) {
bucket_table[index]->next = node;
(bucket_table[index]->key)++;
} else {
//链表结构的插入排序
while (p->next != NULL && p->next->key <= node->key)
p = p->next;
node->next = p->next;
p->next = node;
(bucket_table[index]->key)++;
}
}
KeyNode * k;
for (i = 0, j = 0; i < bucketNum; i++)
for (k = bucket_table[i]->next; k != NULL; k = k->next)
arr[j++] = k->key;
}
void arrPrint(int * arr, int len) {
int i;
for (i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如基数排序。
各种排序方法比较
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或
归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。但从单个记录起进行两两归并的 排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。