本文根据《大话数据结构》一书,实现了Java版的快速排序

更多:数据结构与算法合集

基本概念

  基本思想:在每轮排序中,选取一个基准元素,其他元素中比基准元素小的排到数列的一边,大的排到数列的另一边;之后对两边的数列继续进行这种排序,最终达到整体有序。

  

图片来自公众号:程序员小灰

实现代码

  根据上述基本思想,可以先写出快速排序最核心的代码:对于数组a中从下标为low至下标为high的元素,选取一个基准元素(记为pivotKey),根据与基准比较的大小,将这些元素排到基准元素的两端。

  注意点:1.两端向中间扫描时,一定要先从高段往低端扫描(low<high && a[high]<pivotKey),这样才能实现pivotKey一直会交换到中间。!!

      2.比较大小时不要忘记low<high还要一直成立,即(low<high && a[high]<pivotKey)。!!  例如,数组全为同一个数字时,不加这个判断有可能导致越界

	/**
* 对数组a中下标从low到high的元素,选取基准元素pivotKey,
* 根据与基准比较的大小,将各个元素排到基准元素的两端。
* 返回值为最后基准元素的位置
*/
public int partition(int[] a, int low, int high) {
int pivotKey = a[low]; //用第一个元素作为基准元素
while (low < high) { //两侧交替向中间扫描
while (low < high && a[high] >= pivotKey)
high--;
swap(a, low, high); //比基准小的元素放到低端
while (low < high && a[low] <= pivotKey)
low++;
swap(a, low, high); //比基准大的元素放到高端
}
return low; //返回基准元素所在位置
}

  将元素分为两部分后,必须对两个子部分继续进行上面的排序,所以要用到递归。代码如下:

	/**
* 递归调用
*/
public void qSort(int[] a, int low, int high) {
int pivot;
if (low >= high)
return;
pivot = partition(a, low, high); //将数列一分为二
qSort(a, low, pivot - 1); //对低子表排序
qSort(a, pivot + 1, high); //对高子表排序
}

  

完整Java代码

(含测试代码)

import java.util.Arrays;

/**
*
* @Description 快速排序
*
* @author yongh
* @date 2018年9月14日 下午2:39:00
*/
public class QuickSort {
public void quickSort(int[] a) {
if (a == null)
return;
qSort(a, 0, a.length - 1);
} /**
* 递归调用
*/
public void qSort(int[] a, int low, int high) {
int pivot;
if (low >= high)
return;
pivot = partition(a, low, high); //将数列一分为二
qSort(a, low, pivot - 1); //对低子表排序
qSort(a, pivot + 1, high); //对高子表排序
} /**
* 对数组a中下标从low到high的元素,选取基准元素pivotKey,
* 根据与基准比较的大小,将各个元素排到基准元素的两端。
* 返回值为最后基准元素的位置
*/
public int partition(int[] a, int low, int high) {
int pivotKey = a[low]; //用第一个元素作为基准元素
while (low < high) { //两侧交替向中间扫描
while (low < high && a[high] >= pivotKey)
high--;
swap(a, low, high); //比基准小的元素放到低端
while (low < high && a[low] <= pivotKey)
low++;
swap(a, low, high); //比基准大的元素放到高端
}
return low; //返回基准元素所在位置
} public void swap(int[] a, int i, int j) {
int temp;
temp = a[j];
a[j] = a[i];
a[i] = temp;
} // =========测试代码=======
public void test1() {
int[] a = null;
quickSort(a);
System.out.println(Arrays.toString(a));
} public void test2() {
int[] a = {};
quickSort(a);
System.out.println(Arrays.toString(a));
} public void test3() {
int[] a = { 1 };
quickSort(a);
System.out.println(Arrays.toString(a));
} public void test4() {
int[] a = { 3, 3, 3, 3, 3 };
quickSort(a);
System.out.println(Arrays.toString(a));
} public void test5() {
int[] a = { -3, 6, 3, 1, 3, 7, 5, 6, 2 };
quickSort(a);
System.out.println(Arrays.toString(a));
} public static void main(String[] args) {
QuickSort demo = new QuickSort();
demo.test1();
demo.test2();
demo.test3();
demo.test4();
demo.test5();
}
}

  

null
[]
[]
[, , , , ]
[-, , , , , , , , ]

QuickSort

快速排序优化

 1.优化选取枢纽

  基准应尽量处于序列中间位置,可以采取“三数取中”的方法,在partition()方法开头加以下代码,使得a[low]为三数的中间值:

		// 三数取中,将中间元素放在第一个位置
if (a[low] > a[high])
swap(a, low, high);
if (a[(low + high) / 2] > a[high])
swap(a, (low + high) / 2, high);
if (a[low] < a[(low + high) / 2])
swap(a, (low + high) / 2, low);

  

 2.优化不必要的交换

  两侧向中间扫描时,可以将交换数据变为替换:

		while (low < high) { // 两侧交替向中间扫描
while (low < high && a[high] >= pivotKey)
high--;
a[low] = a[high];
// swap(a, low, high); //比基准小的元素放到低端
while (low < high && a[low] <= pivotKey)
low++;
a[high] = a[low];
// swap(a, low, high); //比基准大的元素放到高端
}
a[low]=pivotKey; //在中间位置放回基准值

  

 3.优化小数组时的排序方案

  当数组非常小时,采用直接插入排序(简单排序中性能最好的方法)

 4.优化递归操作

  qSort()方法中,有两次递归操作,递归对性能有较大影响。因此,使用while循环,在第一次递归后,变量low就没有用处了,可将pivot+1赋值给low,下次循环中,partition(a, low, high)的效果等同于qSort(a, pivot + 1, high),从而可以减小堆栈的深度,提高性能。

		// pivot = partition(a, low, high); // 将数列一分为二
// qSort(a, low, pivot - 1); // 对低子表排序
// qSort(a, pivot + 1, high); // 对高子表排序 //优化递归操作
while (low < high) {
pivot = partition(a, low, high); // 将数列一分为二
qSort(a, low, pivot - 1); // 对低子表排序
low = pivot + 1;
}

  

复杂度分析

  快速排序时间性能取决于递归深度,而空间复杂度是由递归造成的栈空间的使用。递归的深度可以用递归树来描述,如{50,10,90,30,70,40,80,60,20}的递归树如下:

 最优情况:

  最优情况下,每次选取的基准元素都是元素中间值,partition()方法划分均匀,此时根据二叉树的性质4可以知道,排序n个元素,其递归树的深度为[log2n]+1,所以仅需要递归log2n次。

  将排序n个元素的时间记为T(n),则有以下推断:

  所以最优情况下的时间复杂度为:O(nlogn);同样根据递归树的深度,最优空间复杂度为O(logn)

 最坏情况:

  递归树为一棵斜树,需要n-1次调用,所以最坏空间复杂度为O(logn)。在第i次调用中需要n-1次的关键字比较,所以比较次数为:Σ(n-i)=(n-1)+……+2+1=n(n-1)/2,所以最坏时间复杂度为O(n^2)

 平均情况:

  

  平均时间复杂度:O(nlogn),平均空间复杂度O(logn)。

更多:数据结构与算法合集

【Java】 大话数据结构(15) 排序算法(2) (快速排序及其优化)的更多相关文章

  1. 【Java】 大话数据结构(14) 排序算法(1) (冒泡排序及其优化)

    本文根据<大话数据结构>一书,实现了Java版的冒泡排序. 更多:数据结构与算法合集 基本概念 基本思想:将相邻的元素两两比较,根据大小关系交换位置,直到完成排序. 对n个数组成的无序数列 ...

  2. 【Java】 大话数据结构(16) 排序算法(3) (堆排序)

    本文根据<大话数据结构>一书,实现了Java版的堆排序. 更多:数据结构与算法合集 基本概念 堆排序种的堆指的是数据结构中的堆,而不是内存模型中的堆. 堆:可以看成一棵完全二叉树,每个结点 ...

  3. 【Java】 大话数据结构(17) 排序算法(4) (归并排序)

    本文根据<大话数据结构>一书,实现了Java版的归并排序. 更多:数据结构与算法合集 基本概念 归并排序:将n个记录的序列看出n个有序的子序列,每个子序列长度为1,然后不断两两排序归并,直 ...

  4. 【Java】 大话数据结构(18) 排序算法(5) (直接插入排序)

    本文根据<大话数据结构>一书,实现了Java版的直接插入排序. 更多:数据结构与算法合集 基本概念 直接插入排序思路:类似扑克牌的排序过程,从左到右依次遍历,如果遇到一个数小于前一个数,则 ...

  5. Java常见排序算法之快速排序

    在学习算法的过程中,我们难免会接触很多和排序相关的算法.总而言之,对于任何编程人员来说,基本的排序算法是必须要掌握的. 从今天开始,我们将要进行基本的排序算法的讲解.Are you ready?Let ...

  6. Java排序算法之快速排序

    Java排序算法之快速排序 快速排序(Quicksort)是对冒泡排序的一种改进. 快速排序由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分 ...

  7. javascript数据结构与算法--高级排序算法(快速排序法,希尔排序法)

    javascript数据结构与算法--高级排序算法(快速排序法,希尔排序法) 一.快速排序算法 /* * 这个函数首先检查数组的长度是否为0.如果是,那么这个数组就不需要任何排序,函数直接返回. * ...

  8. 【Java】 大话数据结构(11) 查找算法(2)(二叉排序树/二叉搜索树)

    本文根据<大话数据结构>一书,实现了Java版的二叉排序树/二叉搜索树. 二叉排序树介绍 在上篇博客中,顺序表的插入和删除效率还可以,但查找效率很低:而有序线性表中,可以使用折半.插值.斐 ...

  9. Java中的数据结构及排序算法

    (明天补充) 主要是3种接口:List Set Map List:ArrayList,LinkedList:顺序表ArrayList,链表LinkedList,堆栈和队列可以使用LinkedList模 ...

随机推荐

  1. luogu1084 [NOIp2012]疫情控制 (二分答案+倍增+dfs序)

    先二分出一个时间,把每个军队倍增往上跳到不能再跳 然后如果它能到1号点,就记下来它跳到1号点后剩余的时间:如果不能,就让它就地扎根,记一记它覆盖了哪些叶节点(我在这里用了dfs序+差分,其实直接dfs ...

  2. bzoj 1824: [JSOI2010]下棋问题

    考虑每次新放一个棋子会产生多少新的矩形,以及减掉多少旧的矩形. 用第$i$个点的坐标把坐标轴分成4个象限. 显然第一问的答案用四个单调栈就能解决. 而且第二问每个矩形的两个端点一定在1,3或2,4象限 ...

  3. [学习笔记]插头dp

    基于连通性的状压dp 巧妙之处:插头已经可以表示内部所有状态了. 就是讨论麻烦一些. 简介 转移方法:逐格转移,分类讨论 记录状态方法:最小表示法(每次要重新编号,对于一类没用“回路路径”之类的题,可 ...

  4. ByteBuffer: 当由一个byte[]来生成一个固定不变的ByteBuffer时,使用ByteBuffer.wrap(byte[]);

    StringBuilder sb = new StringBuilder(1024); //向sb中写入900个左右的随机字符内容 for(int j=1; j< 50;j++) { sb.ap ...

  5. zabbix监控的基础概念、工作原理及架构(一)

    zabbix监控的基础概念.工作原理及架构 转载于网络 一.什么是zabbix及优缺点 Zabbix能监视各种网络参数,保证服务器系统的安全运营,并提供灵活的通知机制以让系统管理员快速定位/解决存在的 ...

  6. Swift学习笔记2

    1.函数参数都有一个外部参数名(external parameter name)和一个局部参数名(local parameter name).外部参数名用于在函数调用时标注传递给函数的参数,局部参数名 ...

  7. Kafka 0.8 sever:源代码High level分析

    本文主要介绍了Kafka High level的代码架构和主要的类. 这张图是0.8版本的架构 Boker 架构 1 network layer Kafka使用NIO自己实现了网络层的代码, 而不是采 ...

  8. Hadoop Yarn源码 - day1

    Hadoop 2.6.0下面的关于Yarn工程,如下所示,主要有以下七个module: hadoop-yarn-api:和外部平台交互的接口 hadoop-yarn-applications hado ...

  9. 阿里云(一)云存储OSS的命令行osscmd的安装和使用

    一.安装Python 在Linux Shell里验证Python版本: $ python -V Python 2.7.10 二.安装OSScmd SDK osscmd是基于python 2.5.4(其 ...

  10. 【转】UICollectionView使用介绍

    CHENYILONG Blog UICollectionView 使用介绍 技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博http://weibo.com/lu ...