快速排序实现及其pivot的选取
coursera上斯坦福的算法专项在讲到快速排序时,称其为最优雅的算法之一。快速排序确实是一种比较有效的排序算法,很多类库中也都采用了这种排序算法,其最坏时间复杂度为$O(n^2)$,平均时间复杂度为$O(nlogn)$,且其不需要额外的存储空间。
基本步骤
快速排序主要使用了分治的思想,通过选取一个pivot,将一个数组划分为两个子数组。其步骤为:
1.从数组中选择一个元素作为pivot
2.重新排列数组,小于pivot的在pivot的左边,大于pivot的在其右边。
3.递归地对划分后的左右两部分重复上述步骤。
简单的伪代码如下:

其中最主要的就是partition划分过程了。
划分过程
partition过程需要首先选择一个pivot,然后将小于pivot的元素放到左半部分,大于pivot的放到右半部分,并且最终pivot的位置及为其在排序好的数组中的最终位置。
这里使用第一个元素作为pivot,若选择其他元素作为pivot,则将其交换到第一个元素,这样可以保证代码的一致性及容易实现。示意图如下:

这里使用i和j,i和j最初为p+1的位置,在遍历的过程中i始终指向>p的第一个元素,j始终指向当前待遍历的元素,若a[j] < p,则将其与a[i]进行交换。相关过程如下:

基本实现如下:
/**
* a[l+1],...,a[i-1] < p
* a[i],...,a[j-1] > p
*/
private static int partition(int[] a, int l, int r) {
int p = a[l];
int i = l + 1;
for (int j=l+1; j<=r; j++) {
if (a[j] < p) {
swap(a, j, i);
i++;
}
}
swap(a, l, i-1);
return i-1;
}
基本实现
public class QuickSort {
public static void qSort(int[] a) {
if (a == null || a.length <= 1) {
return;
}
qSort(a, 0, a.length-1);
}
private static void qSort(int[] a, int l, int r) {
if (l >= r) {
return;
}
int pos = partition(a, l, r);
qSort(a, l, pos - 1);
qSort(a, pos + 1, r);
}
/**
* a[l+1],...,a[i-1] < p
* a[i],...,a[j-1] > p
*/
private static int partition(int[] a, int l, int r) {
int p = a[l];
int i = l + 1;
for (int j=l+1; j<=r; j++) {
if (a[j] < p) {
swap(a, j, i);
i++;
}
}
swap(a, l, i-1);
return i-1;
}
//返回pivot下标 选择第一个元素
private static int choosePivotFirst(int[] a, int l, int r) {
return l;
}
private static void swap(int[] a, int x, int y) {
int temp = a[x];
a[x] = a[y];
a[y] = temp;
}
pivot的选取
根据斯坦福算法专项课,然我们实现三种不同的pivot选取方式,并计算相应比较次数,分别为choose first, choose last, median of three, 还可以进行随机选取,这也是快速排序为什么是一种随机化算法。
pivot的选取决定了快速排序的运行时间,下面对几种特殊情况进行分析:
1.最坏情况
假设我们始终选取第一个元素作为pivot, 并且输入数组是有序的,那么每次划分后面所有元素都大于pivot, 每次只能将问题规模减少1,所以运行时间为$n+n-1+n-2+...+1$ = $O(n^2)$.
2.最好情况
最好情况为每次选取的pivot都能将数组平均地划分为两部分,由于划分的过程为$O(n)$,所以总的运行时间为$$T(n) = 2T(n/2) + O(n)$$根据主方法,时间复杂度为O(nlogn)。
3.随机选取
每次运行过程中,随机选取pivot, 通常能得到比较好的结果。
选取方式及实现
斯坦福算法专项课上让我们实现三种不同的选取方式,选取第一个,最后一个,以及三数取中。
1.choose first
该种方式最为简单,只需返回子数组的第一个元素下标即可,下面为其实现:
//返回pivot下标 选择第一个元素
private static int choosePivotFirst(int[] a, int l, int r) {
return l;
}
2.choose last
选择最后一个元素,实现如下:
//选择最后一个元素作为pivot
private static int choosePivotLast(int[] a, int l, int r) {
return r;
}
3.median-of-three
选取第一个、最后一个以及中间的元素的中位数,如4 5 6 7, 第一个4, 最后一个7, 中间的为5, 这三个数的中位数为5, 所以选择5作为pivot,8 2 5 4 7, 三个元素分别为8 5 7, 中位数为7, 所以选择最后一个元素7作为pivot,其实现如下:
//median-of-three pivot rule
private static int choosePivotMedianOfThree(int[] a, int l, int r) {
int mid = 0;
if ((r-l+1) % 2 == 0) {
mid = l + (r-l+1)/2 - 1;
} else {
mid = l + (r-l+1)/2;
}
//只需要找出中位数即可,不需要交换
//有的版本也可以进行交换
if (((a[l]-a[mid]) * (a[l]-a[r])) <= 0) {
return l;
} else if (((a[mid]-a[l]) * (a[mid]-a[r])) <= 0) {
return mid;
} else {
return r;
}
}
最后的划分过程如下:
private static int partition(int[] a, int l, int r) {
//pivot选择方式
//int pi = choosePivotFirst(a, l, r);
//int pi = choosePivotLast(a, l, r);
int pi = choosePivotMedianOfThree(a, l, r);
//始终将第一个元素作为pivot, 若不是, 则与之交换
if (pi != l) {
swap(a, pi, l);
}
int p = a[l];
int i = l + 1;
for (int j=l+1; j<=r; j++) {
if (a[j] < p) {
swap(a, j, i);
i++;
}
}
swap(a, l, i-1);
return i-1;
}
注意最后的划分过程相比于之前增加的pivot的选取方式,而不是单纯地将第一个元素作为pivot, 可以看到,若第一个元素不是pivot, 需要将pivot与第一个元素进行交换,这样保证代码的统一性。
总结与感想
1.学会体会这些算法背后的思想,为什么要这样设计
2.对于比较复杂的算法,学会使用特殊情况进行分析
参考资料:
(1) coursera斯坦福算法专项课part1
(2) 维基百科快速排序
快速排序实现及其pivot的选取的更多相关文章
- QuickSort快速排序的多种实现和优化
并不是很懂wikipedia上面说快排的空间复杂度最坏情况是O(NlogN)啊,难道不是空间复杂度平均O(logN),最坏O(N)么--原地快排难道不是只要算递归栈深度就好了么--有谁给我解释一下啊( ...
- 【Java】 大话数据结构(15) 排序算法(2) (快速排序及其优化)
本文根据<大话数据结构>一书,实现了Java版的快速排序. 更多:数据结构与算法合集 基本概念 基本思想:在每轮排序中,选取一个基准元素,其他元素中比基准元素小的排到数列的一边,大的排到数 ...
- 快速排序之python
快速排序( Quick sort) 快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行递归排序,以达到整个序列有 ...
- 分治思想--快速排序解决TopK问题
----前言 最近一直研究算法,上个星期刷leetcode遇到从两个数组中找TopK问题,因此写下此篇,在一个数组中如何利用快速排序解决TopK问题. 先理清一个逻辑解决TopK问题→快速排序→递 ...
- 使用Erlang实现简单的排序算法:快速排序,冒泡排序,插入排序
[排序算法] -module(sort). -compile(export_all). %%快速排序 qsort([]) -> []; qsort([Pivot|T]) -> qsort( ...
- Python实现排序算法之快速排序
Python实现排序算法:快速排序.冒泡排序.插入排序.选择排序.堆排序.归并排序和希尔排序 Python实现快速排序 原理 首先选取任意一个数据(通常选取数组的第一个数)作为关键数据,然后将所有比它 ...
- c++快速排序原理及优化
快速排序 快速排序的时间复杂度为O(logn) 注意:快速排序主要是标志数的选取,如果所选的数恰好为最小或者最大,则是最糟糕的情况,即一轮下来数据没有发生变化! 如何选取中间的标志数成为了算法的关键. ...
- luogu_P1177 【模板】快速排序 (快排和找第k大的数)
[算法] 选取pivot,然后每趟快排用双指针扫描(l,r)区间,交换左指针大于pivot的元素和右指针小于pivot的元素,将区间分成大于pivot和小于pivot的 [注意] 时间复杂度取决于pi ...
- 排序算法-Java实现快速排序算法
随机推荐
- 关于.net导出数据到excel/word【占位符替换】
1]excel的占位符替换 效果如图 关键代码: ///savedFilePath需要保存的路径 templateDocPath模板路径 替换的关键字和值 格式 [姓名]$%$小王 public st ...
- MySQL5.7下面,误操作导致的drop table db1.tb1; 的恢复方法:
MySQL5.7下面,误操作导致的drop table db1.tb1; 的恢复方法: 0.停业务数据写入.[iptables封禁] 1.从备份服务器上拉取最新的一个全备文件,恢复到一个临时的服务器上 ...
- 阿里云ECS云服务器的简单使用
随着云服务的推广与普及,越来越多的人开始接触到云服务器.然而有些人购买后却不知从何下手,今天就来看下阿里云服务器的使用吧! 一.购买完阿里云服务器后进入阿里云官网首页点击控制台 找到所购买的云产品项点 ...
- vivo机器如何无需root激活XPOSED框架的流程
在大多运行室的引流或者业务操作中,理论上需要使用安卓的神一般的Xposed框架,上周我们运行室购来了一批新的vivo机器,理论上都是基于7.0以上系统版本,理论上不能够获得root的su权限,虽然一部 ...
- SQL 存储过程中事务回滚
在事务语句最前面加上 set xact_abort on GO SET QUOTED_IDENTIFIER OFF GO ALTER PROCEDURE [dbo].[test] @a int, @b ...
- 远程连接身份验证错误,又找不到加密Oracle修正
远程连接服务器出现了错误,错误信息为:远程连接身份验证错误,又找不到加密Oracle修正. 服务器系统:Windows Server2016 客户端系统:Windows10家庭版和专业版 出错原因 ...
- java日志框架log4j详细配置及与slf4j联合使用教程
最后更新于2017年02月09日 一.log4j基本用法 首先,配置log4j的jar,maven工程配置以下依赖,非maven工程从maven仓库下载jar添加到“build path” <d ...
- 面试题解:输入一个数A,找到大于A的一个最小数B,且B中不存在连续相等的两个数字
玄魂工作室秘书 [玄魂工作室] 昨天发的算法有一处情况没考虑到,比如加一后有进位,导致又出现重复数字的情况,修正后今天重新发一次. 比如输入99,那B应该是101 因为100有两个连 ...
- 查找第三方银行官方app下载链接探索过程
需求:最近有个需求,点击按钮,弹出一个所需银行选项的非全屏弹出层,再点击某银行选项,随即跳转到该银行的app下载界面,如下图所示 注:这里只是引用相关银行的链接,不需要做什么逻辑处 ...
- 学python走过的坑一 类的实例化
运行结果:line 21, in <module> s=speaker('ken',10,'aaa')TypeError: __init__() missing 1 required p ...