排序算法的稳定性定义:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
稳定性判定方法:
排序算法——冒泡法排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
最坏时间复杂度 O(n^2)
冒泡排序是稳定的排序算法。
#include <stdio.h>
#define bool int
#define false 0
#define true 1
void bubble_sort(int a[], int num)
{
int i, j, k;
for (i = 0; i < num; i++) {
bool flag = false; /* mark for need bubble sort*/
for (j = 0; j < num - i - 1; j++) {
if (a[j] > a[j+1]) {
flag = true;
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
if (!flag) /* if this turn have no exchange.end sort*/
break;
print(a, num);
}
}
void print(int a[], int num)
{
for (k = 0; k < num; k++)
printf("%d ", a[k]);
printf("/n");
}
int main()
{
int i, num;
int a[] = {4,7,3,8,2,1,9,6,5,10};
num = sizeof(a) / sizeof(int);
bubble_sort(a, num);
print(a, num);
return 0
}
第10行设置了flag,一旦发现不用再交换元素则终止循环,提高了效率。排序算法——选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列起始位置。以此类推,直到所有元素均排序完毕。
最坏时间复杂度 O(n^2)
选择排序是不稳定的排序算法。例如5 8 5 2 9。第一次选择5 与 2交换 ,之后两个5的相对位置就改变了。
#include <stdio.h>
void select_sort(int a[], int num)
{
int i, j, k, min, temp;
for (i = 0; i < num - 1; i++) {
min = i;
for (j = i + 1; j < num; j++) {
if (a[min] > a[j]) {
min = j;
}
}
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
}
void print(int a[], int num)
{
int k;
for (k = 0; k < num; k++)
printf("%d ", a[k]);
printf("/n");
}
int main()
{
int i, num;
int a[10] = {1,7,5,8,4,2,6,9,3,10};
num = sizeof(a) / sizeof(int);
select_sort(a, num);
print(a, num);
return 0;
}
8-18行代码,每次遍历记录最小值的下标,最终选取的是当前剩余序列的最小值。
排序算法——插入排序
插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。(插入排序的前提是逐步构造有序表)
最坏时间复杂度 O(n^2)
插入排序是稳定的排序算法。
#include <stdio.h>
void insert_sort(int a[], int num)
{
int i, j, temp;
for (i = 1; i < num; i++) //共需进行num-1次插入排序操作
{
temp = a[i]; //记录插入新元素的值temp
for (j = i - 1; j >= 0 && a[j] > temp; j--) //由后往前从已有序的序列中寻找temp放置的位置
{
a[j+1] = a[j];
}
a[j+1] = temp; //将新插入元素a[i]放到找到的位置
}
}
void print(int a[], int num)
{
int i;
for (i = 0; i < num; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
int i, num;
int a[10] = {1,7,5,8,4,2,6,9,3,10};
num = sizeof(a) / sizeof(int);
insert_sort(a, num);
print(a, num);
return 0;
}
排序算法——二分插入排序
二分插入排序用于在插入排序中减少比较操作的位置,是插入排序的一个变种。
最坏时间复杂度 O(n^2)
#include <stdio.h>
void insert_sort(int a[], int num)
{
int i, j, k, temp, left, right, middle;
for (i = 1; i < num; i++) {
temp = a[i];
left = 0; /*search for insert pos*/
right = i - 1;
while (left <= right) //二分法从,查找新插入的元素待放置位置区间[left,right]
{
middle = (left + right) / 2;
if (temp > a[middle])
left = middle + 1;
else
right = middle -1;
}
//最终while循环中left会出现在right的右边,因此temp>=a[right],且temp<=a[left]
printf("left=%d right=%d\n",left , right);
for (j = i - 1; j > right; j--)
a[j+1] = a[j];
a[left] = temp;
}
}
main()
{
int i, num;
int a[] = {8,2,9,5,6,7,5};
num = sizeof(a) / sizeof(int);
insert_sort(a, num);
for (i = 0; i < num; i++)
printf("%d ", a[i]);
printf("\n");
}
排序算法——合并排序
合并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
最坏时间复杂度 O(nlogn), 合并排序是稳定的排序算法
#include <stdio.h>
#include <stdlib.h>
void merge(int a[], int p, int q, int r)
{
int i, j, k;
int *temp = (int *)malloc((r - p + 1) * sizeof(int));
i = p;
j = q + 1;
k = 0;
while ((i <= q) && (j <= r))
{
if (a[i] < a[j])
temp[k] = a[i++];
else
temp[k] = a[j++];
k++;
}
while (i <= q)
temp[k++] = a[i++];
while (j <= r)
temp[k++] = a[j++];
for (i = 0; i < r - p + 1; i++)
{
a[p+i] = temp[i];
}
free(temp);
}
void merge_sort(int a[], int p, int r)
{
int q;
if (p < r) {
q = (p + r) / 2;
merge_sort(a, p, q);
merge_sort(a, q + 1, r);
merge(a, p, q, r);
}
}
int main()
{
int a[] = {5,2,4,7,1,3,2,6}, i;
int p = 0, r = sizeof(a) / sizeof(int);
merge_sort(a, p, r);
for (i = 0; i < r; i++)
printf("%d ", a[i]);
return 0;
}
排序算法——堆排序(递归版本)
堆积排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法。堆积树是一个近似完整二叉树的结构,并同时满足堆积属性:即子结点的键值或索引总是小于(或者大于)它的父结点。在堆积树的数据结构中,堆积树中的最大值总是位于根节点。堆积树中定义以下几种操作:
1.最大堆积调整(max_heapify):将堆积树的末端子结点作调整,使得子结点永远小于父结点
2.建立最大堆积(build_max_heap):将堆积树所有数据重新排序
3.堆积排序(heap_sort):移除位在第一个数据的根结点,并做最大堆积调整的递归运算
最坏时间复杂度 O(nlogn),堆排序是不稳定的排序算法
#include <stdio.h>
void max_heapify(int a[], int parent, int heap_size)
{
int l, r, largest, temp;
l = (parent << 1) + 1;
r = l + 1;
largest = parent;
if (l <= heap_size && a[l] > a[largest])
{
largest = l;
}
if (r <= heap_size && a[r] > a[largest])
{
largest = r;
}
if (largest != parent)
{
temp = a[parent];
a[parent] = a[largest];
a[largest] = temp;
max_heapify(a, largest, heap_size);
}
}
void build_max_heap(int a[], int heap_size)
{
int i;
for (i = heap_size / 2; i >= 0; i--)
{
max_heapify(a, i, heap_size);
}
}
void heap_sort(int a[], int heap_size)
{
int i, temp;
build_max_heap(a, heap_size);
for (i = heap_size; i >= 0; i--)
{
temp = a[i];
a[i] = a[0];
a[0] = temp;
heap_size--;
max_heapify(a, 0, heap_size);
}
}
int main()
{
int a[] = {46,79,56,38,40,84}, i;
int size = sizeof(a) / sizeof(int);
heap_sort(a, size - 1);
for (i = 0; i < size; i++) {
printf("%d ", a[i]);
}
return 0;
}
排序算法——快速排序
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。步骤为:
1.从数列中挑出一个元素,称为 "基准"(pivot),
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割之后,该基准是它的最后位置。这个称为分割(partition)操作。
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
最坏时间复杂度: O(n^2),平均时间复杂度:O(nlogn),快速排序是不稳定的排序算法。
#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int a[], int p, int q)
{
int x, i, j;
x = a[p]; /* x as a pivot */
i = p;
for (j = p + 1; j < q; j++)
{
if (a[j] <= x)
{
i++;
swap(&a[i], &a[j]);
}
}
swap(&a[p], &a[i]);
return i;
}
void quick_sort(int a[], int p, int q)
{
if (p < q)
{
int r = partition(a, p, q);
quick_sort(a, p, r); /* sort sub list a[p, r) */
quick_sort(a, r+1, q);/* sort sub list a[r+1,q) */
}
}
int main()
{
int a[] = {6, 10, 13, 5, 8, 3, 2, 11}, i;
int size = sizeof(a) / sizeof(int);
quick_sort(a, 0, size);
for (i = 0; i < size; i++)
printf("%d ", a[i]);
return 0;
}
注意:
code 16:j <= q
code 30:quick_sort(a, p, r-1)
code 31:quick_sort(a, r+1, q)
排序算法——计数排序
1、输入的线性表的元素属于有限偏序集S;
2、设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。
在这两个条件下,计数排序的复杂性为O(n+k)。
计数排序算法的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。
计数排序是稳定的排序算法。
#include <stdio.h>
#include <stdlib.h>
void counting_sort(int a[], int size)
{
int i, j, min, max, range;
int *c, *b;
min = max = a[0];
for (i = 1; i < size; i++)
{/* find the range of input list */
if (a[i] < min)
{
min = a[i];
}
else if (a[i] > max)
{
max = a[i];
}
}
range = max - min + 1;
c = (int *)malloc(range * sizeof(int));/* for temporary storage */
b = (int *)malloc(size * sizeof(int)); /* for storage sorted list */
for (i = 0; i < range; i++)
{
c[i] = 0;
}
/* c[i] include the num of elements which equal to i */
for (j = 0; j < size; j++)
{
c[a[j] - min] += 1;
}
/* c[i] include the num of elements which less or equal than i */
for (i = 1; i < range; i++)
{
c[i] += c[i-1];
}
for (j = size - 1; j >= 0; j--)
{
b[c[a[j] - min] - 1] = a[j];/* for c style */
c[a[j] - min] -= 1;
}
/* input sorted list to array a. not necessary, we can return the address of array b */
for (i = 0; i < size; i++)
a[i] = b[i];
free(b);
free(c);
}
main()
{
int a[] = {4, 1, 3, 4, 3}, i;
int size = sizeof(a) / sizeof(int);
counting_sort(a, size);
for (i = 0; i < size; i++)
printf("%d ", a[i]);
}
由于C数组的下标由0开始,这里用到下标转换有点麻烦
a:index 0 1 2 3 4
value 4 1 3 4 3
c:index 0 1 2 3
value 1 0 2 2 (code 26-28)
value 1 1 3 5 (code 31-33)
b:index 0 1 2 3 4
value 1 3 3 4 4
排序算法——基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
基数排序时间复杂度: O(d(n+k)) n个d位数,每个数位有k种取值
#include <stdio.h>
#include <stdlib.h>
int max_bit(int a[], int size) /* get the max bit of number */
{
int i, max, b;
for (i = 1, max = a[0]; i < size; i++)
{
if (a[i] > max)
{
max = a[i];
}
}
b = 1;
while (max / 10)
{
b++;
max /= 10;
}
return b;
}
void radix_sort(int a[], int size)
{
int d = max_bit(a, size);
int i, j, k, range, radix;
int *c, *b;
range = 10; /* for counting sort, the range of every bit is 0 - 9 */
radix = 1;
c = (int *)malloc(sizeof(int) * range);
b = (int *)malloc(sizeof(int) * size);
for (i = 0; i < d; i++, radix *= 10)
{
/* use counting sort */
/* clear count before every sort */
for (j = 0; j < range; j++)
{
c[j] = 0;
}
/* c[k] content the num of elements which equal to k */
for (j = 0; j < size; j++)
{
k = (a[j] / radix) % 10;
c[k]++;
}
/* c[j] content the num of elements which equal or less than j */
for (j = 1; j < range; j++)
c[j] += c[j-1];
/* put a[j] into the space of b[c[k] - 1] */
for (j = size - 1; j >= 0; j--)
{
k = (a[j] / radix) % 10;
b[c[k] - 1] = a[j];
c[k]--;
}
/* copy the 'sorted' list to a[j] */
for (j = 0; j < size; j++)
a[j] = b[j];
}
free(c);
free(b);
}
main()
{
int a[] = {329, 457, 657, 839, 436, 720, 355}, i;
int size = sizeof(a) / sizeof(int);
radix_sort(a, size);
for (i = 0; i < size; i++)
printf("%d ", a[i]);
}
排序算法——桶排序
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10, i*10]的整数,i = 1,2,..100。总共有100个桶。然后对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 然后再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。最后依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。
假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果对每个桶中的数字采用快速排序,那么整个算法的复杂度是O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)
从上式看出,当m接近n的时候,桶排序复杂度接近O(n)
当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern void quick_sort(int a[], int p, int q);/* not necessary */
struct barrel
{
int node[10];
int count;/* the num of node */
};
void bucket_sort(int data[], int size)
{
int max, min, num, pos;
int i, j, k;
struct barrel *pBarrel;
max = min = data[0];
for (i = 1; i < size; i++)
{
if (data[i] > max)
{
max = data[i];
}
else if (data[i] < min)
{
min = data[i];
}
}
num = (max - min + 1) / 10 + 1;
pBarrel = (struct barrel*)malloc(sizeof(struct barrel) * num);
memset(pBarrel, 0, sizeof(struct barrel) * num);
/* put data[i] into barrel which it belong to */
for (i = 0; i < size; i++)
{
k = (data[i] - min + 1) / 10;/* calculate the index of data[i] in barrel */
(pBarrel + k)->node[(pBarrel + k)->count] = data[i];
(pBarrel + k)->count++;
}
pos = 0;
for (i = 0; i < num; i++)
{
quick_sort((pBarrel+i)->node, 0, (pBarrel+i)->count);/* sort node in every barrel */
for (j = 0; j < (pBarrel+i)->count; j++)
{
data[pos++] = (pBarrel+i)->node[j];
}
}
free(pBarrel);
}
main()
{
int data[] = {78, 17, 39, 26, 72, 94, 21, 12, 23, 91}, i;
int size = sizeof(data) / sizeof(int);
bucket_sort(data, size);
for (i = 0; i < size; i++)
printf("%d ", data[i]);
}
注意:注意这里的快速排序用上面的版本。
排序算法——希尔排序
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
对有n个元素的可比较资料,先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-1<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
该方法实质上是一种分组插入方法。
在具体实现上,希尔排序的性能与所选取的分组长度序列有很大关系
#include <stdio.h>
void shell_sort(int a[], int size)
{
int gap, temp;
int i, j;
for (gap = size / 2;gap > 0; gap /= 2)
{
for (i = gap; i < size; i++)
{
for (j = i - gap; j >= 0 && a[j] > a[j+gap]; j-=gap)
{
temp = a[j];
a[j] = a[j+gap];
a[j+gap] = temp;
}
}
}
}
main()
{
int a[] = {49, 38, 65, 97, 76, 13, 27, 49, 55, 4}, i;
int size = sizeof(a) / sizeof(int);
shell_sort(a, size);
for (i = 0; i < size; i++)
printf("%d ", a[i]);
}
各种排序算法总结
1、这是《漫谈经典排序算法》最后一篇,总结了各种排序算法的时间复杂度、稳定性、辅助空间、约束条件。
各种排序算法的解析请参考如下:
《漫谈经典排序算法:五、线性时间排序(计数、基数、桶排序)》
2、各种算法分析如下
更正:归并排序是稳定的内部排序