顾名思义,快速排序(quick sort)速度十分快,时间复杂度为O(nlogn)。虽然从此角度讲,也有很多排序算法如归并排序、堆排序甚至希尔排序等,都能达到如此快速,但是快速排序使用更加广泛,以至于STL中默认排序方法就是快速排序。此外,快速排序的思想——划分(Partition)思想给人很多启发。下面以非降序排序进行介绍,不求有更深的理解,只求为自己做个简要笔记。

1)划分(Partition)

划分思想十分简单,却又十分重要,应用广泛。即:将待排序数组以某一个元素为键值(Key),将比此key小的放在左边,否则放在右边。其可能的情况为:3,1,4,2,0,KEY=5,6,9,7,8.键值Key可以任意选择。好的键值对于提高性能有帮助,但选择最优键值的方法,本身就是一种排序思想。所以,对于排序来说,一般选择第一个元素作为Key。以下为Partition代码的一种实现:

  1. // Partition - <small> KEY <big>
  2. int partition(int arr[], int begin, int end)
  3. {
  4. int i=begin, j=end-1, key;
  5. if (i>=j) return i;
  6. for (key=arr[i]; i<j;){
  7. // find last arr[j]<key
  8. for(; i<j && arr[j]>=key; --j);
  9. arr[i] = arr[j];
  10. // find first arr[i]>key
  11. for(; i<j && arr[i]<=key; ++i);
  12. arr[j] = arr[i];
  13. }
  14. arr[i] = key;
  15. return i; // current key position
  16. } // Time O(n)
  17. // after partition, the KEY must be the position where ordered

首先定义两个下标变量i,j,分别指向开始和结尾,并将开始begin的元素作为键值key。然后从后向前遍历arr[],找到小于key的元素j,将其覆盖i;从前向后遍历arr[],找到大于key的元素i,将其覆盖j。直到i与j碰头,将key写回i。此时i的位置即选中的key应该在的位置,即:key左边的元素不大于key,key右边的不小于key。返回此时key的下标位置i。其时间复杂度为O(n).

2)快速排序(quick sort)

快速排序就是利用划分思想。每次经过划分之后,其key所在位置(下标)必然是经过排序后key所在的位置!如上面的3,1,4,2,0,KEY=5,6,9,7,8。元素5此时在位置5,正是其排序后的位置。再如:3,5,1,KEY=10,23,19.元素10在位置3,也是其排完序后所在的位置。正是如此,利用划分进行快速排序才成为可能。

快速排序思想为:对于数组arr[]以及其首位元素位置begin和末尾元素end,选择其中一个元素作为Key,进行一趟划分,得到key此时应该在的位置i;然后对于key的左部分begin~i-1进行划分,对于key右部分i+1~end进行划分;逐渐划分更小的范围进行划分。最终排序完毕。每次进二分划分,一共划分了logn次,故快速排序时间复杂度为O(nlogn)。

  1. // quick sort, ascending order
  2. void quick_sort(int arr[], int begin, int end)
  3. {
  4. int mid = partition(arr,begin,end);
  5. if (mid<0) return;
  6. quick_sort(arr,begin,mid);
  7. quick_sort(arr,mid+1,end);
  8. }

一种写法更加巧妙的快速排序,将quick_sort与partition结合,如下:

  1. // A simple quick sort version, which combine partition and sort
  2. void quick_sort(int arr[], int begin, int end)
  3. {
  4. int i=begin, j=end-1, key;
  5. if (i>=j) return;
  6. for(key=arr[i]; i<j;){
  7. for(; i<j && arr[j]>=key; --j);
  8. arr[i] = arr[j];
  9. for(; i<j && arr[i]<=key; ++i);
  10. arr[j] = arr[i];
  11. }
  12. arr[i] = key;
  13. quick_sort(arr,begin,i);
  14. quick_sort(arr,i+1,end);
  15. }

对于长度为N的数组arr[]而言,快速排序只需调用quick_sort(arr,0,N)。

3)链表的快速排序

链表的排序方法也有很多,此处使用快速排序对单链表进行排序。其中链表节点定义如下:

  1. // A simple quick sort version, which combine partition and sort
  2. // A simple ListNode define
  3. typedef struct __ListNode
  4. {
  5. int val;
  6. struct __ListNode *next;
  7. }ListNode;

由于数组进行划分时,其元素进行移动很费时,然而对于链表而言,其元素划分后,并不需要移动,只需要指针交换即可。所以,定义两个指针mid和p,p进行快速向后遍历,当遇到小于KEY的节点时,将其加入到mid尾部并且mid后移一位。执行一趟partition后,p到达尾部NULL,mid为KEY的位置,然后继续划分begin~mid以及mid~end。具体过程见下面:

  1. /* quick sort - list version (ascending order)
  2. * 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
  3. * |__begin: as KEY |__end
  4. * |__mid : as sorted list mid
  5. *
  6. * p1 p2
  7. * 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
  8. * |__begin
  9. * |__mid (1)
  10. * |__mid (2)
  11. * p3 p4
  12. * 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
  13. * |__begin |
  14. * |__mid (2)
  15. * p5
  16. * 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
  17. * |__begin |
  18. * |__mid (2): move mid, and swap(mid,p)
  19. * ||
  20. * p5
  21. * 5 -> 3 -> 2 -> 1 -> 8 -> 7 -> 9 ->NULL
  22. * |__begin |
  23. * |__mid (3)
  24. *
  25. * at last, swap begin and mid:
  26. * 1 -> 3 -> 2 -> 5 -> 8 -> 7 -> 9 ->NULL
  27. * |__begin |
  28. * |__mid
  29. *
  30. * */

其代码如下:

  1. // quick sort by ascending order for list
  2. void qsort(ListNode *begin, ListNode *end)
  3. {
  4. if (begin==end || begin==NULL) return;
  5. ListNode *p, *mid;
  6. for(mid=begin, p=mid->next; p!=end; p=p->next){
  7. if (p->val > begin->val) continue;
  8. mid = mid->next;
  9. if (mid!=p) swap(p->val,mid->val);
  10. }
  11. swap(begin->val,mid->val);
  12. qsort(begin,mid);
  13. qsort(mid->next,end);
  14. }

对于一个无头节点的单链表ListNode *head而言,进行快速排序只需调用qsort(head,NULL);即可。此处已将parition合并到快速排序中,并没有单独给出Partition,如需要请自行写出。

4) 第K小的数字(Kth-smallest number)

划分可以不用完全排序,就可以求一个数组中第k小(或第k大)的数字。

例如给定一个数组{5,2,3,1,4},求其第2小的数字。如果排序,则很容易找到其答案为2,但使用的时间为O(nlogn)。在划分时,如果当前返回的key的位置pos正是k,则可以直接找到答案,而不必完全排序。

  1. // Kth-smallest number, partition by ascending order
  2. int kth_smallest(int k, int arr[], int n) {
  3. int i=0, j=n, pos=-1;
  4. for(--k; pos!=k;){
  5. pos = partition(arr,i,j);
  6. i = (pos<k ? pos+1 : i);
  7. j = (pos>k ? pos : j);
  8. }
  9. return arr[k];
  10. }

类似的题目可参见第k大元素

注:本文涉及的源码:https://git.oschina.net/eudiwffe/codingstudy/blob/master/src/sort/quicksort.c

[算法]——快速排序(Quick Sort)的更多相关文章

  1. [算法] 快速排序 Quick Sort

    快速排序(Quick Sort)使用分治法策略. 它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分:其中一部分的所有数据都比另外一部分的所有数据都要小.然后,再按此方法对这 ...

  2. 排序算法 - 快速排序(Quick Sort)

    算法思想 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod). (1) 分治法的基本思想  ...

  3. 基础排序算法之快速排序(Quick Sort)

    快速排序(Quick Sort)同样是使用了分治法的思想,相比于其他的排序方法,它所用到的空间更少,因为其可以实现原地排序.同时如果随机选取中心枢(pivot),它也是一个随机算法.最重要的是,快速排 ...

  4. Java中的经典算法之快速排序(Quick Sort)

    Java中的经典算法之快速排序(Quick Sort) 快速排序的思想 基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小, 然后再按此方法对 ...

  5. 快速排序Quick sort

    快速排序Quick sort 原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归 ...

  6. 快速排序算法回顾 --冒泡排序Bubble Sort和快速排序Quick Sort(Python实现)

    冒泡排序的过程是首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换,然后比较第二个记录和第三个记录的关键字.以此类推,直至第n-1个记录和第n个记录的关键字进行过比较为止 ...

  7. 基础算法之快速排序Quick Sort

    原理 快速排序(Quicksort)是对冒泡排序的一种改进. 从数列中挑出一个元素,称为"基准"(pivot); 排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的 ...

  8. quicksort 快速排序 quick sort

    * Java基本版 package cn.mediamix; import java.util.LinkedList; public class QuickSort { public static v ...

  9. 快速排序——Quick Sort

    基本思想:(分治) 先从数列中取出一个数作为key值: 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边: 对左右两个小数列重复第二步,直至各区间只有1个数. 辅助理解:挖坑填数 初 ...

  10. 排序:快速排序Quick Sort

    原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序 ...

随机推荐

  1. 平台之大势何人能挡? 带着你的Net飞奔吧!

    镇楼图: 跨平台系列: Linux基础 1.Linux基础学习 By dnt http://www.cnblogs.com/dunitian/p/4822807.html 环境配置 1.Hyper-v ...

  2. 【疯狂造轮子-iOS】JSON转Model系列之一

    [疯狂造轮子-iOS]JSON转Model系列之一 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 之前一直看别人的源码,虽然对自己提升比较大,但毕竟不是自己写的,很容易遗 ...

  3. nodejs进阶(3)—路由处理

    1. url.parse(url)解析 该方法将一个URL字符串转换成对象并返回. url.parse(urlStr, [parseQueryString], [slashesDenoteHost]) ...

  4. 探索ASP.NET MVC5系列之~~~2.视图篇(上)---包含XSS防御和异步分部视图的处理

    其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/dunitian/p/4822808.ht ...

  5. redux学习

    redux学习: 1.应用只有一个store,用于保存整个应用的所有的状态数据信息,即state,一个state对应一个页面的所需信息 注意:他只负责保存state,接收action, 从store. ...

  6. runtime梳理。

    一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C语言,函数的调用在编译的时候会决定调用哪个函数. 对于OC的函数,属于 ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(80)-自由桌面

    系列目录 前言 这次我们来做一个有趣的事情,有朋友跟做了很远,找我要自由桌面的代码,这次我们将演示自由桌面的代码. 自由桌面:用户可以随意增删改桌面的布局.个数(只留自己需要看到的数据),这次纯属Ea ...

  8. 9、 Struts2验证(声明式验证、自定义验证器)

    1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法.有效的. Struts2 的输入验证 基于 XWork Validation Framework 的声明式验证: ...

  9. 初学者看过来之JSON入门

    1. 什么是JSON JSON---Javascript Object Notation,前两个单词大家应该都认识,最后一个notation,是"记号.标记法"的意思,连在一起,便 ...

  10. 【干货分享】流程DEMO-出差申请单

    流程名: 出差申请  业务描述: 员工出差前发起流程申请,流程发起时,会检查预算,如果预算不够,将不允许发起费用申请,如果预算够用,将发起流程,同时占用相应金额的预算,但撤销流程会释放相应金额的预算. ...