QuickSort

来源:互联网 发布:数码兽传说 网络侦探pc 编辑:程序博客网 时间:2024/06/07 06:56

在coursera上学习了quicksort的实现方法,老师给的代码一下子能完美运行,先给出老师的代码:

package part1.week3.quicksort;import edu.princeton.cs.algs4.StdRandom;/** . * Learn from coursera. * @author aerfalwl * */public class QuickSort {    public void sort(Comparable[] a) {        StdRandom.shuffle(a);        sort(a, 0, a.length - 1);    }    private void sort(Comparable[] a, int lo, int hi) {        if (lo >= hi) {            return;        }        int p = partition2(a, lo, hi);        sort(a, lo, p-1);        sort(a, p + 1, hi);    }    /**     * This function is what teacher taught in the coursera's class.     * @param a     * @param low     * @param hi     * @return     */    private int partition2(Comparable[] a, int low, int hi) {        int i = low, j = hi + 1;        while (true) {            while (less(a[++i], a[low])) {                if (i == hi) {                    break;                }            }            while (less(a[low], a[--j])) {                if (j == low) {                    break;                }            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }    private void exchane(Comparable[] a, int i, int j) {        Comparable temp = a[i];        a[i] = a[j];        a[j] = temp;    }    private boolean less(final Comparable a, final Comparable b) {        return a.compareTo(b) < 0;    }}

之后自己写了一个,但是却错误百出!下边这个是我想到的第一个版本,因为老师给的版本是++i, –j,我就想着,我要变成i++,j–,于是就变成了下边的版本,但是不是运行结果不正确,就是报数组越界,最后原因如下图注释所示。(注意,在该函数中,hi一定是大于low的,可以在sort函数中发现这一点)

/**    /**     * This function is wrong, because i is increased by 2 at the first step.     * The first step is i = low + 1, the second step is i++, so this may make     * outOFrange of array.     * @param a     * @param low     * @param hi     * @return     */    private int partition3(Comparable[] a, int low, int hi) {        int i = low + 1, j = hi;        while (true) {            while (less(a[i++], a[low])) {                if (i == hi) {                    break;                }            }            while (less(a[low], a[j--])) {                if (j == low) {                    break;                }            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }

之后我想,既然++, –运算符这个复杂,我就不嵌套使用了,于是出现了下边的版本。这个代码在输入数组不全是相等的时候能正确运行,但是如果输入数组中的数字全部都一样,就会死循环。我们先假设输入数字不全都相同。

/**     *      * @param a     * @param low     * @param hi     */    private int partition(Comparable[] a, int low, int hi) {        int i = low + 1, j = hi;        while (true) {            while (i <= hi && less(a[i], a[low])) {                i++;            }            while (j >= low && less(a[low], a[j])) {                j--;            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }

神奇的是,在判断条件少了=的时候仍然能正确运行

/**     * This function is different from partition, but it works too.     * @param a     * @param low     * @param hi     * @return     */    private int partition4(Comparable[] a, int low, int hi) {        int i = low + 1, j = hi;        while (true) {            while (i < hi && less(a[i], a[low])) {                i++;            }            while (j > low && less(a[low], a[j])) {                j--;            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }

于是我又改了下边的版本:原本以为和partition一样能正确运行的,但是却发现又是错误的。会出现j=-1的情况。

/**     * This function is wrong, it's only a little different from function partition.     * But be careful that a < b is not equal to !(b < a).     * We can draw it in the 数轴,!(b<a) contaions condition b == a.      * @param a     * @param low     * @param hi     * @return     */    private int partition5(Comparable[] a, int low, int hi) {        int i = low + 1, j = hi;        while (true) {            while (i <= hi && less(a[i], a[low])) {                i++;            }            while (j >= low && !less(a[j], a[low])) {                j--;            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }

换种角度看上边这个函数,其实它是将flag左边存小于**flag的数,而flag右边放大于等于flag的数,而之前几个函数都是flag左边存小于flag的数,而flag右边放大于**flag的数,因此当都是相同的数的时候,就会死循环。
为了避免越界和死循环,我们结合上边几个函数,发现下边这个函数能正确运行。

private int partition6(Comparable[] a, int low, int hi) {        int i = low + 1, j = hi;        while (true) {            while (i < hi && less(a[i], a[low])) {                i++;            }            while (j > low && !less(a[j], a[low])) {                j--;            }            if (i >= j) {                break;            }            exchane(a, i, j);        }        exchane(a, low, j);        return j;    }

综上所述,quicksort有很多边界条件需要考虑,一不小心就会跳坑里。
为了避免这些问题,我们可以使用3-way-partition的方法。

Quick-select
结合quick-sort,我们可以发现quick-select算法,即可以选出给定的数K,我们可以在数组中选出前K大的数和前K小的树,时间复杂度是线性的:

q:what is the expected running time to find the median of an array of n distinct keys using randomized quick-select?
a: linear (not linearithmic) (This is the main advantage of quickselect over quicksort the expected number of comparares is linear instead of linearithmic)

3-way-partition
在普通的快排算法中,如果要排序的数组中含有重复的值(duplicate key),该算法会有缺陷。如果在排序的时候将与哨兵值相同的值全放在一边,就会花费1/2N*N次比较,如果遇到与哨兵值相同的值的时候停下来,一共会花费NlgN次比较。下边的算法不会出现这种问题。

public void threeWaySort(Comparable[] a, int lo, int hi) {        if(hi <= lo) {            return;        }        Comparable v = a[lo];        int lt = lo;        int gt = hi;        int i = lo;        while(i <= gt) {            int ans = a[i].compareTo(v);            if(ans < 0) {                exchane(a, i++, lt++);            }            else if(ans > 0) {                exchane(a, i, gt--);            }            else{                i++;            }        }        threeWaySort(a, lo, lt - 1);        threeWaySort(a, gt + 1, hi);    }

这个算法在所有的数字都不同的时候,需要NlogN次比较,在所有数字都相同的时候,需要N次比较。

3-way-partition是用来改善2路排序算法中含有重复值得情况。

Java提供的Arrays.sort()中对于primitive data type,使用的是two-quick-sort,对于reference type,使用的排序方式为MergeSort。

The java API for Arrays.sort()for reference type requires that it is stable and guarantees nlogn performance. Neither of these are properties of standard quicksort.

Quicksort uses less memory and is faster in practice on typical inputs(and is typically used by Arrays.sort() when sorting primitive types, where stability is not relevant).