快速排序及优化(Java版)

快速排序(Quicksort)是对冒泡排序的一种改进。快速排序由C. A. R. Hoare在1962年提出。

一次快速排序详细过程:

选择数组第一个值作为枢轴值。

代码实现:

package QuickSort;

public class QuickSortRealize {

    public static void QuickSort(int[] arr){
        QSort(arr,0,arr.length-1);
    }

    //对顺序表子序列作快速排序     待排序序列的最小下标值low和最大下标值high
    public static void QSort(int[] arr,int low,int high){
        int pivot;
        if(low<high){
            pivot = Partition(arr,low,high);//将数组子序列一分为二

            QSort(arr, low, pivot-1);//对低子表递归排序
            QSort(arr, pivot+1, high);//对高子表递归排序
        }
    }

    //选择一个关键字,想尽办法将它放到一个位置,使得它左边的值都比它小,
    //右边的值都比它大,我们称这个关键字叫枢轴。
    public static int Partition(int[] arr,int low,int high){
        if(arr == null || low<0 || high>=arr.length){
            new Exception();
        }

        int pivotkey;
        pivotkey = arr[low];//选取第一个记录作枢轴记录

        while(low<high)//从表的两端向中间扫描
        {
            while(low<high && arr[high]>=pivotkey){//如果大于枢轴值,则下标减一,否则,跳出循环。
                high--;
            }
            Swap(arr, low, high);//交换
            while (low<high && arr[low]<pivotkey){//如果小于枢轴值,则下标加一,否则,跳出循环。
                low++;
            }
            Swap(arr, low, high);//交换
        }
        return low;
    }

    public static void Swap(int[] arr,int low,int high){
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {50,10,90,30,70,40,80,60,20};
        QuickSort(arr);
        for (int array : arr) {
            System.out.print(array+" ");
        }
        System.out.println();
    }
}

快速排序的时间性能取决于快速排序递归的深度,可以用递归数来描述算法的执行情况。如果递归树是平衡的,那么此时的性能也是最好的。

也就是说,在最优的情况下,快速排序算法的时间复杂度为 O(nlogn)。

就空间复杂度来说,主要是递归造成的栈空间的使用,最好情况,递归树的深度log2n ,其空间复杂度也就为 O(logn) ,最坏情况,需要进行递归调用,其空间复杂度为 O(n),平均情况 空间复杂度也为 (logn)。

可惜的是 关键字的比较和交换是跳跃进行的,因此,快速排序是 种不稳

定的排序方法。

优化算法:

1、优化选取枢轴

三数取中,即取三个关键字先进行排序,将中间数作为枢轴, 一般是取左端、右端和中间三个数, 也可以随机选取。

对于非常大的待排序的序列来说还是不足以保证能够选择出一个好的pivo tkey, 因此还有个办法是所谓的九数取中,先从数组中分三次取样,每次取三个数,三个样品各取出中数,然后从这三个中数当中再取出一个中数作为枢轴 。

package QuickSort;

public class QuickSortRealize {

    public static void QuickSort(int[] arr){
        QSort(arr,0,arr.length-1);
    }

    //对顺序表子序列作快速排序     待排序序列的最小下标值low和最大下标值high
    public static void QSort(int[] arr,int low,int high){
        int pivot;
        if(low<high){
            pivot = Partition(arr,low,high);//将数组子序列一分为二

            QSort(arr, low, pivot-1);//对低子表递归排序
            QSort(arr, pivot+1, high);//对高子表递归排序
        }
    }

    //选择一个关键字,想尽办法将它放到一个位置,使得它左边的值都比它小,
    //右边的值都比它大,我们称这个关键字叫枢轴。
    public static int Partition(int[] arr,int low,int high){

        if(arr == null || low<0 || high>=arr.length){
            new Exception();
        }

        int pivotkey;

        ChoosePivotkey(arr,low,high);//选取枢轴值

        pivotkey = arr[low];

        while(low<high)//从表的两端向中间扫描
        {
            while(low<high && arr[high]>=pivotkey){//如果大于枢轴值,则下标减一,否则,跳出循环。
                high--;
            }
            Swap(arr, low, high);//交换
            while (low<high && arr[low]<pivotkey){//如果小于枢轴值,则下标加一,否则,跳出循环。
                low++;
            }
            Swap(arr, low, high);//交换
        }
        return low;
    }

    public static void Swap(int[] arr,int low,int high){
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }

    //三数取中 选择枢轴    将枢轴值调至第一个位置
    public static void  ChoosePivotkey(int[] arr,int low,int high){
        int mid = low + (int)(high-low)/2;
        if(arr[low]>arr[high]){//保证左端较小
            Swap(arr, low, high);
        }
        if(arr[mid]>arr[high]){//保证中间较小
            Swap(arr, mid, high);
        }
        //此时最大值在最右边
        if(arr[mid]>arr[low]){//保证中间较小
            Swap(arr, mid, low);
        }
    }

    public static void main(String[] args) {
        int[] arr = {50,10,90,30,70,40,80,60,20};
        QuickSort(arr);
        for (int array : arr) {
            System.out.print(array+" ");
        }
        System.out.println();
    }
}

2、优化不必要的交换

package QuickSort;

public class QuickSortRealize3 {

    public static void QuickSort(int[] arr){
        QSort(arr,0,arr.length-1);
    }

    //对顺序表子序列作快速排序     待排序序列的最小下标值low和最大下标值high
    public static void QSort(int[] arr,int low,int high){
        int pivot;
        if(low<high){
            pivot = Partition(arr,low,high);//将数组子序列一分为二

            QSort(arr, low, pivot-1);//对低子表递归排序
            QSort(arr, pivot+1, high);//对高子表递归排序
        }
    }

    //选择一个关键字,想尽办法将它放到一个位置,使得它左边的值都比它小,
    //右边的值都比它大,我们称这个关键字叫枢轴。
    public static int Partition(int[] arr,int low,int high){

        if(arr == null || low<0 || high>=arr.length){
            new Exception();
        }

        int pivotkey;
        pivotkey = arr[low];//选取第一个记录作枢轴记录

        int tempCopy = pivotkey;//将枢轴值备份到tempCopy中

        while(low<high)//从表的两端向中间扫描
        {
            while(low<high && arr[high]>=pivotkey){//如果大于枢轴值,则下标减一,否则,跳出循环。
                high--;
            }
            //Swap(arr, low, high);//交换
            arr[low] = arr[high];//采用替换而不是交换的方式进行操作
            while (low<high && arr[low]<pivotkey){//如果小于枢轴值,则下标加一,否则,跳出循环。
                low++;
            }
            //Swap(arr, low, high);//交换
            arr[high] = arr[low];//采用替换而不是交换的方式进行操作
        }
        arr[low] = tempCopy;//将枢轴值替换回arr[low]
        return low;//返回枢轴值所在位置
    }

    public static void Swap(int[] arr,int low,int high){
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {50,10,90,30,70,40,80,60,20};
        QuickSort(arr);
        for (int array : arr) {
            System.out.print(array+" ");
        }
        System.out.println();
    }
}

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

快速排序适用于非常大的数组的解决办法, 那么相反的情况,如果数组非常小,其实快速排序反而不如直接插入排序来得更好(直接插入是简单排序中性能最好的)。其原因在于快速排序用到了递归操作,在大量数据排序时,这点性能影响相对于它的整体算法优势是可以忽略的,但如果数组只有几个记录需要排序时,这就成了大材小用,因此我们需要改进一下 QSort函数。

package QuickSort;

public class QuickSortRealize4 {
    final static int MAX_LENGTH_INSERT_SORT = 7;

    public static void QuickSort(int[] arr){
        QSort(arr,0,arr.length-1);
    }

    //对顺序表子序列作快速排序     待排序序列的最小下标值low和最大下标值high
    public static void QSort(int[] arr,int low,int high){
        int pivot;
        if((high-low)>MAX_LENGTH_INSERT_SORT){
            pivot = Partition(arr,low,high);//将数组子序列一分为二

            QSort(arr, low, pivot-1);//对低子表递归排序
            QSort(arr, pivot+1, high);//对高子表递归排序
        }
        else{
            insertSort(arr);
        }
    }

    //选择一个关键字,想尽办法将它放到一个位置,使得它左边的值都比它小,
    //右边的值都比它大,我们称这个关键字叫枢轴。
    public static int Partition(int[] arr,int low,int high){

        if(arr == null || low<0 || high>=arr.length){
            new Exception();
        }

        int pivotkey;
        pivotkey = arr[low];//选取第一个记录作枢轴记录

        int tempCopy = pivotkey;//将枢轴值备份到tempCopy中

        while(low<high)//从表的两端向中间扫描
        {
            while(low<high && arr[high]>=pivotkey){//如果大于枢轴值,则下标减一,否则,跳出循环。
                high--;
            }
            //Swap(arr, low, high);//交换
            arr[low] = arr[high];//采用替换而不是交换的方式进行操作
            while (low<high && arr[low]<pivotkey){//如果小于枢轴值,则下标加一,否则,跳出循环。
                low++;
            }
            //Swap(arr, low, high);//交换
            arr[high] = arr[low];//采用替换而不是交换的方式进行操作
        }
        arr[low] = tempCopy;//将枢轴值替换回arr[low]
        return low;//返回枢轴值所在位置
    }

    public static void Swap(int[] arr,int low,int high){
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }

    public static void insertSort(int[] arr){
        int i,j;
        //4,2,1,7,8
        for(i=1;i<arr.length;i++){
            if(arr[i-1]>arr[i]){
                //temp=2
                int temp = arr[i];//设置哨兵
                //必须要保证数组下标>=0,才for循环
                for(j= i-1; j>=0&&arr[j]>temp ;j--){
                    arr[j+1]=arr[j];//arr[1]=4
                }
                //j=-1
                arr[j+1]=temp;//arr[0]=2
                //2 4 1 7 8
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {50,10,90,30,70,40,80,60,20};
        QuickSort(arr);
        //insertSort(arr);
        for (int array : arr) {
            System.out.print(array+" ");
        }
        System.out.println();
    }
}

我们增加了一个判断, high-low不大于某个常数时(有资料认为7较合适,认为5更合理理,实际应用可适当调整) ,就用直接插入排序,这样就能保证最大化地利用两种排序的优势来完成排序。

4、优化递归操作

我们知道,递归对性能是有一定影响的, QSort 函数在其尾部有两次递归操作。

如果待排序的序列划分极端不平衡,递归深度将趋近与N ,而不是平衡时的 logN,就不仅仅是速度快慢的问题了,栈的大小是很有限的,每次递归调用都会耗费一定的空间 ,函数的参数越多,每次递归耗费的空间也越多。如果能减少递归,将会提高性能。我们对 QSort 实施尾递归优化

package QuickSort;

public class QuickSortRealize5 {
    final static int MAX_LENGTH_INSERT_SORT = 7;

    public static void QuickSort(int[] arr){
        QSort(arr,0,arr.length-1);
    }

    //对顺序表子序列作快速排序     待排序序列的最小下标值low和最大下标值high
    public static void QSort(int[] arr,int low,int high){
        int pivot;
        if((high-low)>MAX_LENGTH_INSERT_SORT){
            while(low<high){
                pivot = Partition(arr,low,high);//将数组子序列一分为二
                QSort(arr, low, pivot-1);//对低子表递归排序
                /////////////////////////////////////////////////////
                //QSort(arr, pivot+1, high);//对高子表递归排序
                low = pivot + 1;
            }
        }
        else{
            insertSort(arr);
        }
    }

    //选择一个关键字,想尽办法将它放到一个位置,使得它左边的值都比它小,
    //右边的值都比它大,我们称这个关键字叫枢轴。
    public static int Partition(int[] arr,int low,int high){

        if(arr == null || low<0 || high>=arr.length){
            new Exception();
        }

        int pivotkey;
        pivotkey = arr[low];//选取第一个记录作枢轴记录

        int tempCopy = pivotkey;//将枢轴值备份到tempCopy中

        while(low<high)//从表的两端向中间扫描
        {
            while(low<high && arr[high]>=pivotkey){//如果大于枢轴值,则下标减一,否则,跳出循环。
                high--;
            }
            //Swap(arr, low, high);//交换
            arr[low] = arr[high];//采用替换而不是交换的方式进行操作
            while (low<high && arr[low]<pivotkey){//如果小于枢轴值,则下标加一,否则,跳出循环。
                low++;
            }
            //Swap(arr, low, high);//交换
            arr[high] = arr[low];//采用替换而不是交换的方式进行操作
        }
        arr[low] = tempCopy;//将枢轴值替换回arr[low]
        return low;//返回枢轴值所在位置
    }

    public static void Swap(int[] arr,int low,int high){
        int temp = arr[low];
        arr[low] = arr[high];
        arr[high] = temp;
    }

    public static void insertSort(int[] arr){
        int i,j;
        //4,2,1,7,8
        for(i=1;i<arr.length;i++){
            if(arr[i-1]>arr[i]){
                //temp=2
                int temp = arr[i];//设置哨兵
                //必须要保证数组下标>=0,才for循环
                for(j= i-1; j>=0&&arr[j]>temp ;j--){
                    arr[j+1]=arr[j];//arr[1]=4
                }
                //j=-1
                arr[j+1]=temp;//arr[0]=2
                //2 4 1 7 8
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {50,10,90,30,70,40,80,60,20};
        QuickSort(arr);
        //insertSort(arr);
        for (int array : arr) {
            System.out.print(array+" ");
        }
        System.out.println();
    }
}

当我们将 if 改成 while 后,因为第一次递归以后,变量low就没有用处了,所以可以将 pivot+1 赋值给low,再循环后,来一次 Partition

(arr,low,high)时,其效果等同于 “QSort(arr, pivot+1, high);”。结果相同,但因采用迭代而不是递归的方法可以缩减堆栈深度,从而提高了整体性能。

时间: 2024-10-17 00:47:23

快速排序及优化(Java版)的相关文章

快速排序及优化(Java实现)

普通快速排序 找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base.再分为两个子数组的排序.如此递归下去. public class QuickSort { public static <T extends Comparable<? super T>> void sort(T[] arr) { sort(arr, 0, arr.length - 1); } public static <T extends Comparabl

快速排序,一个爱情故事-java版

public static void myquicksort(int[] ages,int girl,int boy){ //这是一个站在数组两端,追求完美爱情的故事 //年龄不匹配的不要 //第0步 if(girl > boy){return;} int perfect = ages[girl]; int i = girl;//分身 int j = boy;//分身 int tmp = 0; //寻找的终止条件是男生的分身的位置和女生分身的位置相等 //第四步 while(i!=j) { //

排序算法Java版,以及各自的复杂度,以及由堆排序产生的top K问题

常用的排序算法包括: 冒泡排序:每次在无序队列里将相邻两个数依次进行比较,将小数调换到前面, 逐次比较,直至将最大的数移到最后.最将剩下的N-1个数继续比较,将次大数移至倒数第二.依此规律,直至比较结束.时间复杂度:O(n^2) 选择排序:每次在无序队列中"选择"出最大值,放到有序队列的最后,并从无序队列中去除该值(具体实现略有区别).时间复杂度:O(n^2) 直接插入排序:始终定义第一个元素为有序的,将元素逐个插入到有序排列之中,其特点是要不断的 移动数据,空出一个适当的位置,把待插

AKKA文档(java版)

目前我正在翻译AKKA官网文档.翻译:吴京润 译者注:本人正在翻译AKKA官网文档,本篇是文档第一章,欢迎有兴趣的同学加入一起翻译.更多内容请读这里:https://tower.im/projects/ac49db18a6a24ae4b340a5fa22d930dc/lists/ded96c34f7ce4a6bb8b5473f596e1008/show/https://tower.im/projects/ac49db18a6a24ae4b340a5fa22d930dc/todos/640e53d

程序员必须掌握的8大排序算法(Java版)

程序员必须掌握的8大排序算法(Java版) 提交 我的评论 加载中 已评论 程序员必须掌握的8大排序算法(Java版) 2015-07-28 极客学院 极客学院 极客学院 微信号 jikexueyuan00 功能介绍 极客学院官方帐号,最新课程.活动发布.欢迎大家反馈问题哟^_^ 本文由网络资料整理而来,如有问题,欢迎指正! 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配排序(基数排序) 所需辅助空

Java版菠菜程序搭建BC网站项目开发给演示站包网小白的一些意见

Java版程序是目前世界上公认的高安全性扩展性最好,页面反馈速度极佳的BC网站搭建程序语言,在菲律宾,柬埔寨等东南亚国家,对Java版菠菜程序搭建的网站运营是由衷放心的特别是一些集团公司.在做BC网站项目之前,需要Q先分析3530自己的实际2742情况55,了解自己想要去做的具体方向,打算如何运营,团队如何去组建(网站一个人是运营不起来的),如果这些你都没有清楚的认识那我劝你还是别做了,应为做了对你来说没有具体的运营方向也就没办法运营好,运营不好的网站对你来说根本一文不值.考虑好了网站的运营方向

Java版贪吃蛇(比较完善的版本)

很认真的写的一个java版的贪吃蛇游戏,图形界面,支持菜单操作,键盘监听,可加速,减速,统计得分,设定运动速度,设定游戏背景颜色等!应该没有Bug了,因为全被我修改没了.哈哈. 下面是项目各包及类的层次关系: 游戏的主要运行界面截图如下: 下面是部分代码,详细源码见此链接:http://pan.baidu.com/s/1bnubnzh //Snake类: package com.huowolf.entities; import java.awt.Color; import java.awt.Gr

快速排序的优化(一)随机化快速排序

这周研究快速排序优化策略,首先是利用随机化对快速排序进行优化. 众所周知,之前的基础快速排序算法,其效率一个关键点就在与划分元素的选取,由于之前一直选取的是第一个元素,所以当遇到特殊输入,比如太大或者太小,就会造成区间划分极度不合理. 引入随机化,就是在每一次划分的时候随机选取一个元素作为关键字,用它来进行划分.由于每一次划分都是随机选取,所以每一次都选到不好的元素概率低,可以作为一个优化的方向. 和之前的基础快速排序主要区别有两个 1.首先是partition部分 利用rand产生随机数  ,

回溯算法解八皇后问题(java版)

八皇后问题是学习回溯算法时不得不提的一个问题,用回溯算法解决该问题逻辑比较简单. 下面用java版的回溯算法来解决八皇后问题. 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 思路是按行来规定皇后,第一行放第一个皇后,第二行放第二个,然后通过遍历所有列,来判断下一个皇后能否放在该列.直到所有皇后都放完,或者放哪

pureMVC java版搭建流程

转自:http://blog.csdn.net/sutaizi/article/details/6588004 pureMVC 是一个轻量级的框架 它在 flex中非常流行(和cairngorm差不多火) 目前几乎已经移植到所有平台上. 下面实现java版得pureMVC搭建 先给大家看总体的层次: 众所周知 pureMVC是一个轻量级的MVC框架 分为 Model ,View ,Controller.这三个是pureMVC的核心. 除此之外pureMVC中含有一个单例模式Facade.faca