排序算法 之 快速排序

本文介绍了快速排序算法,对比了它与选择排序的优劣。快速排序平均时间复杂度为O(n*logn),但在最坏情况下为O(n^2)。尽管如此,由于其空间复杂度和常数因子,快速排序通常在实际应用中优于选择排序。文章通过递归实现快速排序,并提供了使用示例。此外,讨论了如何避免最糟糕情况,如通过数组随机化和选择其他排序算法如堆排序或归并排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        选择排序曾经是我的最爱,但自从认识了快速排序之后,咳咳…我依然坚持用选择排序…只是…感觉没有以前那么爱了…

        如我上一篇博文写到的,选择排序的代码非常的简单,对于稍有一点代码经验的程序猿来说,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)
返回值类型效果
等于0a 和 b 无前后关系
小于0a 将被排在 b 的前面
大于0a 将被排在 b 的后面

        仔细体会体会这张表,应该就明白了。

        文章最后,有的人也许会问,既然快排的时间复杂度在最糟糕的情况下会达到O(n^2),那么有没有什么办法可以避免出现最糟糕的情况呢?

        方法肯定是有滴!比如说:

        1、先将待排序数组随机打乱,降低其有序程度;

        2、改用 堆排序 或 归并排序,这两种排序算法的时间复杂度上限均为O(n*logn),具体内容留着以后介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值