上节介绍了如何使用起泡排序的思想对无序表中的记录按照一定的规则进行排序,本节再介绍一种排序算法——快速排序算法(Quick Sort)。

C语言中自带函数库中就有快速排序——qsort函数 ,包含在 <stdlib.h> 头文件中。

快速排序算法是在起泡排序的基础上进行改进的一种算法,其实现的基本思想是:通过一次排序将整个无序表分成相互独立的两部分,其中一部分中的数据都比另一部分中包含的数据的值小,然后继续沿用此方法分别对两部分进行同样的操作,直到每一个小部分不可再分,所得到的整个序列就成为了有序序列。

例如,对无序表{49,38,65,97,76,13,27,49}进行快速排序,大致过程为:

  1. 首先从表中选取一个记录的关键字作为分割点(称为“枢轴”或者支点,一般选择第一个关键字),例如选取 49;
  2. 将表格中大于 49 个放置于 49 的右侧,小于 49 的放置于 49 的左侧,假设完成后的无序表为:{27,38,13,49,65,97,76,49}
  3. 以 49 为支点,将整个无序表分割成了两个部分,分别为{27,38,13}{65,97,76,49},继续采用此种方法分别对两个子表进行排序;
  4. 前部分子表以 27 为支点,排序后的子表为{13,27,38},此部分已经有序;后部分子表以 65 为支点,排序后的子表为{49,65,97,76}
  5. 此时前半部分子表中的数据已完成排序;后部分子表继续以 65为支点,将其分割为{49}{97,76},前者不需排序,后者排序后的结果为{76,97}
  6. 通过以上几步的排序,最后由子表{13,27,38}{49}{49}{65}{76,97}构成有序表:{13,27,38,49,49,65,76,97}

整个过程中最重要的是实现第 2 步的分割操作,具体实现过程为:

  • 设置两个指针 low 和 high,分别指向无序表的表头和表尾,如下图所示:
  • 先由 high 指针从右往左依次遍历,直到找到一个比 49 小的关键字,所以 high 指针走到 27 的地方停止。找到之后将该关键字同 low 指向的关键字进行互换:
  • 然后指针 low 从左往右依次遍历,直到找到一个比 49 大的关键字为止,所以 low 指针走到 65 的地方停止。同样找到后同 high 指向的关键字进行互换:
  • 指针 high 继续左移,到 13 所在的位置停止(13<49),然后同 low 指向的关键字进行互换:
  • 指针 low 继续右移,到 97 所在的位置停止(97>49),然后同 high 指向的关键字互换位置:
  • 指针 high 继续左移,此时两指针相遇,整个过程结束;

该操作过程的具体实现代码为:

  1. #define MAX 8
  2. typedef struct
    {
  3. int key;
  4. }SqNote;
  5.  
  6. typedef struct
    {
  7. SqNote r[MAX];
  8. int length;
  9. }SqList;
  10. //交换两个记录的位置
  11. void swap(SqNote *a, SqNote *b)
    {
  12. int key = a->key;
  13. a->key = b->key;
  14. b->key = key;
  15. }
  16. // 快速排序,分割的过程
  17. int Partition(SqList *L, int low, int high)
    {
  18. int pivotkey = L->r[low].key;
  19. // 直到两指针相遇,程序结束
  20. while (low < high)
       {
  21. // high指针左移,直至遇到比pivotkey值小的记录,指针停止移动
  22. while (low<high && L->r[high].key>=pivotkey)
         {
  23. high--;
  24. }
  25. // 交换两指针指向的记录
  26. swap(&(L->r[low]), &(L->r[high]));
  27. // low 指针右移,直至遇到比pivotkey值大的记录,指针停止移动
  28. while (low<high && L->r[low].key<=pivotkey)
         {
  29. low++;
  30. }
  31. // 交换两指针指向的记录
  32. swap(&(L->r[low]), &(L->r[high]));
  33. }
  34. return low;
  35. }

该方法其实还有可以改进的地方:在上边实现分割的过程中,每次交换都将支点记录的值进行移动,而实际上只需在整个过程结束后(low==high),两指针指向的位置就是支点记录的准确位置,所以无需每次都移动支点的位置,最后移动至正确的位置即可。

所以上边的算法还可以改写为:

  1. // 此方法中,存储记录的数组中,下标为 0 的位置时空着的,不放任何记录,记录从下标为 1 处开始依次存放
  2. int Partition(SqList *L, int low, int high)
    {
  3. L->r[] = L->r[low];
  4. int pivotkey = L->r[low].key;
  5. // 直到两指针相遇,程序结束
  6. while (low<high)
       {
  7. // high指针左移,直至遇到比pivotkey值小的记录,指针停止移动
  8. while (low<high && L->r[high].key>=pivotkey)
         {
  9. high--;
  10. }
  11. // 直接将high指向的小于支点的记录移动到low指针的位置。
  12. L->r[low] = L->r[high];
  13. // low 指针右移,直至遇到比pivotkey值大的记录,指针停止移动
  14. while (low<high && L->r[low].key<=pivotkey)
         {
  15. low++;
  16. }
  17. // 直接将low指向的大于支点的记录移动到high指针的位置
  18. L->r[high] = L->r[low];
  19. }
  20. // 将支点添加到准确的位置
  21. L->r[low] = L->r[];
  22. return low;
  23. }

快速排序的完整实现代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define MAX 9
  4.  
  5. // 单个记录的结构体
  6. typedef struct
    {
  7. int key;
  8. }SqNote;
  9. // 记录表的结构体
  10. typedef struct
    {
  11. SqNote r[MAX];
  12. int length;
  13. }SqList;
  14. // 此方法中,存储记录的数组中,下标为 0 的位置时空着的,不放任何记录,记录从下标为 1 处开始依次存放
  15. int Partition(SqList *L, int low, int high)
    {
  16. L->r[] = L->r[low];
  17. int pivotkey = L->r[low].key;
  18. // 直到两指针相遇,程序结束
  19. while (low<high)
      {
  20. // high指针左移,直至遇到比pivotkey值小的记录,指针停止移动
  21. while (low<high && L->r[high].key>=pivotkey)
         {
  22. high--;
  23. }
  24. // 直接将high指向的小于支点的记录移动到low指针的位置。
  25. L->r[low] = L->r[high];
  26. // low 指针右移,直至遇到比pivotkey值大的记录,指针停止移动
  27. while (low<high && L->r[low].key<=pivotkey)
         {
  28. low++;
  29. }
  30. // 直接将low指向的大于支点的记录移动到high指针的位置
  31. L->r[high] = L->r[low];
  32. }
  33. // 将支点添加到准确的位置
  34. L->r[low] = L->r[];
  35. return low;
  36. }
  37. void QSort(SqList *L, int low, int high)
    {
  38. if (low<high)
       {
  39. // 找到支点的位置
  40. int pivotloc = Partition(L, low, high);
  41. // 对支点左侧的子表进行排序
  42. QSort(L, low, pivotloc-);
  43. // 对支点右侧的子表进行排序
  44. QSort(L, pivotloc+, high);
  45. }
  46. }
  47. void QuickSort(SqList *L)
    {
  48. QSort(L, , L->length);
  49. }
  50. int main()
    {
  51. SqList *L = (SqList*)malloc(sizeof(SqList));
  52. L->length = ;
  53. L->r[].key = ;
  54. L->r[].key = ;
  55. L->r[].key = ;
  56. L->r[].key = ;
  57. L->r[].key = ;
  58. L->r[].key = ;
  59. L->r[].key = ;
  60. L->r[].key = ;
  61. QuickSort(L);
  62. for (int i=; i<=L->length; i++)
       {
  63. printf("%d ", L->r[i].key);
  64. }
  65. return ;
  66. }
  67. 运行结果:

总结

快速排序算法的时间复杂度为O(nlogn),是所有时间复杂度相同的排序方法中性能最好的排序算法。

数据结构65:快速排序算法(QSort,快排)的更多相关文章

  1. 快速排序c++实现 快排C++代码实现

    快速排序c++实现 快排C++ 第一.算法描述 快速排序由C. A. R. Hoare在1962年提出,该算法是目前实践中使用最频繁,实用高效的最好排序算法, 快速排序算法是采用分治思想的算法,算法分 ...

  2. qsort 快排函数(C语言)

    qsort 快排函数(C语言) 函数原型 void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, ...

  3. poj1007 qsort快排

    这道题比较简单,但通过这个题我学会了使用c++内置的qsort函数用法,收获还是很大的! 首先简要介绍一下qsort函数. 1.它是快速排序,所以就是不稳定的.(不稳定意思就是张三.李四成绩都是90, ...

  4. JS算法之快排&冒泡

    1.快速排序思想: 1.1 先找数组的最中间的一个数为基准 1.2 把数组通过此基准分为小于基准的left数组和大于基准的right数组, 1.3 递归重复上面的两个步骤, 代码如下: functio ...

  5. 如何用快排思想在O(n)内查找第K大元素--极客时间王争《数据结构和算法之美》

    前言 半年前在极客时间订阅了王争的<数据结构和算法之美>,现在决定认真去看看.看到如何用快排思想在O(n)内查找第K大元素这一章节时发现王争对归并和快排的理解非常透彻,讲得也非常好,所以想 ...

  6. 排序算法C语言实现——冒泡、快排、堆排对比

    对冒泡.快排.堆排这3个算法做了验证,结果分析如下: 一.结果分析 时间消耗:快排 < 堆排 < 冒泡. 空间消耗:冒泡O(1) = 堆排O(1) < 快排O(logn)~O(n) ...

  7. 快速排序详解(lomuto划分快排,hoare划分快排,classic经典快排,dualpivot双轴快排源码)

    目录 快速排序(lomuto划分快排,hoare划分快排,classic经典快排,dualpivot双轴快排) 一.快速排序思想 二.划分思想 三.测试用例 快速排序(lomuto划分快排,hoare ...

  8. 快排 快速排序 qsort quicksort C语言

    现在网上搜到的快排和我以前打的不太一样,感觉有点复杂,我用的快排是FreePascal里/demo/text/qsort.pp的风格,感觉特别简洁. #include<stdio.h> # ...

  9. javascript高级排序算法之快速排序(快排)

    javascript高级排序算法之快速排序(快排)我们之前讨论了javascript基本排序算法 冒泡排序 选择排序 插入排序 简单复习: 冒泡排序: 比较相邻的两个元素,如果前一个比后一个大,则交换 ...

随机推荐

  1. Visual studio 2015/2017 opencv3.4 kineck2.0 osg realsense配置

    ----------------------------------------------包含目录-------------------------------------------------- ...

  2. spring4-5-事务管理

    1.简单介绍 事务管理是企业级应用程序开发中必不可少的技术,  用来确保数据的完整性和一致性. 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用 事务的 ...

  3. Python学习笔记_读Excel去重

    读取一个Excel文件,按照某列关键字,如果有重复则去掉 这里不介绍所有的解决办法,只是列出一个办法. 软件环境: OS:Win10 64位 Python 3.7 测试路径:D:\Work\Pytho ...

  4. 633E Binary Table

    传送门 分析 我们发现n特别小,所以可以从这里入手 我们记录出所有列中某一种状态的列有多少个 我们再记录出每种列最少有多少个1(原来的1的个数和取反后的个数去最小值) 于是我们可以得出对于所有列异或一 ...

  5. SpringMVC 2.5.6 +Hibernate 3.2.0

    spring MVC配置详解 现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时 ...

  6. [ASP.NET MVC 小牛之路]03 - Razor语法(转)

    出处:http://www.cnblogs.com/willick/p/3224144.html Razor是MVC3中才有的新的视图引擎.我们知道,在ASP.NET中,ASPX的视图引擎依靠< ...

  7. Android滑动冲突解决方法

    叙述 滑动冲突可以说是日常开发中比较常见的一类问题,也是比较让人头疼的一类问题,尤其是在使用第三方框架的时候,两个原本完美的控件,组合在一起之后,忽然发现整个世界都不好了. 关于滑动冲突 滑动冲突分类 ...

  8. mysql中四种存储引擎的区别和选择

    前言 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以 ...

  9. [LeetCode 题解]: Container With Most Water

    Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai).  ...

  10. MIPS rev.1 指令参数

    由于MIPS各个版本之间的操作数会变,如果出现无法识别的情况 需要根据官方的MIPS instruction手册逐条核对,此处的版本为Rev.1 //******MIPS-55*********// ...