[算法]——快速排序(Quick Sort)
顾名思义,快速排序(quick sort)速度十分快,时间复杂度为O(nlogn)。虽然从此角度讲,也有很多排序算法如归并排序、堆排序甚至希尔排序等,都能达到如此快速,但是快速排序使用更加广泛,以至于STL中默认排序方法就是快速排序。此外,快速排序的思想——划分(Partition)思想给人很多启发。下面以非降序排序进行介绍,不求有更深的理解,只求为自己做个简要笔记。
1)划分(Partition)
划分思想十分简单,却又十分重要,应用广泛。即:将待排序数组以某一个元素为键值(Key),将比此key小的放在左边,否则放在右边。其可能的情况为:3,1,4,2,0,KEY=5,6,9,7,8.键值Key可以任意选择。好的键值对于提高性能有帮助,但选择最优键值的方法,本身就是一种排序思想。所以,对于排序来说,一般选择第一个元素作为Key。以下为Partition代码的一种实现:
// Partition - <small> KEY <big>
int partition(int arr[], int begin, int end)
{
int i=begin, j=end-1, key;
if (i>=j) return i;
for (key=arr[i]; i<j;){
// find last arr[j]<key
for(; i<j && arr[j]>=key; --j);
arr[i] = arr[j];
// find first arr[i]>key
for(; i<j && arr[i]<=key; ++i);
arr[j] = arr[i];
}
arr[i] = key;
return i; // current key position
} // Time O(n)
// 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)。
// quick sort, ascending order
void quick_sort(int arr[], int begin, int end)
{
int mid = partition(arr,begin,end);
if (mid<0) return;
quick_sort(arr,begin,mid);
quick_sort(arr,mid+1,end);
}
一种写法更加巧妙的快速排序,将quick_sort与partition结合,如下:
// A simple quick sort version, which combine partition and sort
void quick_sort(int arr[], int begin, int end)
{
int i=begin, j=end-1, key;
if (i>=j) return;
for(key=arr[i]; i<j;){
for(; i<j && arr[j]>=key; --j);
arr[i] = arr[j];
for(; i<j && arr[i]<=key; ++i);
arr[j] = arr[i];
}
arr[i] = key;
quick_sort(arr,begin,i);
quick_sort(arr,i+1,end);
}
对于长度为N的数组arr[]而言,快速排序只需调用quick_sort(arr,0,N)。
3)链表的快速排序
链表的排序方法也有很多,此处使用快速排序对单链表进行排序。其中链表节点定义如下:
// A simple quick sort version, which combine partition and sort
// A simple ListNode define
typedef struct __ListNode
{
int val;
struct __ListNode *next;
}ListNode;
由于数组进行划分时,其元素进行移动很费时,然而对于链表而言,其元素划分后,并不需要移动,只需要指针交换即可。所以,定义两个指针mid和p,p进行快速向后遍历,当遇到小于KEY的节点时,将其加入到mid尾部并且mid后移一位。执行一趟partition后,p到达尾部NULL,mid为KEY的位置,然后继续划分begin~mid以及mid~end。具体过程见下面:
/* quick sort - list version (ascending order)
* 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
* |__begin: as KEY |__end
* |__mid : as sorted list mid
*
* p1 p2
* 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
* |__begin
* |__mid (1)
* |__mid (2)
* p3 p4
* 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
* |__begin |
* |__mid (2)
* p5
* 5 -> 3 -> 2 -> 7 -> 8 -> 1 -> 9 ->NULL
* |__begin |
* |__mid (2): move mid, and swap(mid,p)
* ||
* p5
* 5 -> 3 -> 2 -> 1 -> 8 -> 7 -> 9 ->NULL
* |__begin |
* |__mid (3)
*
* at last, swap begin and mid:
* 1 -> 3 -> 2 -> 5 -> 8 -> 7 -> 9 ->NULL
* |__begin |
* |__mid
*
* */
其代码如下:
// quick sort by ascending order for list
void qsort(ListNode *begin, ListNode *end)
{
if (begin==end || begin==NULL) return;
ListNode *p, *mid;
for(mid=begin, p=mid->next; p!=end; p=p->next){
if (p->val > begin->val) continue;
mid = mid->next;
if (mid!=p) swap(p->val,mid->val);
}
swap(begin->val,mid->val);
qsort(begin,mid);
qsort(mid->next,end);
}
对于一个无头节点的单链表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,则可以直接找到答案,而不必完全排序。
// Kth-smallest number, partition by ascending order
int kth_smallest(int k, int arr[], int n) {
int i=0, j=n, pos=-1;
for(--k; pos!=k;){
pos = partition(arr,i,j);
i = (pos<k ? pos+1 : i);
j = (pos>k ? pos : j);
}
return arr[k];
}
类似的题目可参见第k大元素。
注:本文涉及的源码:https://git.oschina.net/eudiwffe/codingstudy/blob/master/src/sort/quicksort.c
[算法]——快速排序(Quick Sort)的更多相关文章
- [算法] 快速排序 Quick Sort
快速排序(Quick Sort)使用分治法策略. 它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分:其中一部分的所有数据都比另外一部分的所有数据都要小.然后,再按此方法对这 ...
- 排序算法 - 快速排序(Quick Sort)
算法思想 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod). (1) 分治法的基本思想 ...
- 基础排序算法之快速排序(Quick Sort)
快速排序(Quick Sort)同样是使用了分治法的思想,相比于其他的排序方法,它所用到的空间更少,因为其可以实现原地排序.同时如果随机选取中心枢(pivot),它也是一个随机算法.最重要的是,快速排 ...
- Java中的经典算法之快速排序(Quick Sort)
Java中的经典算法之快速排序(Quick Sort) 快速排序的思想 基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小, 然后再按此方法对 ...
- 快速排序Quick sort
快速排序Quick sort 原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归 ...
- 快速排序算法回顾 --冒泡排序Bubble Sort和快速排序Quick Sort(Python实现)
冒泡排序的过程是首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则将两个记录交换,然后比较第二个记录和第三个记录的关键字.以此类推,直至第n-1个记录和第n个记录的关键字进行过比较为止 ...
- 基础算法之快速排序Quick Sort
原理 快速排序(Quicksort)是对冒泡排序的一种改进. 从数列中挑出一个元素,称为"基准"(pivot); 排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的 ...
- quicksort 快速排序 quick sort
* Java基本版 package cn.mediamix; import java.util.LinkedList; public class QuickSort { public static v ...
- 快速排序——Quick Sort
基本思想:(分治) 先从数列中取出一个数作为key值: 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边: 对左右两个小数列重复第二步,直至各区间只有1个数. 辅助理解:挖坑填数 初 ...
- 排序:快速排序Quick Sort
原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序 ...
随机推荐
- Python高手之路【四】python函数装饰器
def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...
- C# - 值类型、引用类型&走出误区,容易错误的说法
1. 值类型与引用类型小总结 1)对于引用类型的表达式(如一个变量),它的值是一个引用,而非对象. 2)引用就像URL,是允许你访问真实信息的一小片数据. 3)对于值类型的表达式,它的值是实际的数据. ...
- Django
一.Django 简介 Django 是一个由 Python 写成的开放源代码的 Web 应用框架.它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是 CMS(内容管理系统) ...
- CSS知识总结(七)
CSS常用样式 5.背景样式 1)背景颜色 background-color : transparent | color 常用值:①英文单词,②十六进制,③RGB或RGBA 另外,还有一种是 渐变色彩 ...
- C#与C++通信
# C#与C++相互发送消息 # ## C#端: ## namespace CshapMessage { /// /// MainWindow.xaml 的交互逻辑 /// public partia ...
- 报错:You need to use a Theme.AppCompat theme (or descendant) with this activity.
学习 Activity 生命周期时希望通过 Dialog 主题测试 onPause() 和 onStop() 的区别,点击按钮跳转 Activity 时报错: E/AndroidRuntime: FA ...
- Android代码分析工具lint学习
1 lint简介 1.1 概述 lint是随Android SDK自带的一个静态代码分析工具.它用来对Android工程的源文件进行检查,找出在正确性.安全.性能.可使用性.可访问性及国际化等方面可能 ...
- .NET跨平台:在CentOS上编译dnx并运行ASP.NET 5示例程序
在之前的博文中我们在 Ubuntu 上成功编译出了 dnx ,并且用它成功运行了 ASP.NET 5 示例程序.在这篇博文中我们将 Ubuntu 换成 CentOS. 目前 dnx 的编译需要用到 m ...
- 那些年黑了你的微软BUG
本文为 Dennis Gao 原创技术文章,发表于博客园博客,未经作者本人允许禁止任何形式的转载. 前言 炎炎夏日,朗朗乾坤,30℃ 的北京,你还在 Coding 吗? 整个 7 月都在忙项目,还加了 ...
- [Intel Edison开发板] 04、Edison开发基于nodejs和redis的服务器搭建
一.前言 intel-iot-examples-datastore 是Intel提供用于所有Edison开发板联网存储DEMO所需要的服务器工程.该工程是基于nodejs和redis写成的一个简单的工 ...