手记

学习笔记----快速排序的java实现及其改良

    快速排序算法是我们学习数据结构必学的算法之一,虽然在java中,对列表的排序可直接使用Collections.sort(List l) (这使用的是归并排序算法) ,对基本类型的数组也有Arrays.sort()(这使用的是改进的快速排序算法),但实现一个快速排序,仍能给自己很多启发。很多算法书上都使用c或者c++实现快排算法,但是作为一个写惯了java的程序员,再去写c或c++有着各种水土不服(没有丰富的API库帮忙),加上jvm虚拟机性能的提高,也就可以用java回顾一下以前的快速排序算法,也算为面试做个准备。
    快速排序的平均时间复杂度能达到O(n*log n),最坏情况下 (元素基本有序,中轴选择总是最大或最小 、元素,此时快速排序退化成冒泡排序)为O(n^2)。本例中使用随机选择中轴元素的方式降低了最坏情况可能性,并在子数组比较小(在书上推荐是5-15个元素即比较小)的时候停止快排,采用插入排序(最好情况既元素基本有序下,时间复杂度为O(n))完成整个List的排序。
    有关快速排序的原理,算法书上已经讲的很清楚了,按照我个人的理解,快速排序就是如何将整个数组通过选择的中轴(key)划分成两个子数组,并且满足(<key) key (>key)这种关系。这就算完成一趟快排了,剩下的只需要递归(<key)和(>key)即可。
    快排代码如下:

public class QuickSort {

    static List<Integer> list = new ArrayList<>();
    private static int END_QUICKSORT = 7;    //当子数组小于这个数即不再递归。

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for (int i = 0; i < 100000; i++) {
            list.add((int) (Math.random() * 1000));//生成大量数据
        }
        //long a1 = System.nanoTime();//排序用的时间
        quickSort(list);

        //long a2 = System.nanoTime();
        //System.out.println(a2 - a1);
        for (Integer temp : list) {
            System.out.println(temp);
        }
    }

    public static void quickSort(List<Integer> list) {

        quick(list, 0, list.size() - 1);//这里不能用list.size(),因为接下来需要用hight访问list。     
        // 对快排后基本有序数组进行一次插入排序
        insertSort(list);
    }

    private static void insertSort(List<Integer> list2) {
        // TODO Auto-generated method stub
        for (int i = 1; i < list2.size(); i++) {
            int v = list2.get(i);
            int j = i - 1;
            for (; j >= 0 && list2.get(j) > v; j--) {
                list2.set(j + 1, list.get(j));
            }
            list.set(j + 1, v);
        }
    }

    private static void quick(List<Integer> list, int low, int hight) {
        // TODO Auto-generated method stub
        if (low < hight) {
            int key = partition(list, low, hight); // 选择中轴并对数组排序(成为(<key)key (>key) 形式)
            if (key - low > END_QUICKSORT)//当(key,hight)范围小到一定程度,不再进行快排。
                quick(list, low, key - 1); // 对左右半部分继续使用快排。
            if (hight - key > END_QUICKSORT)
                quick(list, key + 1, hight); 
        }
    }

    private static int partition(List<Integer> list, int low, int hight) {
        // TODO Auto-generated method stub
        int rand = (int) (Math.random() * (hight - low)) + low;//随机选择hight-low中的一个数。
        int key = list.get(rand);
        if (rand != low) {//将中轴元素交换到list第一位
            int temp = list.get(low);
            list.set(low, key);
            list.set(rand, temp);
        }
        while (low < hight) {
            while (low < hight && list.get(hight) >= key) //这里可以模仿JDK传入一个比较器,这样可以排序任意List<?> 元素。
                hight--;
            list.set(low, list.get(hight));//当hight指向的元素<key,循环退出,说明它应该交换到low部分去,下面也是一样。
            while (low < hight && list.get(low) <= key)
                low++;
            list.set(hight, list.get(low));
        }

        list.set(low, key);//中轴元素回到low指向的位置(也就是数组的中间部分)
        return low;
    }
}

然而比较下原快速排序和随机化快排的代码执行速度发现两者差别并不大,当然在电脑上还存在其它进程影响执行,不够准确,所以这个时间值就不贴出来了。

在JDK中,对快速排序做了如下三个优化,有兴趣的同学可以去看看Arrays.sort方法的源码。
    1、就像上面提到的,对于元素数小于7(java内定值)的数组,直接返回插入排序的结果(因为插入排序对基本有序数组和简单数组能保证线性性能,所以一些O(n^2)的算法不一定就不能使用)
    2、使用三平均划分法(数组最左边,最右边,最中间的元素的中位数)找到中轴。
    3、对于这一点我还不太理解,即是说:将和中轴相等的元素都放到数组中间,如:4,3,1,15,15,15,23,34,21 . 这样只需排序(4,3,1)和(23,34,21)这三个子数组,对于重复元素多的数组可减少问题规模。
16人推荐
随时随地看视频
慕课网APP

热门评论

h

110

查看全部评论