算法系列(五)快速排序及其改进(Java实现)
来源:互联网 发布:居则曰 不吾知也翻译 编辑:程序博客网 时间:2024/05/22 00:49
前言:
快速排序是C.A.R Hoare于1960年发明的。基本思想也是分治。快速排序不需要额外的空间,是原地排序,时间复杂度是O(NlgN),
快速排序快在元素之间是跳跃比较,比较的次数将会减少,内循环短小。在实际应用中,比其他排序算法都要快得多。
快速排序:
对于待排数组a,长度为N,
1,首先,选定一个元素作为固定的比较对象,我们称之为基准元素 v。选定两个变量 i , j作为索引 ,开始时i=0,j=N-1;
2,从 j 开始向前搜索,找到第一个a[ j ]<v ,从 i 开始向后搜索,找到第一个a[ i ] > v, 交换a[ i ]和a[ j ];
3,当i >= j时退出,退出后将 v 归位,即将 v 和 a[ j ] 互换
此时 v 将原数组切分成两个子数组,左边都是比它小的元素,右边都是比它大的元素。
4,对 v左右两边的数组重复以上步骤,直到每组只剩一个元素。
/** * 快速排序 * 最快的排序算法 * 平均比较次数2NlogN,无需额外空间 * 之所以快是因为和定值比较,跳跃交换,交换和比较的次数变少 * @author CANAAN * */ public class QuickSort {public static void sort(Comparable[] a){StdRandom.shuffle(a); //防止出现倒序等时间复杂度为平方的序列sort(a,0,a.length-1);}private static void sort(Comparable[] a, int lo, int hi) {if(hi <= lo) //比较完时退出return;int j = partition(a,lo,hi);sort(a,lo,j-1);sort(a,j+1,hi);}private static int partition(Comparable[] a, int lo, int hi) {int i = lo,j = hi+1; //i从第二个元素开始,j从最后一个元素开始while(true){while(less(a[++i], a[lo])){ //比较前先计算++if(i == hi) break;}while(less( a[lo],a[--j])){if(j == lo) break; }if(i>=j) break;exch(a, i, j); //a[i]>基准,a[j]<基准,交换}exch(a,lo,j); //将基准元素调换到j处,此时左边都是小于a[j]的,右边都是大于a[j]的return j;}private static boolean less(Comparable v, Comparable w){ //比较v是否小于wreturn v.compareTo(w) < 0;}private static void exch(Comparable[] a, int i, int j) { //交换Comparable t = a[i];a[i] = a[j];a[j] = t;}public static void show(Comparable[] a){ //单行打印数组for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}System.out.println();}public static boolean isSorted(Comparable[] a){ //判断是否有序for (int i = 1; i < a.length; i++) {if(less(a[i], a[i-1]))return false;}return true;}public static void main(String[] args) {String[] a = StdIn.readAllStrings(); //注意这里是字符串Integer[] b = new Integer[a.length]; //转化成比较整数for(int i = 0; i<a.length;i++){b[i] = Integer.parseInt(a[i]);} sort(b);show(b);}}
改进一:当数组较小时,使用插入排序
改进二:基准元素的选取,取样3个值取中间大小的值作为基准元素,保证切分后两个子数组差不多大
public class QuickSort2 {public static int CUTOFF = 5;public static void sort(Comparable[] a){StdRandom.shuffle(a); sort(a,0,a.length-1);}/* * 改进1:当数组较小时,使用插入排序 */private static void sort(Comparable[] a, int lo, int hi) {if(hi <= lo+CUTOFF-1) {InsertionSort.sort(a, lo, hi);return;}//if(hi <= lo) //return;int m = medianOf3(a, lo, lo + (hi - lo)/2, hi);exch(a, lo, m);int j = partition(a,lo,hi);sort(a,lo,j-1);sort(a,j+1,hi);}/* * 改进2:尽量保证基准元素位于中间(排序后) */private static int medianOf3(Comparable[] a, int i, int j, int k) { //这里包含一个等号的处理if(!less(a[i],a[k]) && !less(a[j],a[i]) || !less(a[i],a[j]) && !less(a[k],a[i])) return i;if(!less(a[j],a[k]) && !less(a[i],a[j]) || !less(a[j],a[i]) && !less(a[k],a[j])) return j;if(!less(a[k],a[i]) && !less(a[j],a[k]) || !less(a[k],a[j]) && !less(a[i],a[k])) return k;return 0;}private static int partition(Comparable[] a, int lo, int hi) {int i = lo,j = hi+1; //i从第二个元素开始,j从最后一个元素开始while(true){while(less(a[++i],a[lo]))if(i == hi) break;while(less(a[lo],a[--j]))if(j == lo) break; if(i>=j) break;exch(a,i,j);}exch(a,lo,j); //将基准元素调换到j处,此时左边都是小于a[j]的,右边都是大于a[j]的return j;}private static boolean less(Comparable v, Comparable w){ //比较v是否小于wreturn v.compareTo(w) < 0;}private static void exch(Comparable[] a, int i, int j) { //交换Comparable t = a[i];a[i] = a[j];a[j] = t;}public static void show(Comparable[] a){ //单行打印数组for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}System.out.println();}public static boolean isSorted(Comparable[] a){ //判断是否有序for (int i = 1; i < a.length; i++) {if(less(a[i], a[i-1]))return false;}return true;}public static void main(String[] args) {String[] a = StdIn.readAllStrings();Integer[] b = new Integer[a.length]; //转化成比较整数for(int i = 0; i<a.length;i++){b[i] = Integer.parseInt(a[i]);} sort(b);show(b);}}
插入排序部分:
public class InsertionSort { public static void sort(Comparable[] a, int lo, int hi) { for (int i = lo; i <= hi; i++) { //这里到底是比较到hi 还是hi-1,看你给它的是a.length还是a.length-1,要保证比较到最后一个元素 for (int j = i; j > lo && less(a[j], a[j-1]); j--) { exch(a, j, j-1); } } assert isSorted(a, lo, hi); }private static boolean less(Comparable v, Comparable w){ //比较v是否小于wreturn v.compareTo(w) < 0;}private static void exch(Comparable[] a, int i, int j) { //交换Comparable t = a[i];a[i] = a[j];a[j] = t;}}
改进三:取三个变量切分数组,将数组分为大于,等于,小于基准元素三部分,这样在递归时就可以剔除相等的元素,减小比较的次数,当数组中存在大量重复元素时,效果明显。
/** * 三向切分的快速排序--->应对含有大量重复元素的数组 * @author CANAAN * */public class Quick3way {public static void sort(Comparable[] a){StdRandom.shuffle(a);sort(a,0,a.length-1);}private static void sort(Comparable[] a, int lo, int hi) {if(hi <= lo) return;int lt=lo,i=lo+1,gt=hi; //三个变量,while(i<=gt){int cmp = a[i].compareTo(a[lo]);if(cmp<0) exch(a,lt++,i++); //a[lo..lt-1]是小于a[lo]的else if(cmp>0)exch(a,i,gt--); //a[gt+1..hi]是大于a[lo]的else i++; //a[lt..i-1]是等于a[lo]的}sort(a,lo,lt-1); //剔除了相等的元素,减少了递归中不必要的比较和交换!!!sort(a,lt+1,hi);}private static int partition(Comparable[] a, int lo, int hi) {int i = lo,j = hi+1; //i从第二个元素开始,j从最后一个元素开始while(true){while(less(a[++i], a[lo])) //比较前先计算++if(i == hi) break;while(less( a[lo],a[--j]))if(j == lo) break; if(i>=j) break;exch(a, i, j); //a[i]>基准,a[j]<基准,交换}exch(a,lo,j); //将基准元素调换到j处,此时左边都是小于a[j]的,右边都是大于a[j]的return j;}private static boolean less(Comparable v, Comparable w){ //比较v是否小于wreturn v.compareTo(w) < 0;}private static void exch(Comparable[] a, int i, int j) { //交换Comparable t = a[i];a[i] = a[j];a[j] = t;}public static void show(Comparable[] a){ //单行打印数组for (int i = 0; i < a.length; i++) {System.out.print(a[i] + " ");}System.out.println();}public static boolean isSorted(Comparable[] a){ //判断是否有序for (int i = 1; i < a.length; i++) {if(less(a[i], a[i-1]))return false;}return true;}public static void main(String[] args) {String[] a = StdIn.readAllStrings(); //注意这里是字符串,下面比较的是字符串的大小sort(a);show(a);}}
由快速排序得到的查找数组中第K小的元素的算法:
public static Comparable select(Comparable[] a,int k){StdRandom.shuffle(a);int lo = 0,hi = a.length-1;while(hi>lo){int j = partition(a,lo,hi);if(j<k) lo = j+1;else if(j>k) hi = j-1;elsereturn a[k];}return a[k];}
总结:
1. 快速排序平均需要~NlgN次比较和1/3 NlgN次交换。最坏时需要N^2/2次比较,随机打乱可以预防(专业术语叫做去除对输入的依赖);
2. 切分不平衡会影响程序的效率,所以基准元素的选取要注意,可以打乱元素,三取1,六取1,九取1;
3. 与归并排序相比,都是分治,归并在递归后处理数组,而快排在递归前处理数组。快速排序比归并排序稍快。在Java的Arrays类的sort方法源码里使用的是归并排序,原因是归并排序是稳定的,而快速排序是不稳定的。而对于基本数据类型的排序时使用的是快速排序,因为归并要消耗额外的空间。
注:
排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
插入排序和归并排序是稳定的;选择排序,希尔排序,快速排序是不稳定的
- 算法系列(五)快速排序及其改进(Java实现)
- 算法系列(四)归并排序及其改进(java实现)
- 快速排序算法及其改进算法实现
- 快速排序及其改进算法C++实现
- Java排序算法(五):快速排序
- Java排序算法(五):快速排序
- Java排序算法(五):快速排序
- Java排序算法(五):快速排序 .
- Java排序算法(五):快速排序
- 算法系列—快速排序及其优化(递归)
- 快速排序 及其改进
- 快速排序及其改进
- 快速排序及其改进
- 容易理解的快速排序算法及其相关算法的总结(含java实现方法)
- 算法学习之排序——冒泡排序及其改进算法(Java)
- 排序算法之五--冒泡排序及其改进
- 数据结构算法之排序系列Java、C源码实现(6)--快速排序
- 关于快速排序(quick sort)及其改进
- 计算机基础(三)视频格式
- JS 差集
- 计算机基础(二)概念术语
- JS字符串和json转换
- linux步步为营(6)--VI命令
- 算法系列(五)快速排序及其改进(Java实现)
- 解决 Linux 安装 httpd局域网无法访问
- 算法(四)KMP字符串模式匹配详解
- linux 下,编译c程序
- js得到当前窗口内的宽度和高度
- 第三十三次codeforces竞技结束 #441 Div 2
- ultrledit 编辑器
- jeasyui 弹出遮罩层
- jconsole 远端连