排序算法(三):快速排序和三向快速排序

快速排序

快速排序是一種分治的排序算法。它將一個數組分紅兩個子數組,將兩部分獨立的排序。快速排序和歸併排序是互補的:歸併排序將數組分紅兩個子數組分別排序,並將有序的子數組歸併以將整個數組排序;而快速排序將數組排序的方式則是當兩個子數組都有序時整個數組也就天然有序了。在歸併排序中,遞歸調用發生在處理整個數組以前,而快速排序中,遞歸調用發生在處理整個數組以後。java

快速排序算法是最快的通用排序算法,大部分狀況下,能夠直接選擇快速排序算法

 

 

如上圖所示,將第一個元素K做爲切分元素,使得左邊的元素均不大於K,右邊的元素均不小於K,這樣若是左右兩邊都有序了,那麼整個數組也就有序了。數組

這就是快速排序的基本原理,若是看過我前一篇關於歸併排序的博客,那麼很容易理解,這裏須要用到遞歸。ide

 

先上代碼:函數

}性能

 

    public void quickSort(Integer[] a,Integer low,Integer high) {

        if(low >= high)

            return;

        Integer j = partion(a,low,high);

        quickSort(a,low,j);

        quickSort(a,j+1,high);

    }    

 

這是遞歸調用的代碼,咱們先無論partion函數,如今只須要知道這個函數返回切分元素的下表,如上面圖示的那樣,返回K元素所在的下標,那麼就將數組分紅兩個數組:ui

  1. 全部小於K的元素的數組A
  2. 全部大於K的元素的數組B
  3. 一樣在A中找到一個切分元素將A分紅兩個子數組A'
  4. 一樣在B中找到一個切分元素將A分紅兩個子數組B'
  5. 一直遞歸下去,直到不能再切分
  6. 當最小切分的數組有序時,再一次往回遞歸排序,這樣最後整個數組就有序了。

說的有些亂,可是其實仍是很好理解的。下面給出partion函數的代碼:spa

    public Integer partion(Integer[] a,Integer low,Integer high) {

        Integer i = low;

        Integer j = high + 1;

        while(true) {

            //在循環中最好使用++i,若是使用i++,則會致使不少邏輯錯誤,i++是在循環結束時(即運行到大括號時)纔會++,這也是爲何前面須要使用high+1

            while(a[++i]<a[low])

                if(i == high)

                    break;

            

            while(a[--j]>a[low])

                if(j == low)

                    break;

            

            if(i >= j)

                break;

            

            change(a,i,j);

        }

        change(a,low,j);

        System.out.println("low: " + low);

        return j;

    }

 

 

這個函數的做用是能夠用下圖說明:指針

 

一、以第一個元素爲切分元素code

二、從low往右找到第一個大於a[low]的元素

三、從high往左找到第一個小於a[high]的元素

四、交換a[low]和a[high]

五、重複2-4,

六、最後就獲得如上圖第三幅圖所示的結果。

 

使得第一個切分元素v左邊的元素所有小於v,右邊的元素所有大於v,並返回v的下標j

 

 

完整代碼以下:

public class QuickSort extends SortBase {

 

    /* (non-Javadoc)

     * @see Sort.SortBase#sort(java.lang.Integer[])

     */

    @Override

    public Integer[] sort(Integer[] a) {

        print("init",a);

        quickSort(a,0,a.length-1);

        print("result",a);

        return a;

    }

    

    public void quickSort(Integer[] a,Integer low,Integer high) {

        if(low >= high)

            return;

        Integer j = partion(a,low,high);

        quickSort(a,low,j);

        quickSort(a,j+1,high);

    }

      

    

    public Integer partion(Integer[] a,Integer low,Integer high) {

        Integer i = low;

        Integer j = high + 1;

        while(true) {

            //在循環中最好使用++i,若是使用i++,則會致使不少邏輯錯誤,i++是在循環結束時(即運行到大括號時)纔會++,這也是爲何前面須要使用high+1

            while(a[++i]<a[low])

                if(i == high)

                    break;

            

            while(a[--j]>a[low])

                if(j == low)

                    break;

            

            if(i >= j)

                break;

            

            change(a,i,j);

        }

        change(a,low,j);

        System.out.println("low: " + low);

        return j;

    }

    

    public static void main(String[] args) {

        Integer[] a = {2,1,5,9,0,6,8,7,3};

        (new QuickSort()).sort(a);

    }

    

}

 

 

 

平均時間複雜度NlogN

 

 

三向快速排序

實際狀況中常常會出現含有大量重複元素的數組,例如咱們可能須要將大量人員資料按照生日排序,或是按照性別區分。在這些狀況下,快速排序算法性能尚可,但還有巨大的改進空間,這就是三向快速排序。

 

簡單的說,三向快速排序的原理爲:將數組切分紅三部分們分別對應於小於、等於、

大於切分元素的數組元素。與快速排序相比,增長了一個等於切分元素的區域。

流程以下:

從作到右遍歷數組一次,維護一個指針lt使得a[low…lt-1]中的元素都小於v,一個指針gt使得a[gt+1…high]中的元素都大於 v,一個指針i使得a[lt…i-1]中的元素都等於v,a[i…gt]中的元素都還爲肯定,一開始i和lo相等。

使得i遞增,對於a[i]:

a[i]小於v,將a[lt]和a[i]交換,將lt和i加一

a[i]大於v,將a[gt]和a[i]交換,將gt減一

a[i]等於v,將i加一

 

最後使數組呈現下圖中的狀況:

 

實現代碼:

    public void quickSort3Way(Integer[] a,Integer low,Integer high) {

        if(low >= high)

            return;

        

        Integer lt = low;

        Integer i = low + 1;

        Integer gt = high;

        while(i<=gt) {

            if(a[i] < a[lt]) {

                change(a,i,lt);

                i++;

                lt++;

            } else if(a[i] > a[lt]) {

                change(a,i,gt);

                //這裏不能使用i--,由於交換a[gt]和a[i]後,如今的a[i]並無肯定位置,若是使用i++,就將跳過交換後該元素的排序

                gt--;

            } else {

                i++;

            }

            

            print("a",a);

        }

        

        quickSort3Way(a,low,lt-1);

        quickSort3Way(a,gt+1,high);

    }

 

運行結果:

init: [2 ,1 ,5 ,9 ,0 ,6 ,8 ,7 ,3]

a: [1 ,2 ,5 ,9 ,0 ,6 ,8 ,7 ,3]

a: [1 ,2 ,3 ,9 ,0 ,6 ,8 ,7 ,5]

a: [1 ,2 ,7 ,9 ,0 ,6 ,8 ,3 ,5]

a: [1 ,2 ,8 ,9 ,0 ,6 ,7 ,3 ,5]

a: [1 ,2 ,6 ,9 ,0 ,8 ,7 ,3 ,5]

a: [1 ,2 ,0 ,9 ,6 ,8 ,7 ,3 ,5]

a: [1 ,0 ,2 ,9 ,6 ,8 ,7 ,3 ,5]

a: [1 ,0 ,2 ,9 ,6 ,8 ,7 ,3 ,5]

a: [0 ,1 ,2 ,9 ,6 ,8 ,7 ,3 ,5]

a: [0 ,1 ,2 ,6 ,9 ,8 ,7 ,3 ,5]

a: [0 ,1 ,2 ,6 ,8 ,9 ,7 ,3 ,5]

a: [0 ,1 ,2 ,6 ,8 ,7 ,9 ,3 ,5]

a: [0 ,1 ,2 ,6 ,8 ,7 ,3 ,9 ,5]

a: [0 ,1 ,2 ,6 ,8 ,7 ,3 ,5 ,9]

a: [0 ,1 ,2 ,6 ,5 ,7 ,3 ,8 ,9]

a: [0 ,1 ,2 ,5 ,6 ,7 ,3 ,8 ,9]

a: [0 ,1 ,2 ,5 ,6 ,3 ,7 ,8 ,9]

a: [0 ,1 ,2 ,5 ,3 ,6 ,7 ,8 ,9]

a: [0 ,1 ,2 ,3 ,5 ,6 ,7 ,8 ,9]

a: [0 ,1 ,2 ,3 ,5 ,6 ,7 ,8 ,9]

result: [0 ,1 ,2 ,3 ,5 ,6 ,7 ,8 ,9]

 

平均時間複雜度:介於N和NlogN之間