快速掌握Java幾種排序演算法的區別與排序演算法的應用

SnailClimb發表於2017-11-04

上一篇文章《排序演算法(Java)——那些年面試常見的排序演算法》
介紹這幾種排序演算法區別之前我們先來了解一個概念:

穩定性:

  如果一個排序演算法能夠保留陣列中重複元素的相對位置則可以被稱為是穩定的。這個性質在許多情況下很重要。例如:在考慮一個需要處理大量含有地理位置和時間戳的事件網際網路商業應用程式。首先,我們在時間發生時將它們挨個儲存在一個陣列中,這樣在陣列中它們已經是按照時間順序排序好了的。現在假設在進一步處理前將按照地理位置劃分。一種簡單的方法是將陣列按照位置排序。如果排序演算法不是穩定的,排序後的每個城市的交易可能不會再是按照時間順序排序的了。很多情況下,不熟悉排序穩定性的程式設計師在第一次遇到這種情形的時候會在使用不穩定排序演算法後把陣列弄的一團糟而一臉懵逼。上一篇文章中我們講到的插入排序和歸併排序都屬於穩定的,其他幾個(選擇,希爾,快速,堆排序)都是不穩定的。有很多辦法能夠將任意排序演算法程式設計穩定的,但一般只有在穩定性是必要的情況下穩定的排序演算法才有優勢。不要想當然認為演算法具有穩定性是理所當然的,但事實上沒有任何實際應用中常見的演算法不是用了大量額外的時間和空間才做到了這一點(研究人員開發了這樣的演算法,但應用程式設計師發現它們太複雜了,無法在實際開發中使用)。
上述例子如下圖所示:

我們究竟該使用哪種演算法

  下表總結了我們面試常見的排序演算法的各種重要性質。除了希爾排序(它的複雜度只是一個近似),插入排序(它的複雜度取決於輸入元素的排列情況)和快速排序的兩個版本(它門的複雜度和概率有關,取決於輸入元素的分不清情況)之外,將這些執行時間的增長數量級乘以適當的常數就能夠大致估計出其執行時間。這裡的常數有時和演算法有關(比如堆排序的比較次數是歸併排序的兩倍,且兩者訪問陣列的次數都比快速排序多得多),但主要取決於演算法的實現,Java編譯器以及你的計算機,這些因素決定了需要執行的機器指令的數量以及每條指令所需的執行時間。最重要的是,因為這些都是常數,你能通過較小的N得到的實現資料和我們標準雙倍測試來推測較大N所需的執行時間。


快速排序是最快的通用排序演算法。自從數十年前快速排序發明以來,它在無數計算機系統中的無數實現都已經證明了這一點。總的來說,快速排序之所以最快是因為它的內迴圈中的指令很少(而且它還能利用緩衝,因為它總算是順序地訪問資料),所以它的執行時間的增長數量級為~cNlgN,而這裡的c比其他線性對數級別的排序演算法的相應常數都要小。在使用三向切分優化之後,快速排序對於實際應用中可能出現的某些分佈的輸入變成線性級別的了,而其他的排序演算法仍然需要線性對數時間。
因此,在大多數實際情況中,快速排序是最佳的選擇。當然,面對排序演算法和各式計算機及系統,這麼一句乾巴巴的話可能很難讓人信服。例如我們已經見過一個明顯的例外:如果穩定性很重要而空間又不是問題,歸併排序可能是最好的。我們會在後續的深入學習中見到更多的例外。

Java系統庫的排序演算法:

  Java系統庫主要排序方法java.util.Arrays.sort()。更具不同的引數型別,它實際代表了一系列排序方法:

  • 每種原始資料型別都有一個不同排序方法;
  • 一個適用於所有實現了Comparable介面的資料型別的排序方法;
  • 一個適用於實現了比較器Comparator的資料型別的排序方法。
    Java的系統程式設計師選擇對原始資料型別使用(三向切分的)快速排序,對引用型別使用歸併排序。這些選擇實際上也暗示用速度和空間(對於原始資料型別)來換取穩定性(對於引用型別)。
    當為實際應用開發Java程式,你會發現Java的Arrays.sort()發現(可能加上你自己實現的compareTo()或者compare())已經基本夠用了,因為它使用的三向快速排序和歸併排序都是經典。
    下面看一個重寫compareTo方法來實現按年齡排序的例項
package map;
import java.util.Set;
import java.util.TreeMap;

public class TreeMap2 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TreeMap<Person, String> pdata = new TreeMap<Person, String>();
        pdata.put(new Person("張三", 30), "zhangsan");
        pdata.put(new Person("李四", 20), "lisi");
        pdata.put(new Person("王五", 10), "wangwu");
        pdata.put(new Person("小紅", 5), "xiaohong");
         //得到key的值的同時得到key所對應的值
        Set<Person> keys = pdata.keySet();
        for (Person key : keys) {
            System.out.println(key.getAge() + "-" + key.getName());

        }
    }
}

// person物件沒有實現Comparable介面,所以必須實現,這樣才不會出錯,才可以使treemap中的資料按順序排列
// 前面一個例子的String類已經預設實現了Comparable介面,詳細可以檢視String類的API文件,另外其他
// 像Integer類等都已經實現了Comparable介面,所以不需要另外實現了

class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    /**
     *TODO重寫compareTo方法實現按年齡來排序
     */
    @Override
    public int compareTo(Person o) {
        // TODO Auto-generated method stub
        if (this.age > o.getAge()) {
            return 1;
        } else if (this.age < o.getAge()) {
            return -1;
        }
        return age;
    }
}複製程式碼

相關文章