选择排序曾经是我的最爱,但自从认识了快速排序之后,咳咳…我依然坚持用选择排序…只是…感觉没有以前那么爱了…
如我上一篇博文写到的,选择排序的代码非常的简单,对于稍有一点代码经验的程序猿来说,30秒就能搞定一个选择排序算法。相比之下,快速排序的代码要复杂一些,但是其时间复杂度也更优一些。一般情况下,在待排序数组元素达到36个以上时,快速排序就比选择排序耗时更短了。
快速排序的平均时间复杂度是O(n*logn),但是在最糟糕的情况下,也即是待排序数组本身已经完全有序的情况下,快速排序的时间复杂度将达到O(n^2),这个时候快速排序算法的表现就不如选择排序了。咦?选择排序的时间复杂度不也是O(n^2)吗?为什么说快速排序在最糟糕的情况下,还比不过选择排序呢?——别忘了,快速排序需要一定的辅助存储空间,其空间复杂度是O(logn),而选择排序的空间复杂度是O(1),因此综合考量起来,在最糟糕的情况下,快速排序的确不如选择排序了呢!
唠叨了很多,现在来进一步介绍快速排序。
快速排序,简称“快排”,是一种不稳定的排序算法。我们以升序排序为例,其基本思想是:
1、在数组中选择一个基准值,将数组划分成两部分,要求左边的部分都比基准值小,右边的部分都比基准值大;
2、分别对左、右两部分执行第1步操作,直到无法再划分下去为止。
如果你有一定的编程基础,但之前又从未听说过快速排序,那么你现在应该会猜到:快速排序运用了分治思想,而代码的实现可以采用递乌龟的方式。嗯~我经常把“递归”说成是“递乌龟”…事不宜迟,先上代码:
void SwapInt(int &a, int &b) // 交换两个变量的值
{
int temp = a;
a = b;
b = temp;
}
int QuickSort_PartSort(int x[], int left, int right)
{
int key = x[left];
while (left < right)
{
while (left < right && key <= x[right]) // 让 right 指向比 key 大且在最右边的值
right--;
SwapInt(x[left], x[right]);
while (left < right && key >= x[left]) // 让 left 指向比 key 小且在最左边的值
left++;
SwapInt(x[left], x[right]);
}
return left; // 返回基准值的最后位置
}
void QuickSort(int x[], int left, int right)
{
if (left < right)
{
int mid = QuickSort_PartSort(x, left, right); // 获取基准值的最后位置
QuickSort(x, left, mid-1); // 对左边部分执行快排
QuickSort(x, mid+1, right); // 对右边部分执行快排
}
}
快排的代码确实不短,如果每次调用都要手写一遍,一定非常麻烦,更别提一不小心写错了。好在C语言提供了快排的调用函数,其函数原型为:void qsort(void*base, size_t num, size_t width, int (__cdecl*compare)(const void*,const void*));
使用时需要添加 stdlib.h 头文件,具体用法如下:
#include <stdio.h>
#include <stdlib.h>
#define N 10
int cmp_int(const void *a, const void *b) // 供 qsort 调用的比较函数
{
return *(int*)a - *(int*)b; // 升序排列
}
int main()
{
int i, x[N]={3, 1, 2, 8, 6, 4, 5, 9, 10, 7};
qsort(x, N, sizeof(x[0]), cmp_int);
// 调用 qsort,参数依次为:待排序数组首地址,待排序元素数量,待排序元素字节大小,比较函数地址
for (i=0; i<N; i++)
printf("%d ", x[i]);
printf("\n");
return 0;
}
相信比较函数,对于没用过 qsort 的人来说可能有点难以理解,这里再做一下介绍。比较函数的返回值被分为三种,以 int cmp_int(const void *a, const void *b) 为例,具体划分如下表:
int cmp_int(const void *a, const void *b) | |
返回值类型 | 效果 |
等于0 | a 和 b 无前后关系 |
小于0 | a 将被排在 b 的前面 |
大于0 | a 将被排在 b 的后面 |
仔细体会体会这张表,应该就明白了。
文章最后,有的人也许会问,既然快排的时间复杂度在最糟糕的情况下会达到O(n^2),那么有没有什么办法可以避免出现最糟糕的情况呢?
方法肯定是有滴!比如说:
1、先将待排序数组随机打乱,降低其有序程度;
2、改用 堆排序 或 归并排序,这两种排序算法的时间复杂度上限均为O(n*logn),具体内容留着以后介绍。