C++,java算法與數據結構(一)--慕課網筆記

第1章 當我們談論算法的時候,我們在談論什麼?
1-1 我們究竟爲什麼要學習算法

爲什麼要學習算法?

大公司面試?
因爲算法無處不在

  • IDE
  • 搜索引擎 搜索算法+排序算法
  • Siri 語音算法
  • 推薦算法
  • 電影后期
  • 迷宮生成
  • 掃雷
  • 電腦AI
  • 計算機視覺
  • AR
  • PS 魔棒工具
  • 壓縮軟件
  • 數據庫 算法庫

我們每天都在接觸算法

學好算法,才能創造出更有意義的東西

算法之美

1-2 課程介紹

學習要求

  • 語言:C++
  • 擁有自己的編程環境
  • 算法其實是和語言無關的
  • 以後爭取支持更多語言:Java,Python,Javascript,Swift…
  • 課程Github:https://github.com/liuyubobobo/Play-with-Algorithms
  • 需要掌握最基礎的語言知識
  • 瞭解數組,鏈表,推,棧等線性結構
  • 對基本算法知識有常識性瞭解:如遞歸、遍歷、算法複雜度

  • 線性(排序)

  • 樹形結構
  • 圖形結構

白板編程
白板編程,不給你編譯器,給你一個白板,直接在上面寫代碼
白板編程考察的是大家算法思想的基本功

數據結構的重要性

“I will in fact,claim that the difference between a bad programmer
and a good one is whether he considers his code or his data structures
more important.Bad programmers worry about the code.Good
Programmers worry about data structures and their relationships.”
–Linux Torvalds(creator of Linux)

Algorithms + Data Structures = Programs

無法覆蓋所有的數據結構和算法

  • 堆:斐波那契堆?
  • 樹:線段樹?
  • 圖:網絡流?

算法思想

  • 分治算法:歸併排序,快速排序…
  • 貪心算法:最小生成樹…
  • 動態規劃:最短路徑…
  • 遞歸搜索:樹形結構…

每個細分領域都是算法

  • 圖形學
  • 圖像學
  • 機器學習
  • 人工智能
  • 機器挖掘
  • 操作系統
  • 編譯原理
  • 網絡安全
  • 虛擬現實
  • 高性能計算

讓我們一起體會算法之美

Computer programming is an art,because it
applies accumulated knowledge to the word,
because it requires skill and ingenuity,and
especially because it produces objects of
beauty.A programmer who subconsciously
views himself as an artist will enjoy what he
does and will do it better.
–Donald Knuth

第2章 排序基礎
2-1 選擇排序法
public class SelectionSort {
    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(int[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                if( arr[j] < arr[minIndex] )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {

        int[] arr = {10,9,8,7,6,5,4,3,2,1};
        SelectionSort.sort(arr);
        for( int i = 0 ; i < arr.length ; i ++ ){
            System.out.print(arr[i]);
            System.out.print(' ');
        }
        System.out.println();
    }
}
2-2 使用模板(泛型)編寫算法

O(n^2)的排序算法

  • 基礎
  • 編碼簡單,易於實現,是一些簡單情景的首選
  • 在一些特殊情況下,簡單的排序算法更有效
  • 簡單的排序算法思想衍生出複雜的排序算法
  • 作爲子過程,改進更復雜的排序算法

選擇排序 Selection SortedMap

public class SelectionSort {

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {

        // 測試Integer
        Integer[] a = {10,9,8,7,6,5,4,3,2,1};
        SelectionSort.sort(a);
        for( int i = 0 ; i < a.length ; i ++ ){
            System.out.print(a[i]);
            System.out.print(' ');
        }
        System.out.println();

        // 測試Double
        Double[] b = {4.4, 3.3, 2.2, 1.1};
        SelectionSort.sort(b);
        for( int i = 0 ; i < b.length ; i ++ ){
            System.out.print(b[i]);
            System.out.print(' ');
        }
        System.out.println();

        // 測試String
        String[] c = {"D", "C", "B", "A"};
        SelectionSort.sort(c);
        for( int i = 0 ; i < c.length ; i ++ ){
            System.out.print(c[i]);
            System.out.print(' ');
        }
        System.out.println();

        // 測試自定義的類 Student
        Student[] d = new Student[4];
        d[0] = new Student("D",90);
        d[1] = new Student("C",100);
        d[2] = new Student("B",95);
        d[3] = new Student("A",95);
        SelectionSort.sort(d);
        for( int i = 0 ; i < d.length ; i ++ )
            System.out.println(d[i]);
    }
}
public class Student implements Comparable<Student> {

    private String name;
    private int score;

    public Student(String name, int score){
        this.name = name;
        this.score = score;
    }

    // 定義Student的compareTo函數
    // 如果分數相等,則按照名字的字母序排序
    // 如果分數不等,則分數高的靠前
    @Override
    public int compareTo(Student that) {

        if( this.score == that.score )
            return this.name.compareTo(that.name);

        if( this.score < that.score )
            return 1;
        else if( this.score > that.score )
            return -1;
        else // this.score == that.score
            return 0;
    }

    // 定義Student實例的打印輸出方式
    @Override
    public String toString() {
        return "Student: " + this.name + " " + Integer.toString( this.score );
    }
}
2-3 隨機生成算法測試用例
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object arr[]) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }
}
2-4 測試算法的性能
public class SelectionSort{

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {

        // 測試排序算法輔助函數
        int N = 20000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("bobo.algo.SelectionSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object arr[]) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        // * 依然是,使用反射機制並不是這個課程的重點, 大家也完全可以使用自己的方式書寫代碼, 最終只要能夠測試出自己書寫的算法的效率即可
        // * 推薦大家閱讀我在問答區向大家分享的一個學習心得: 【學習心得分享】請大家抓大放小,不要糾結於C++語言的語法細節
        // * 鏈接: http://coding.imooc.com/learn/questiondetail/4100.html
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
2-5 插入排序法
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {

            // 尋找元素arr[i]合適的插入位置

            // 寫法1
//            for( int j = i ; j > 0 ; j -- )
//                if( arr[j].compareTo( arr[j-1] ) < 0 )
//                    swap( arr, j , j-1 );
//                else
//                    break;

            // 寫法2
            for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
                swap(arr, j, j-1);

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 20000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_5.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較SelectionSort和InsertionSort兩種排序算法的性能效率
    // 此時,插入排序比選擇排序性能略低
    public static void main(String[] args) {

        int N = 20000;
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_5.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_5.InsertionSort", arr2);

        return;
    }
}
public class SelectionSort{

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試SelectionSort
    public static void main(String[] args) {

        int N = 20000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_5.SelectionSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
2-6 插入排序法的改進
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {

            // 尋找元素arr[i]合適的插入位置

            // 寫法1
//            for( int j = i ; j > 0 ; j -- )
//                if( arr[j].compareTo( arr[j-1] ) < 0 )
//                    swap( arr, j , j-1 );
//                else
//                    break;

            // 寫法2
//            for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
//                swap(arr, j, j-1);

            // 寫法3
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 20000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_6.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較SelectionSort和InsertionSort兩種排序算法的性能效率
    // 優化後,插入排序比選擇排序性能略好
    // 對於有序性強的數組,插入排序遠遠優於選擇排序
    public static void main(String[] args) {

        int N = 20000;

        // 測試1 一般測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_6.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_6.InsertionSort", arr2);

        System.out.println();


        // 測試2 有序性更強的測試
        System.out.println("Test for more ordered random array, size = " + N + " , random range [0,3]");

        arr1 = SortTestHelper.generateRandomArray(N, 0, 3);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_6.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_6.InsertionSort", arr2);

        System.out.println();


        // 測試3 測試近乎有序的數組
        int swapTimes = 100;
        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_6.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_6.InsertionSort", arr2);

        return;
    }
}
public class SelectionSort{

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試SelectionSort
    public static void main(String[] args) {

        int N = 20000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_6.SelectionSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
2-7 更多關於O(n^2)排序算法的思考
  • Bubble Sort 冒泡排序
public class BubbleSort {

    // 我們的算法類不允許產生任何實例
    private BubbleSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        boolean swapped = false;
        //int newn; // 理論上,可以使用newn進行優化,但實際優化效果較差

        do{
            swapped = false;
            //newn = 0;
            for( int i = 1 ; i < n ; i ++ )
                if( arr[i-1].compareTo(arr[i]) > 0 ){
                    swap( arr , i-1 , i );
                    swapped = true;

                    // 可以記錄最後一次的交換位置,在此之後的元素在下一輪掃描中均不考慮
                    // 實際優化效果較差,因爲引入了newn這個新的變量
                    //newn = n;
                }

            //n = newn;

            // 優化, 每一趟Bubble Sort都將最大的元素放在了最後的位置
            // 所以下一次排序, 最後的元素可以不再考慮
            // 理論上, newn的優化是這個優化的複雜版本,應該更有效
            // 實測, 使用這種簡單優化, 時間性能更好
            n --;
        }while(swapped);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.BubbleSort", arr);

        return;
    }

}
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {

            // 尋找元素arr[i]合適的插入位置

            // 寫法1
//            for( int j = i ; j > 0 ; j -- )
//                if( arr[j].compareTo( arr[j-1] ) < 0 )
//                    swap( arr, j , j-1 );
//                else
//                    break;

            // 寫法2
//            for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
//                swap(arr, j, j-1);

            // 寫法3
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.InsertionSort", arr);

        return;
    }
}
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {

            // 尋找元素arr[i]合適的插入位置

            // 寫法1
//            for( int j = i ; j > 0 ; j -- )
//                if( arr[j].compareTo( arr[j-1] ) < 0 )
//                    swap( arr, j , j-1 );
//                else
//                    break;

            // 寫法2
//            for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
//                swap(arr, j, j-1);

            // 寫法3
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.InsertionSort", arr);

        return;
    }
}
public class SelectionSort{

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試SelectionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.SelectionSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
  • Shell Sort 希爾排序
public class BubbleSort {

    // 我們的算法類不允許產生任何實例
    private BubbleSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        boolean swapped = false;
        //int newn; // 理論上,可以使用newn進行優化,但實際優化效果較差

        do{
            swapped = false;
            //newn = 0;
            for( int i = 1 ; i < n ; i ++ )
                if( arr[i-1].compareTo(arr[i]) > 0 ){
                    swap( arr , i-1 , i );
                    swapped = true;

                    // 可以記錄最後一次的交換位置,在此之後的元素在下一輪掃描中均不考慮
                    // 實際優化效果較差,因爲引入了newn這個新的變量
                    //newn = n;
                }

            //n = newn;

            // 優化, 每一趟Bubble Sort都將最大的元素放在了最後的位置
            // 所以下一次排序, 最後的元素可以不再考慮
            // 理論上, newn的優化是這個優化的複雜版本,應該更有效
            // 實測, 使用這種簡單優化, 時間性能更好
            n --;
        }while(swapped);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }
}
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {

            // 尋找元素arr[i]合適的插入位置

            // 寫法1
//            for( int j = i ; j > 0 ; j -- )
//                if( arr[j].compareTo( arr[j-1] ) < 0 )
//                    swap( arr, j , j-1 );
//                else
//                    break;

            // 寫法2
//            for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
//                swap(arr, j, j-1);

            // 寫法3
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較SelectionSort, InsertionSort和BubbleSort和ShellSort四種排序算法的性能效率
    // ShellSort是這四種排序算法中性能最優的排序算法
    public static void main(String[] args) {

        int N = 20000;

        // 測試1 一般測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.InsertionSort", arr2);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.BubbleSort", arr3);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.ShellSort", arr4);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 100;
        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);
        arr4 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.SelectionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.InsertionSort", arr2);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.BubbleSort", arr3);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.ShellSort", arr4);

        return;
    }
}
public class SelectionSort{

    // 我們的算法類不允許產生任何實例
    private SelectionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            // 尋找[i, n)區間裏的最小值的索引
            int minIndex = i;
            for( int j = i + 1 ; j < n ; j ++ )
                // 使用compareTo方法比較兩個Comparable對象的大小
                if( arr[j].compareTo( arr[minIndex] ) < 0 )
                    minIndex = j;

            swap( arr , i , minIndex);
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試SelectionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch2_7.ShellSort.SelectionSort", arr);

        return;
    }
}
public class ShellSort {

    // 我們的算法類不允許產生任何實例
    private ShellSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        // 計算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
        int h = 1;
        while (h < n/3) h = 3*h + 1;

        while (h >= 1) {

            // h-sort the array
            for (int i = h; i < n; i++) {

                // 對 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
                Comparable e = arr[i];
                int j = i;
                for ( ; j >= h && e.compareTo(arr[j-h]) < 0 ; j -= h)
                    arr[j] = arr[j-h];
                arr[j] = e;
            }

            h /= 3;
        }
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
第3章 高級排序算法
3-1 歸併排序法

O(nlogn)

3-2 歸併排序法的實現
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;

        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_2.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較InsertionSort和MergeSort兩種排序算法的性能效率
    // 整體而言, MergeSort的性能最優, 對於近乎有序的數組的特殊情況, 見測試2的詳細註釋
    public static void main(String[] args) {

        int N = 50000;

        // 測試1 一般測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_2.InsertionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_2.MergeSort", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        // 對於近乎有序的數組, 數組越有序, InsertionSort的時間性能越趨近於O(n)
        // 所以可以嘗試, 當swapTimes比較大時, MergeSort更快
        // 但是當swapTimes小到一定程度, InsertionSort變得比MergeSort快
        int swapTimes = 10;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_2.InsertionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_2.MergeSort", arr2);

        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        if (l >= r)
            return;

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_2.MergeSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-3 歸併排序法的優化
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_3.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較InsertionSort和MergeSort兩種排序算法的性能效率
    // 整體而言, MergeSort的性能最優, 對於近乎有序的數組的特殊情況, 見測試2的詳細註釋
    public static void main(String[] args) {

        int N = 50000;

        // 測試1 一般測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_3.InsertionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_3.MergeSort", arr2);
        SortTestHelper.testSort("com.imooc.ch3_3.MergeSort2", arr3);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 10;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_3.InsertionSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_3.MergeSort", arr2);
        SortTestHelper.testSort("com.imooc.ch3_3.MergeSort2", arr3);

        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        if (l >= r)
            return;

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_3.MergeSort", arr);

        return;
    }
}
//優化的Merge Sort算法
public class MergeSort2{

 // 我們的算法類不允許產生任何實例
 private MergeSort2(){}

 // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
 private static void merge(Comparable[] arr, int l, int mid, int r) {

     Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

     // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
     int i = l, j = mid+1;
     for( int k = l ; k <= r; k ++ ){

         if( i > mid ){  // 如果左半部分元素已經全部處理完畢
             arr[k] = aux[j-l]; j ++;
         }
         else if( j > r ){   // 如果右半部分元素已經全部處理完畢
             arr[k] = aux[i-l]; i ++;
         }
         else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
             arr[k] = aux[i-l]; i ++;
         }
         else{  // 左半部分所指元素 >= 右半部分所指元素
             arr[k] = aux[j-l]; j ++;
         }
     }
 }

 // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
 private static void sort(Comparable[] arr, int l, int r) {

     // 優化2: 對於小規模數組, 使用插入排序
     if( r - l <= 15 ){
         InsertionSort.sort(arr, l, r);
         return;
     }

     int mid = (l+r)/2;
     sort(arr, l, mid);
     sort(arr, mid + 1, r);

     // 優化1: 對於arr[mid] <= arr[mid+1]的情況,不進行merge
     // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
     if( arr[mid].compareTo(arr[mid+1]) > 0 )
         merge(arr, l, mid, r);
 }

 public static void sort(Comparable[] arr){

     int n = arr.length;
     sort(arr, 0, n-1);
 }

 // 測試MergeSort2
 public static void main(String[] args) {

     // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
     // 可以在1秒之內輕鬆處理100萬數量級的數據
     // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
     // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
     int N = 1000000;
     Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
     SortTestHelper.testSort("com.imooc.ch3_3.MergeSort2", arr);

     return;
 }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-4 自底向上的歸併排序算法
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_4.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較Merge Sort和Merge Sort Bottom Up兩種排序算法的性能效率
    // 整體而言, 兩種算法的效率是差不多的。但是如果進行仔細測試, 自底向上的歸併排序會略勝一籌。
    // 更詳細的測試, 可以參考課程的這個問題: http://coding.imooc.com/learn/questiondetail/3208.html
    // 本章節的代碼倉也會給出更詳細的測試代碼
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_4.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_4.MergeSortBU", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 10;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_4.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_4.MergeSortBU", arr2);

        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_4.MergeSort", arr);

        return;
    }
}
public class MergeSortBU{

    // 我們的算法類不允許產生任何實例
    private MergeSortBU(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;

        // Merge Sort Bottom Up 無優化版本
//        for (int sz = 1; sz < n; sz *= 2)
//            for (int i = 0; i < n - sz; i += sz+sz)
//                // 對 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 進行歸併
//                merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1));

        // Merge Sort Bottom Up 優化
        // 對於小數組, 使用插入排序優化
        for( int i = 0 ; i < n ; i += 16 )
            InsertionSort.sort(arr, i, Math.min(i+15, n-1) );

        for( int sz = 16; sz < n ; sz += sz )
            for( int i = 0 ; i < n - sz ; i += sz+sz )
                // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
                if( arr[i+sz-1].compareTo(arr[i+sz]) > 0 )
                    merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1) );

    }

    // 測試 MergeSort BU
    public static void main(String[] args) {

        // Merge Sort BU 也是一個O(nlogn)複雜度的算法,雖然只使用兩重for循環
        // 所以,Merge Sort BU也可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易根據循環層數來判斷算法的複雜度,Merge Sort BU就是一個反例
        // 關於這部分陷阱,推薦看我的《玩轉算法面試》課程,第二章:《面試中的複雜度分析》:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_4.MergeSortBU", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-5 快速排序法

Partition

public class Main {

    // 比較Merge Sort和Quick Sort兩種排序算法的性能效率
    // 兩種排序算法雖然都是O(nlogn)級別的, 但是Quick Sort算法有常數級的優勢
    // Quick Sort要比Merge Sort快, 即使我們對Merge Sort進行了優化
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_5.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_5.QuickSort", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        // 但是對於近乎有序的數組, 我們的快速排序算法退化成了O(n^2)級別的算法
        // 思考一下爲什麼對於近乎有序的數組, 快排退化成了O(n^2)的算法? :)
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_5.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_5.QuickSort", arr2);

        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_5.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        if( l >= r )
            return;

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_5.QuickSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-6 隨機化快速排序法

快速排序最差情況,退化爲O(n^2)

public class Main {

    // 比較Merge Sort和Quick Sort兩種排序算法的性能效率
    // 兩種排序算法雖然都是O(nlogn)級別的, 但是Quick Sort算法有常數級的優勢
    // Quick Sort要比Merge Sort快, 即使我們對Merge Sort進行了優化
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_6.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_6.QuickSort", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        // 加入了隨機選擇標定點的步驟後, 我們的快速排序可以輕鬆處理近乎有序的數組
        // 但是對於近乎有序的數組, 其效率比優化後的歸併排序要低, 但完全再容忍範圍裏
        // 思考一下爲什麼對於近乎有序的數組, 快排的性能比優化後的歸併排序低? :)
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_6.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_6.QuickSort", arr2);

        System.out.println();


        // 測試3 測試存在包含大量相同元素的數組
        // 但此時, 對於含有大量相同元素的數組, 我們的快速排序算法再次退化成了O(n^2)級別的算法
        // 思考一下爲什麼在這種情況下, 快排退化成了O(n^2)的算法? :)
//        System.out.println("Test for random array, size = " + N + " , random range [0,10]");
//
//        arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
//        arr2 = Arrays.copyOf(arr1, arr1.length);
//
//        SortTestHelper.testSort("com.imooc.ch3_6.MergeSort", arr1);
//        SortTestHelper.testSort("com.imooc.ch3_6.QuickSort", arr2);


        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_6.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_6.QuickSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-7 雙路快速排序法
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_7.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較Merge Sort和Quick Sort 2 Ways兩種排序算法的性能效率
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_7.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_7.QuickSort2Ways", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        // 雙路快速排序算法也可以輕鬆處理近乎有序的數組
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_7.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_7.QuickSort2Ways", arr2);

        System.out.println();


        // 測試3 測試存在包含大量相同元素的數組
        // 使用雙快速排序後, 我們的快速排序算法可以輕鬆的處理包含大量元素的數組
        System.out.println("Test for random array, size = " + N + " , random range [0,10]");

        arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_7.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_7.QuickSort2Ways", arr2);


        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_7.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_7.QuickSort", arr);

        return;
    }
}
public class QuickSort2Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort2Ways(){}

    // 雙路快速排序的partition
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            // 注意這裏的邊界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
            // 思考一下爲什麼?
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;

            // 注意這裏的邊界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
            // 思考一下爲什麼?
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;

            // 對於上面的兩個邊界的設定, 有的同學在課程的問答區有很好的回答:)
            // 大家可以參考: http://coding.imooc.com/learn/questiondetail/4920.html

            if( i > j )
                break;

            swap( arr, i, j );
            i ++;
            j --;
        }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // 雙路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_7.QuickSort2Ways", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-8 三路快速排序法
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較Merge Sort和雙路快速排序和三路快排三種排序算法的性能效率
    // 對於包含有大量重複數據的數組, 三路快排有巨大的優勢
    // 對於一般性的隨機數組和近乎有序的數組, 三路快排的效率雖然不是最優的, 但是是在非常可以接受的範圍裏
    // 因此, 在一些語言中, 三路快排是默認的語言庫函數中使用的排序算法。比如Java:)

    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_8.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort2Ways", arr2);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort3Ways", arr3);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_8.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort2Ways", arr2);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort3Ways", arr3);

        System.out.println();


        // 測試3 測試存在包含大量相同元素的數組
        System.out.println("Test for random array, size = " + N + " , random range [0,10]");

        arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_8.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort2Ways", arr2);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort3Ways", arr3);


        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort", arr);

        return;
    }
}
public class QuickSort2Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort2Ways(){}

    // 雙路快速排序的partition
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            // 注意這裏的邊界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
            // 思考一下爲什麼?
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;

            // 注意這裏的邊界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
            // 思考一下爲什麼?
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;

            // 對於上面的兩個邊界的設定, 有的同學在課程的問答區有很好的回答:)
            // 大家可以參考: http://coding.imooc.com/learn/questiondetail/4920.html

            if( i > j )
                break;

            swap( arr, i, j );
            i ++;
            j --;
        }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        //雙路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort2Ways", arr);

        return;
    }
}
public class QuickSort3Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort3Ways(){}

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );

        Comparable v = arr[l];

        int lt = l;     // arr[l+1...lt] < v
        int gt = r + 1; // arr[gt...r] > v
        int i = l+1;    // arr[lt+1...i) == v
        while( i < gt ){
            if( arr[i].compareTo(v) < 0 ){
                swap( arr, i, lt+1);
                i ++;
                lt ++;
            }
            else if( arr[i].compareTo(v) > 0 ){
                swap( arr, i, gt-1);
                gt --;
            }
            else{ // arr[i] == v
                i ++;
            }
        }

        swap( arr, l, lt );

        sort(arr, l, lt-1);
        sort(arr, gt, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort3Ways
    public static void main(String[] args) {

        // 三路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.QuickSort3Ways", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
3-9 歸併排序和快速排序的衍生問題

分治算法
顧名思義,分而治之,就是將原問題,
分割成同等結構的子問題,
之後將子問題逐一解決後,原問題也就得到了解決。

  • 逆序對
public class InversionCount{

    // 我們的算法類不允許產生任何實例
    private InversionCount(){}

    // merge函數求出在arr[l...mid]和arr[mid+1...r]有序的基礎上, arr[l...r]的逆序數對個數
    private static long merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化逆序數對個數 res = 0
        long res = 0L;
        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l];
                j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l];
                i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) <= 0 ){  // 左半部分所指元素 <= 右半部分所指元素
                arr[k] = aux[i-l];
                i ++;
            }
            else{   // 右半部分所指元素 < 左半部分所指元素
                arr[k] = aux[j-l];
                j ++;
                // 此時, 因爲右半部分k所指的元素小
                // 這個元素和左半部分的所有未處理的元素都構成了逆序數對
                // 左半部分此時未處理的元素個數爲 mid - j + 1
                res += (long)(mid - i + 1);
            }
        }

        return res;
    }

    // 求arr[l..r]範圍的逆序數對個數
    // 思考: 歸併排序的優化可否用於求逆序數對的算法? :)
    private static long solve(Comparable[] arr, int l, int r) {

        if (l >= r)
            return 0L;

        int mid = l + (r-l)/2;
        // 求出 arr[l...mid] 範圍的逆序數
        long res1 = solve(arr, l, mid);
        // 求出 arr[mid+1...r] 範圍的逆序數
        long res2 = solve(arr, mid + 1, r);

        return res1 + res2 + merge(arr, l, mid, r);
    }

    public static long solve(Comparable[] arr){

        int n = arr.length;
        return solve(arr, 0, n-1);
    }

    // 測試 InversionCount
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1: 測試隨機數組
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        System.out.println("Test Inversion Count for Random Array, n = " + N + " :" + solve(arr) );

        // 測試2: 測試完全有序的數組
        // 結果應該爲0
        arr = SortTestHelper.generateOrderedArray(N);
        System.out.println("Test Inversion Count for Ordered Array, n = " + N + " :" + solve(arr) );

        // 測試3: 測試完全逆序的數組
        // 結果應改爲 N*(N-1)/2
        arr = SortTestHelper.generateInversedArray(N);
        System.out.println("Test Inversion Count for Inversed Array, n = " + N + " :" + solve(arr) );


        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 生成一個完全有序的數組
    public static Integer[] generateOrderedArray( int n ){

        return generateNearlyOrderedArray(n, 0);
    }

    // 生成一個完全逆序的數組
    public static Integer[] generateInversedArray( int n ){

        Integer[] arr = generateOrderedArray( n );
        for( int i = n/2 - 1 ; i >= 0 ; i -- ){
            Integer t = arr[i];
            arr[i] = arr[n-i-1];
            arr[n-i-1] = t;
        }
        return arr;
    }
}
  • 取數組中的n大的元素
public class Selection {

    // 我們的算法類不允許產生任何實例
    private Selection(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    // partition 過程, 和快排的partition一樣
    // 思考: 雙路快排和三路快排的思想能不能用在selection算法中? :)
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 求出nums[l...r]範圍裏第k小的數
    private static Comparable solve(Comparable[] nums, int l, int r, int k){

        if( l == r )
            return nums[l];

        // partition之後, nums[p]的正確位置就在索引p上
        int p = partition(nums, l, r);

        if( k == p )    // 如果 k == p, 直接返回nums[p]
            return nums[p];
        else if( k < p )    // 如果 k < p, 只需要在nums[l...p-1]中找第k小元素即可
            return solve( nums, l, p-1, k);
        else // 如果 k > p, 則需要在nums[p+1...r]中找第k小元素
            return solve( nums, p+1, r, k );
    }

    // 尋找nums數組中第k小的元素
    public static Comparable solve(Comparable nums[], int n, int k) {

        assert k >= 0 && k < n;
        return solve(nums, 0, n - 1, k);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 Selection
    public static void main(String[] args) {

        // 生成一個大小爲n, 包含0...n-1這n個元素的隨機數組arr
        int N = 10000;
        Integer[] arr = TestHelper.generateOrderedArray(N);
        TestHelper.shuffleArray(arr);

        // 驗證selection算法, 對arr數組求第i小元素, 應該爲i
        for( int i = 0 ; i < N ; i ++ ){
//            assert solve(arr, N, i) == i;
            System.out.println("test " + i + " complete.");
        }

    }
}
public class TestHelper {

    // SortTestHelper不允許產生任何實例
    private TestHelper(){}

    // 生成一個完全有序的數組
    public static Integer[] generateOrderedArray( int n ){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        return arr;
    }

    // 將數組arr隨機化
    public static void shuffleArray( Object[] arr ){

        int n = arr.length;
        for( int i = 0 ; i < n ; i ++ ){
            int j = (int)(Math.random() * (n-i)) + i;

            Object t = arr[i];
            arr[i] = arr[j];
            arr[j] = t;
        }
    }
}
  • 歸併排序的另外一個優化,在merge外申請aux空間
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra1.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較Merge Sort和Merge Sort 2的性能效率
    // Merge Sort 2 只開闢了一次輔助空間, 之後將這個輔助空間以參數形式傳遞給完成歸併排序的其他子函數
    // 可以看出 Merge Sort 2的性能優於 Merge Sort
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort2", arr2);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort2", arr2);

        System.out.println();


        return;
    }
}
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort", arr);

        return;
    }
}
// 在 mergeSort2中, 我們一次性申請aux空間,
// 並將這個輔助空間以參數形式傳遞給完成歸併排序的各個子函數
public class MergeSort2{

    // 我們的算法類不允許產生任何實例
    private MergeSort2(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, aux, l, mid);
        sort(arr, aux, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, aux, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        Comparable[] aux = new Comparable[n];
        sort(arr, aux, 0, n-1);
    }

    // 測試 MergeSort2
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra1.MergeSort2", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}
  • 自頂向下和自底向上的歸併排序的比較
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra2.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較 Merge Sort 和 Merge Sort BU 的性能效率
    // 使用更科學的比較方式, 每次比較都運行多次測試用例, 取平均值
    // 同時比較了優化和不優化兩種情況
    // 總體來說, Merge Sort BU 比 Merge Sort 快一些。但優化後, 二者的性能差距不明顯
    // 對於這個問題更詳細分析, 可以看課程的問答:
    // http://coding.imooc.com/learn/questiondetail/3208.html
    public static void main(String[] args) {

        // 測試T個測試用例, 每個測試用例的數組大小爲n
        int T = 100;
        int N = 1000000;

        // 比較 Merge Sort 和 Merge Sort BU 兩種算法在不優化的情況下的性能效率
        long time1 = 0, time2 = 0;
        for( int i = 0 ; i < T ; i ++ ) {
            Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
            Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

            time1 += SortTestHelper.testSort2("com.imooc.ch3_8.extra2.MergeSort", arr1);
            time2 += SortTestHelper.testSort2("com.imooc.ch3_8.extra2.MergeSortBU", arr2);
        }
        System.out.println("Without Any Optimization:");
        System.out.println("Merge Sort    Average Run Time: " + time1/T + " ms");
        System.out.println("Merge Sort BU Average Run Time: " + time2/T + " ms");
        System.out.println();


        // 比較 Merge Sort 和 Merge Sort BU 兩種算法在含優化的情況下的性能效率
        time1 = 0; time2 = 0;
        for( int i = 0 ; i < T ; i ++ ) {
            Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
            Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

            time1 += SortTestHelper.testSort2("com.imooc.ch3_8.extra2.MergeSort2", arr1);
            time2 += SortTestHelper.testSort2("com.imooc.ch3_8.extra2.MergeSortBU2", arr2);
        }
        System.out.println("With Optimization:");
        System.out.println("Merge Sort    Average Run Time: " + time1/T + " ms");
        System.out.println("Merge Sort BU Average Run Time: " + time2/T + " ms");


        return;
    }
}
// 自頂向下的歸併排序, 無優化
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r) {

        if( l >= r )
            return;

        int mid = (l+r)/2;
        sort(arr, l, mid);
        sort(arr, mid + 1, r);
        merge(arr, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    // 測試MergeSort
    public static void main(String[] args) {

        // Merge Sort是我們學習的第一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易嘗試使用SelectionSort, InsertionSort或者BubbleSort處理100萬級的數據
        // 否則,你就見識了O(n^2)的算法和O(nlogn)算法的本質差異:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra2.MergeSort", arr);

        return;
    }
}
// 自頂向下的歸併排序, 含優化
public class MergeSort2{

    // 我們的算法類不允許產生任何實例
    private MergeSort2(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, aux, l, mid);
        sort(arr, aux, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, aux, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        Comparable[] aux = new Comparable[n];
        sort(arr, aux, 0, n-1);
    }

    // 測試 MergeSort2
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra2.MergeSort2", arr);

        return;
    }
}
public class MergeSortBU{

    // 我們的算法類不允許產生任何實例
    private MergeSortBU(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    private static void merge(Comparable[] arr, int l, int mid, int r) {

        Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j-l]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i-l]; i ++;
            }
            else if( aux[i-l].compareTo(aux[j-l]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i-l]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j-l]; j ++;
            }
        }
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;

        for (int sz = 1; sz < n; sz *= 2)
            for (int i = 0; i < n - sz; i += sz+sz)
                // 對 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 進行歸併
                merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1));
    }

    // 測試 MergeSort BU
    public static void main(String[] args) {

        // Merge Sort BU 也是一個O(nlogn)複雜度的算法,雖然只使用兩重for循環
        // 所以,Merge Sort BU 也可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易根據循環層數來判斷算法的複雜度,Merge Sort BU 就是一個反例
        // 關於這部分陷阱,推薦看我的《玩轉算法面試》課程,第二章:《面試中的複雜度分析》:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra2.MergeSortBU", arr);

        return;
    }
}
public class MergeSortBU2{

    // 我們的算法類不允許產生任何實例
    private MergeSortBU2(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;

        // 對於小數組, 使用插入排序優化
        for( int i = 0 ; i < n ; i += 16 )
            InsertionSort.sort(arr, i, Math.min(i+15, n-1) );

        Comparable[] aux = new Comparable[n];
        for( int sz = 16; sz < n ; sz += sz )
            for( int i = 0 ; i < n - sz ; i += sz+sz )
                // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
                if( arr[i+sz-1].compareTo(arr[i+sz]) > 0 )
                    merge(arr, aux, i, i+sz-1, Math.min(i+sz+sz-1,n-1) );

    }

    // 測試 MergeSort BU 2
    public static void main(String[] args) {

        // Merge Sort BU 2 也是一個O(nlogn)複雜度的算法,雖然只使用兩重for循環
        // 所以,Merge Sort BU 2 也可以在1秒之內輕鬆處理100萬數量級的數據
        // 注意:不要輕易根據循環層數來判斷算法的複雜度,Merge Sort BU 2 就是一個反例
        // 關於這部分陷阱,推薦看我的《玩轉算法面試》課程,第二章:《面試中的複雜度分析》:)
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra2.MergeSortBU2", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
  • ShellSort, MergeSort 和 QuickSort 的比較
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra3.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較 Shell Sort 和 Merge Sort 和 三種 Quick Sort 的性能效率
    // 使用更科學的比較方式, 每次比較都運行多次測試用例, 取平均值
    // 可以看出, Shell Sort雖然慢於高級的排序方式, 但仍然是非常有競爭力的一種排序算法
    // 其所花費的時間完全在可以容忍的範圍內, 遠不像O(n^2)的排序算法, 在數據量較大的時候無法忍受
    // 同時, Shell Sort實現簡單, 只使用循環的方式解決排序問題, 不需要實現遞歸, 不佔用系統佔空間, 也不依賴隨機數
    // 所以, 如果算法實現所使用的環境不利於實現複雜的排序算法, 或者在項目工程的測試階段, 完全可以暫時使用Shell Sort來進行排序任務:)
    public static void main(String[] args) {

        // 測試T個測試用例, 每個測試用例的數組大小爲n
        int T = 100;
        int N = 1000000;

        // 比較 Shell Sort 和 Merge Sort 和 三種 Quick Sort 的性能效率
        long time1 = 0, time2 = 0, time3 = 0, time4 = 0, time5 = 0;
        for( int i = 0 ; i < T ; i ++ ) {
            Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
            Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
            Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
            Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);
            Integer[] arr5 = Arrays.copyOf(arr1, arr1.length);

            time1 += SortTestHelper.testSort2("com.imooc.ch3_8.extra3.ShellSort", arr1);
            time2 += SortTestHelper.testSort2("com.imooc.ch3_8.extra3.MergeSort", arr2);
            time3 += SortTestHelper.testSort2("com.imooc.ch3_8.extra3.QuickSort", arr3);
            time4 += SortTestHelper.testSort2("com.imooc.ch3_8.extra3.QuickSort2Ways", arr4);
            time5 += SortTestHelper.testSort2("com.imooc.ch3_8.extra3.QuickSort3Ways", arr5);
        }
        System.out.println("Sorting " + N + " elements " + T + " times. Calculate the average run time.");
        System.out.println("Shell Sort        Average Run Time: " + time1/T + " ms");
        System.out.println("Merge Sort        Average Run Time: " + time2/T + " ms");
        System.out.println("Quick Sort        Average Run Time: " + time3/T + " ms");
        System.out.println("Quick Sort 2 Ways Average Run Time: " + time4/T + " ms");
        System.out.println("Quick Sort 3 Ways Average Run Time: " + time5/T + " ms");


        return;
    }
}
// 自頂向下的歸併排序, 含優化
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, aux, l, mid);
        sort(arr, aux, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, aux, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        Comparable[] aux = new Comparable[n];
        sort(arr, aux, 0, n-1);
    }

    // 測試 MergeSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra3.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra3.QuickSort", arr);

        return;
    }
}
public class QuickSort2Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort2Ways(){}

    // 雙路快速排序的partition
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            // 注意這裏的邊界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
            // 思考一下爲什麼?
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;

            // 注意這裏的邊界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
            // 思考一下爲什麼?
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;

            // 對於上面的兩個邊界的設定, 有的同學在課程的問答區有很好的回答:)
            // 大家可以參考: http://coding.imooc.com/learn/questiondetail/4920.html

            if( i > j )
                break;

            swap( arr, i, j );
            i ++;
            j --;
        }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 Quick Sort 2 Ways
    public static void main(String[] args) {

        // 雙路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra3.QuickSort2Ways", arr);

        return;
    }
}
public class QuickSort3Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort3Ways(){}

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );

        Comparable v = arr[l];

        int lt = l;     // arr[l+1...lt] < v
        int gt = r + 1; // arr[gt...r] > v
        int i = l+1;    // arr[lt+1...i) == v
        while( i < gt ){
            if( arr[i].compareTo(v) < 0 ){
                swap( arr, i, lt+1);
                i ++;
                lt ++;
            }
            else if( arr[i].compareTo(v) > 0 ){
                swap( arr, i, gt-1);
                gt --;
            }
            else{ // arr[i] == v
                i ++;
            }
        }

        swap( arr, l, lt );

        sort(arr, l, lt-1);
        sort(arr, gt, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort3Ways
    public static void main(String[] args) {

        // 三路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch3_8.extra3.QuickSort3Ways", arr);

        return;
    }
}
public class ShellSort {

    // 我們的算法類不允許產生任何實例
    private ShellSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        // 計算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
        int h = 1;
        while (h < n/3) h = 3*h + 1;

        while (h >= 1) {

            // h-sort the array
            for (int i = h; i < n; i++) {

                // 對 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
                Comparable e = arr[i];
                int j = i;
                for ( ; j >= h && e.compareTo(arr[j-h]) < 0 ; j -= h)
                    arr[j] = arr[j-h];
                arr[j] = e;
            }

            h /= 3;
        }
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
第4章 堆和堆排序
4-1 爲什麼使用堆

普通隊列:先進先;後進後出
優先隊列:出隊順序和入隊順序無關;和優先級相關

任務處理中心(動態)
先排序在依次處理不可行

在1000000個元素中選出前100名?
在N個元素中選出前M個元素

排序?NlogN
使用優先隊列?NlogM

入隊
出隊(取出優先級最高的元素)

入隊 出隊
普通數組 O(1)
順序數組 O(n)
O(lgn)

對於總共有N個請求:
使用普通數組或者順序數組,最差情況:O(n^2)
使用堆:O(nlgn)

4-2 堆的基本存儲

二叉堆 Binary Heap

二叉堆 是一棵 完全二叉樹

堆中某個節點的值總是不大於其父節點的值;
堆總是一棵完全二叉樹。(最大堆)

用數組存儲二叉堆

public class MaxHeap<Item> {

    private Item[] data;
    private int count;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Object[capacity+1];
        count = 0;
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        System.out.println(maxHeap.size());
    }
}
4-3 Shift Up
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        int N = 50; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );
        System.out.println(maxHeap.size());

    }
}
// 我們的PrintableMaxHeap只能處理整數信息,所以繼承的是MaxHeap<Comparable<Integer>>
public class PrintableMaxHeap extends MaxHeap<Comparable<Integer>>{

    public PrintableMaxHeap(int capacity){
        super(capacity);
    }

    // 以樹狀打印整個堆結構
    public void treePrint(){

        if( size() >= 100 ){
            System.out.println("This print function can only work for less than 100 integer");
            return;
        }

        System.out.println("The max heap size is: " + size());
        System.out.println("Data in the max heap: ");
        for( int i = 1 ; i <= size() ; i ++ ){
            // 我們的print函數要求堆中的所有整數在[0, 100)的範圍內
            assert (Integer)data[i] >= 0 && (Integer)data[i] < 100;
            System.out.print(data[i] + " ");
        }
        System.out.println();
        System.out.println();

        int n = size();
        int maxLevel = 0;
        int numberPerLevel = 1;
        while( n > 0 ){
            maxLevel += 1;
            n -= numberPerLevel;
            numberPerLevel *= 2;
        }

        int maxLevelNumber = (int)Math.pow(2, maxLevel-1);
        int curTreeMaxLevelNumber = maxLevelNumber;
        int index = 1;
        for( int level = 0 ; level < maxLevel ; level ++ ){

            String line1 = new String(new char[maxLevelNumber*3-1]).replace('\0', ' ');

            int curLevelNumber = Math.min(count-(int)Math.pow(2,level)+1,(int)Math.pow(2,level));
            boolean isLeft = true;
            for( int indexCurLevel = 0 ; indexCurLevel < curLevelNumber ; index ++ , indexCurLevel ++ ){
                line1 = putNumberInLine( (Integer)data[index] , line1 , indexCurLevel , curTreeMaxLevelNumber*3-1 , isLeft );
                isLeft = !isLeft;
            }
            System.out.println(line1);

            if( level == maxLevel - 1 )
                break;

            String line2 = new String(new char[maxLevelNumber*3-1]).replace('\0', ' ');
            for( int indexCurLevel = 0 ; indexCurLevel < curLevelNumber ; indexCurLevel ++ )
                line2 = putBranchInLine( line2 , indexCurLevel , curTreeMaxLevelNumber*3-1 );
            System.out.println(line2);

            curTreeMaxLevelNumber /= 2;
        }
    }

    private String putNumberInLine( Integer num, String line, int indexCurLevel, int curTreeWidth, boolean isLeft){

        int subTreeWidth = (curTreeWidth - 1) / 2;
        int offset = indexCurLevel * (curTreeWidth+1) + subTreeWidth;
        assert offset + 1 < line.length();
        if( num >= 10 )
            line = line.substring(0, offset+0) + num.toString()
                    + line.substring(offset+2);
        else{
            if( isLeft)
                line = line.substring(0, offset+0) + num.toString()
                        + line.substring(offset+1);
            else
                line = line.substring(0, offset+1) + num.toString()
                        + line.substring(offset+2);
        }
        return line;
    }

    private String putBranchInLine( String line, int indexCurLevel, int curTreeWidth){

        int subTreeWidth = (curTreeWidth - 1) / 2;
        int subSubTreeWidth = (subTreeWidth - 1) / 2;
        int offsetLeft = indexCurLevel * (curTreeWidth+1) + subSubTreeWidth;
        assert offsetLeft + 1 < line.length();
        int offsetRight = indexCurLevel * (curTreeWidth+1) + subTreeWidth + 1 + subSubTreeWidth;
        assert offsetRight < line.length();

        line = line.substring(0, offsetLeft+1) + "/" + line.substring(offsetLeft+2);
        line = line.substring(0, offsetRight) + "\\" + line.substring(offsetRight+1);

        return line;
    }

    // 測試 PrintableMaxHeap
    public static void main(String[] args) {

        PrintableMaxHeap maxHeap = new PrintableMaxHeap(100);
        int N = 31; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );
        maxHeap.treePrint();

    }
}
4-4 Shift Down
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大堆中的堆頂元素
    public Item getMax(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( data[k].compareTo(data[j]) >= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將maxheap中的數據逐漸使用extractMax取出來
        // 取出來的順序應該是按照從大到小的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = maxHeap.extractMax();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從大到小排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] >= arr[i];
    }
}
4-5 基礎堆排序和Heapify
public class HeapSort1 {

    // 我們的算法類不允許產生任何實例
    private HeapSort1(){}

    // 對整個arr數組使用HeapSort1排序
    // HeapSort1, 將所有的元素依次添加到堆中, 在將所有元素從堆中依次取出來, 即完成了排序
    // 無論是創建堆的過程, 還是從堆中依次取出元素的過程, 時間複雜度均爲O(nlogn)
    // 整個堆排序的整體時間複雜度爲O(nlogn)
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxHeap.insert(arr[i]);

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 HeapSort1
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr);

        return;
    }
}
public class HeapSort2 {

    // 我們的算法類不允許產生任何實例
    private HeapSort2(){}

    // 對整個arr數組使用HeapSort2排序
    // HeapSort2, 藉助我們的heapify過程創建堆
    // 此時, 創建堆的過程時間複雜度爲O(n), 將所有元素依次從堆中取出來, 實踐複雜度爲O(nlogn)
    // 堆排序的總體時間複雜度依然是O(nlogn), 但是比HeapSort1性能更優, 因爲創建堆的性能更優
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(arr);
        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 HeapSort2
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr);

        return;
    }
}
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.InsertionSort", arr);

        return;
    }
}
public class Main {

    /// 比較 Merge Sort, 三種 Quick Sort 和本節介紹的兩種 Heap Sort 的性能效率
    // 注意, 這幾種排序算法都是 O(nlogn) 級別的排序算法
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr5 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr6 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);
        arr4 = Arrays.copyOf(arr1, arr1.length);
        arr5 = Arrays.copyOf(arr1, arr1.length);
        arr6 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);

        System.out.println();


        // 測試3 測試存在包含大量相同元素的數組
        System.out.println("Test for random array, size = " + N + " , random range [0,10]");

        arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);
        arr4 = Arrays.copyOf(arr1, arr1.length);
        arr5 = Arrays.copyOf(arr1, arr1.length);
        arr6 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr1);
        // 這種情況下, 普通的QuickSort退化爲O(n^2)的算法, 不做測試
        //SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_5.HeapSort2", arr6);


        return;
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最大堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MaxHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大堆中的堆頂元素
    public Item getMax(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( data[k].compareTo(data[j]) >= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將maxheap中的數據逐漸使用extractMax取出來
        // 取出來的順序應該是按照從大到小的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = maxHeap.extractMax();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從大到小排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] >= arr[i];
    }
}
// 自頂向下的歸併排序, 含優化
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, aux, l, mid);
        sort(arr, aux, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, aux, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        Comparable[] aux = new Comparable[n];
        sort(arr, aux, 0, n-1);
    }

    // 測試 MergeSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort", arr);

        return;
    }
}
public class QuickSort2Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort2Ways(){}

    // 雙路快速排序的partition
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            // 注意這裏的邊界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
            // 思考一下爲什麼?
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;

            // 注意這裏的邊界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
            // 思考一下爲什麼?
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;

            // 對於上面的兩個邊界的設定, 有的同學在課程的問答區有很好的回答:)
            // 大家可以參考: http://coding.imooc.com/learn/questiondetail/4920.html

            if( i > j )
                break;

            swap( arr, i, j );
            i ++;
            j --;
        }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 Quick Sort 2 Ways
    public static void main(String[] args) {

        // 雙路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort2Ways", arr);

        return;
    }
}
public class QuickSort3Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort3Ways(){}

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );

        Comparable v = arr[l];

        int lt = l;     // arr[l+1...lt] < v
        int gt = r + 1; // arr[gt...r] > v
        int i = l+1;    // arr[lt+1...i) == v
        while( i < gt ){
            if( arr[i].compareTo(v) < 0 ){
                swap( arr, i, lt+1);
                i ++;
                lt ++;
            }
            else if( arr[i].compareTo(v) > 0 ){
                swap( arr, i, gt-1);
                gt --;
            }
            else{ // arr[i] == v
                i ++;
            }
        }

        swap( arr, l, lt );

        sort(arr, l, lt-1);
        sort(arr, gt, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort3Ways
    public static void main(String[] args) {

        // 三路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_5.QuickSort3Ways", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
4-6 優化的堆排序
// 不使用一個額外的最大堆, 直接在原數組上進行原地的堆排序
public class HeapSort {

    // 我們的算法類不允許產生任何實例
    private HeapSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        // 注意,此時我們的堆是從0開始索引的
        // 從(最後一個元素的索引-1)/2開始
        // 最後一個元素的索引 = n-1
        for( int i = (n-1-1)/2 ; i >= 0 ; i -- )
            shiftDown2(arr, n, i);

        for( int i = n-1; i > 0 ; i-- ){
            swap( arr, 0, i);
            shiftDown2(arr, i, 0);
        }
    }

    // 交換堆中索引爲i和j的兩個元素
    private static void swap(Object[] arr, int i, int j){
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 原始的shiftDown過程
    private static void shiftDown(Comparable[] arr, int n, int k){

        while( 2*k+1 < n ){
            int j = 2*k+1;
            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
                j += 1;

            if( arr[k].compareTo(arr[j]) >= 0 )break;

            swap( arr, k, j);
            k = j;
        }
    }

    // 優化的shiftDown過程, 使用賦值的方式取代不斷的swap,
    // 該優化思想和我們之前對插入排序進行優化的思路是一致的
    private static void shiftDown2(Comparable[] arr, int n, int k){

        Comparable e = arr[k];
        while( 2*k+1 < n ){
            int j = 2*k+1;
            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )
                j += 1;

            if( e.compareTo(arr[j]) >= 0 )
                break;

            arr[k] = arr[j];
            k = j;
        }

        arr[k] = e;
    }

    // 測試 HeapSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr);

        return;
    }
}
public class HeapSort1 {

    // 我們的算法類不允許產生任何實例
    private HeapSort1(){}

    // 對整個arr數組使用HeapSort1排序
    // HeapSort1, 將所有的元素依次添加到堆中, 在將所有元素從堆中依次取出來, 即完成了排序
    // 無論是創建堆的過程, 還是從堆中依次取出元素的過程, 時間複雜度均爲O(nlogn)
    // 整個堆排序的整體時間複雜度爲O(nlogn)
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxHeap.insert(arr[i]);

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 HeapSort1
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr);

        return;
    }
}
public class HeapSort2 {

    // 我們的算法類不允許產生任何實例
    private HeapSort2(){}

    // 對整個arr數組使用HeapSort2排序
    // HeapSort2, 藉助我們的heapify過程創建堆
    // 此時, 創建堆的過程時間複雜度爲O(n), 將所有元素依次從堆中取出來, 實踐複雜度爲O(nlogn)
    // 堆排序的總體時間複雜度依然是O(nlogn), 但是比HeapSort1性能更優, 因爲創建堆的性能更優
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(arr);
        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 HeapSort2
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr);

        return;
    }
}
public class InsertionSort{

    // 我們的算法類不允許產生任何實例
    private InsertionSort(){}

    // 對整個arr數組使用InsertionSort排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        for (int i = 0; i < n; i++) {
            Comparable e = arr[i];
            int j = i;
            for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    // 對arr[l...r]的區間使用InsertionSort排序
    public static void sort(Comparable[] arr, int l, int r){

        assert l >= 0 && l <= r && r < arr.length;

        for( int i = l + 1 ; i <= r ; i ++ ){
            Comparable e = arr[i];
            int j = i;
            for( ; j > l && arr[j-1].compareTo(e) > 0 ; j--)
                arr[j] = arr[j-1];
            arr[j] = e;
        }
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試InsertionSort
    public static void main(String[] args) {

        int N = 10000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.InsertionSort", arr);

        return;
    }
}
public class Main {

    // 比較 Merge Sort, 三種 Quick Sort 和三種 Heap Sort 的性能效率
    // 注意, 這幾種排序算法都是 O(nlogn) 級別的排序算法
    public static void main(String[] args) {

        int N = 1000000;

        // 測試1 一般性測試
        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr3 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr4 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr5 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr6 = Arrays.copyOf(arr1, arr1.length);
        Integer[] arr7 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);

        System.out.println();


        // 測試2 測試近乎有序的數組
        int swapTimes = 100;
        assert swapTimes >= 0;

        System.out.println("Test for nearly ordered array, size = " + N + " , swap time = " + swapTimes);

        arr1 = SortTestHelper.generateNearlyOrderedArray(N, swapTimes);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);
        arr4 = Arrays.copyOf(arr1, arr1.length);
        arr5 = Arrays.copyOf(arr1, arr1.length);
        arr6 = Arrays.copyOf(arr1, arr1.length);
        arr7 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);

        System.out.println();


        // 測試3 測試存在包含大量相同元素的數組
        System.out.println("Test for random array, size = " + N + " , random range [0,10]");

        arr1 = SortTestHelper.generateRandomArray(N, 0, 10);
        arr2 = Arrays.copyOf(arr1, arr1.length);
        arr3 = Arrays.copyOf(arr1, arr1.length);
        arr4 = Arrays.copyOf(arr1, arr1.length);
        arr5 = Arrays.copyOf(arr1, arr1.length);
        arr6 = Arrays.copyOf(arr1, arr1.length);
        arr7 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr1);
        // 這種情況下, 普通的QuickSort退化爲O(n^2)的算法, 不做測試
        //SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr2);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr3);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr4);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort1", arr5);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort2", arr6);
        SortTestHelper.testSort("com.imooc.ch4_6.HeapSort", arr7);


        return;
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最大堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MaxHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大堆中的堆頂元素
    public Item getMax(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( data[k].compareTo(data[j]) >= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將maxheap中的數據逐漸使用extractMax取出來
        // 取出來的順序應該是按照從大到小的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = maxHeap.extractMax();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從大到小排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] >= arr[i];
    }
}
// 自頂向下的歸併排序, 含優化
public class MergeSort{

    // 我們的算法類不允許產生任何實例
    private MergeSort(){}

    // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸併
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void merge(Comparable[] arr, Comparable[] aux, int l, int mid, int r) {

        System.arraycopy(arr, l, aux, l, r-l+1);

        // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
        int i = l, j = mid+1;
        for( int k = l ; k <= r; k ++ ){

            if( i > mid ){  // 如果左半部分元素已經全部處理完畢
                arr[k] = aux[j]; j ++;
            }
            else if( j > r ){   // 如果右半部分元素已經全部處理完畢
                arr[k] = aux[i]; i ++;
            }
            else if( aux[i].compareTo(aux[j]) < 0 ){  // 左半部分所指元素 < 右半部分所指元素
                arr[k] = aux[i]; i ++;
            }
            else{  // 左半部分所指元素 >= 右半部分所指元素
                arr[k] = aux[j]; j ++;
            }
        }
    }

    // 遞歸使用歸併排序,對arr[l...r]的範圍進行排序
    // 其中aux爲完成merge過程所需要的輔助空間
    private static void sort(Comparable[] arr, Comparable[] aux, int l, int r) {

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int mid = (l+r)/2;
        sort(arr, aux, l, mid);
        sort(arr, aux, mid + 1, r);
        // 對於arr[mid] <= arr[mid+1]的情況,不進行merge
        // 對於近乎有序的數組非常有效,但是對於一般情況,有一定的性能損失
        if( arr[mid].compareTo(arr[mid+1]) > 0 )
            merge(arr, aux, l, mid, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        Comparable[] aux = new Comparable[n];
        sort(arr, aux, 0, n-1);
    }

    // 測試 MergeSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.MergeSort", arr);

        return;
    }
}
public class QuickSort {

    // 我們的算法類不允許產生任何實例
    private QuickSort(){}

    // 對arr[l...r]部分進行partition操作
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
        for( int i = l + 1 ; i <= r ; i ++ )
            if( arr[i].compareTo(v) < 0 ){
                j ++;
                swap(arr, j, i);
            }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort
    public static void main(String[] args) {

        // Quick Sort也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort", arr);

        return;
    }
}
public class QuickSort2Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort2Ways(){}

    // 雙路快速排序的partition
    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
    private static int partition(Comparable[] arr, int l, int r){

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l , (int)(Math.random()*(r-l+1))+l );

        Comparable v = arr[l];

        // arr[l+1...i) <= v; arr(j...r] >= v
        int i = l+1, j = r;
        while( true ){
            // 注意這裏的邊界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0
            // 思考一下爲什麼?
            while( i <= r && arr[i].compareTo(v) < 0 )
                i ++;

            // 注意這裏的邊界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0
            // 思考一下爲什麼?
            while( j >= l+1 && arr[j].compareTo(v) > 0 )
                j --;

            // 對於上面的兩個邊界的設定, 有的同學在課程的問答區有很好的回答:)
            // 大家可以參考: http://coding.imooc.com/learn/questiondetail/4920.html

            if( i > j )
                break;

            swap( arr, i, j );
            i ++;
            j --;
        }

        swap(arr, l, j);

        return j;
    }

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        int p = partition(arr, l, r);
        sort(arr, l, p-1 );
        sort(arr, p+1, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 Quick Sort 2 Ways
    public static void main(String[] args) {

        // 雙路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort2Ways", arr);

        return;
    }
}
public class QuickSort3Ways {

    // 我們的算法類不允許產生任何實例
    private QuickSort3Ways(){}

    // 遞歸使用快速排序,對arr[l...r]的範圍進行排序
    private static void sort(Comparable[] arr, int l, int r){

        // 對於小規模數組, 使用插入排序
        if( r - l <= 15 ){
            InsertionSort.sort(arr, l, r);
            return;
        }

        // 隨機在arr[l...r]的範圍中, 選擇一個數值作爲標定點pivot
        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );

        Comparable v = arr[l];

        int lt = l;     // arr[l+1...lt] < v
        int gt = r + 1; // arr[gt...r] > v
        int i = l+1;    // arr[lt+1...i) == v
        while( i < gt ){
            if( arr[i].compareTo(v) < 0 ){
                swap( arr, i, lt+1);
                i ++;
                lt ++;
            }
            else if( arr[i].compareTo(v) > 0 ){
                swap( arr, i, gt-1);
                gt --;
            }
            else{ // arr[i] == v
                i ++;
            }
        }

        swap( arr, l, lt );

        sort(arr, l, lt-1);
        sort(arr, gt, r);
    }

    public static void sort(Comparable[] arr){

        int n = arr.length;
        sort(arr, 0, n-1);
    }

    private static void swap(Object[] arr, int i, int j) {
        Object t = arr[i];
        arr[i] = arr[j];
        arr[j] = t;
    }

    // 測試 QuickSort3Ways
    public static void main(String[] args) {

        // 三路快速排序算法也是一個O(nlogn)複雜度的算法
        // 可以在1秒之內輕鬆處理100萬數量級的數據
        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_6.QuickSort3Ways", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
4-7 排序算法總結
平均時間複雜度 原地排序 額外空間 穩定排序
插入排序
Insertion Sort
O(n^2) O(1)
歸併排序
Merge Sort
O(nlogn) x O(n)
快速排序
Quick Sort
O(nlogn) O(logn)
堆排序
Heap Sort
O(nlogn) O(1)

穩定排序:對於相等元素,在排序後,原來靠前的元素依然靠前。
相等元素的相對位置沒有發生改變。

可以通過自定義比較函數,讓排序算法不存在穩定性的問題。

4-8 索引堆
// 使用最大索引堆進行堆排序, 來驗證我們的最大索引堆的正確性
// 最大索引堆的主要作用不是用於排序, 我們在這裏使用排序只是爲了驗證我們的最大索引堆實現的正確性
// 在後續的圖論中, 無論是最小生成樹算法, 還是最短路徑算法, 我們都需要使用索引堆進行優化:)
public class IndexHeapSort {

    // 我們的算法類不允許產生任何實例
    private IndexHeapSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        IndexMaxHeap<Comparable> indexMaxHeap = new IndexMaxHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            indexMaxHeap.insert( i , arr[i] );

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = indexMaxHeap.extractMax();
    }

    // 測試 Index Heap Sort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_8.IndexHeapSort", arr);

        return;
    }
}
// 最大索引堆
public class IndexMaxHeap<Item extends Comparable> {

    protected Item[] data;      // 最大索引堆中的數據
    protected int[] indexes;    // 最大索引堆中的索引
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最大索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        count ++;

        shiftUp(count);
    }

    // 從最大索引堆中取出堆頂元素, 即索引堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最大索引堆中取出堆頂元素的索引
    public int extractMaxIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大索引堆中的堆頂元素
    public Item getMax(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最大索引堆中的堆頂元素的索引
    public int getMaxIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 獲取最大索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert i + 1 >= 1 && i + 1 <= capacity;
        return data[i+1];
    }

    // 將最大索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        i += 1;
        data[i] = newItem;

        // 找到indexes[j] = i, j表示data[i]在堆中的位置
        // 之後shiftUp(j), 再shiftDown(j)
        for( int j = 1 ; j <= count ; j ++ )
            if( indexes[j] == i ){
                shiftUp(j);
                shiftDown(j);
                return;
            }
    }

    // 交換索引堆中的索引i和j
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;
    }

    //********************
    //* 最大索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試索引堆中的索引數組index
    // 注意:這個測試在向堆中插入元素以後, 不進行extract操作有效
    public boolean testIndexes(){

        int[] copyIndexes = new int[count+1];

        for( int i = 0 ; i <= count ; i ++ )
            copyIndexes[i] = indexes[i];

        copyIndexes[0] = 0;
        Arrays.sort(copyIndexes);

        // 在對索引堆中的索引進行排序後, 應該正好是1...count這count個索引
        boolean res = true;
        for( int i = 1 ; i <= count ; i ++ )
            if( copyIndexes[i-1] + 1 != copyIndexes[i] ){
                res = false;
                break;
            }

        if( !res ){
            System.out.println("Error!");
            return false;
        }

        return true;
    }

    // 測試 IndexMaxHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMaxHeap.insert( i , (int)(Math.random()*N) );
        assert indexMaxHeap.testIndexes();
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
4-9 索引堆的優化
// 使用最大索引堆進行堆排序, 來驗證我們的最大索引堆的正確性
// 最大索引堆的主要作用不是用於排序, 我們在這裏使用排序只是爲了驗證我們的最大索引堆實現的正確性
// 在後續的圖論中, 無論是最小生成樹算法, 還是最短路徑算法, 我們都需要使用索引堆進行優化:)
public class IndexHeapSort {

    // 我們的算法類不允許產生任何實例
    private IndexHeapSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        IndexMaxHeap<Comparable> indexMaxHeap = new IndexMaxHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            indexMaxHeap.insert( i , arr[i] );

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = indexMaxHeap.extractMax();
    }

    // 測試 Index Heap Sort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_9.IndexHeapSort", arr);

        return;
    }
}
// 最大索引堆
public class IndexMaxHeap<Item extends Comparable> {

    protected Item[] data;      // 最大索引堆中的數據
    protected int[] indexes;    // 最大索引堆中的索引, indexes[x] = i 表示索引i在x的位置
    protected int[] reverse;    // 最大索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;

        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最大索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        // 再插入一個新元素前,還需要保證索引i所在的位置是沒有元素的。
        assert !contain(i);

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        reverse[i] = count + 1;
        count ++;

        shiftUp(count);
    }

    // 從最大索引堆中取出堆頂元素, 即索引堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最大索引堆中取出堆頂元素的索引
    public int extractMaxIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大索引堆中的堆頂元素
    public Item getMax(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最大索引堆中的堆頂元素的索引
    public int getMaxIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 看索引i所在的位置是否存在元素
    boolean contain( int i ){
        assert  i + 1 >= 1 && i + 1 <= capacity;
        return reverse[i+1] != 0;
    }

    // 獲取最大索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert contain(i);
        return data[i+1];
    }

    // 將最大索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        assert contain(i);

        i += 1;
        data[i] = newItem;

        // 找到indexes[j] = i, j表示data[i]在堆中的位置
        // 之後shiftUp(j), 再shiftDown(j)
//        for( int j = 1 ; j <= count ; j ++ )
//            if( indexes[j] == i ){
//                shiftUp(j);
//                shiftDown(j);
//                return;
//            }

        // 有了 reverse 之後,
        // 我們可以非常簡單的通過reverse直接定位索引i在indexes中的位置
        shiftUp( reverse[i] );
        shiftDown( reverse[i] );
    }

    // 交換索引堆中的索引i和j
    // 由於有了反向索引reverse數組,
    // indexes數組發生改變以後, 相應的就需要維護reverse數組
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;

        reverse[indexes[i]] = i;
        reverse[indexes[j]] = j;
    }

    //********************
    //* 最大索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) < 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) > 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) >= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試索引堆中的索引數組index和反向數組reverse
    // 注意:這個測試在向堆中插入元素以後, 不進行extract操作有效
    public boolean testIndexes(){

        int[] copyIndexes = new int[count+1];
        int[] copyReverseIndexes = new int[count+1];

        for( int i = 0 ; i <= count ; i ++ ) {
            copyIndexes[i] = indexes[i];
            copyReverseIndexes[i] = reverse[i];
        }

        copyIndexes[0] = 0;
        copyReverseIndexes[0] = 0;
        Arrays.sort(copyIndexes);
        Arrays.sort(copyReverseIndexes);

        // 在對索引堆中的索引和反向索引進行排序後,
        // 兩個數組都應該正好是1...count這count個索引
        boolean res = true;
        for( int i = 1 ; i <= count ; i ++ )
            if( copyIndexes[i-1] + 1 != copyIndexes[i] ||
                    copyReverseIndexes[i-1] + 1 != copyReverseIndexes[i] ){
                res = false;
                break;
            }

        if( !res ){
            System.out.println("Error!");
            return false;
        }

        return true;
    }

    // 測試 IndexMaxHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMaxHeap<Integer> indexMaxHeap = new IndexMaxHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMaxHeap.insert( i , (int)(Math.random()*N) );
        assert indexMaxHeap.testIndexes();
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
4-10 和堆相關的其他問題

動態選擇優先級最高的任務執行

在1000000個元素中選出前100名?
在N個元素中選出前M個元素

使用優先隊列?NlogM

多路歸併排序

d叉堆

最大堆 最大索引堆
最小堆 最小索引堆

ShiftUp 和 ShiftDown 中使用賦值操作替換swap操作

表示堆的數組從0開始索引

沒有capacity的限制,動態的調整堆中數組的大小

最大最小隊列

二項堆

斐波那契堆

補充
  • 補充1 優化的Shift Up和Shift Down
public class Main {

    // 比較 MaxHeapSort 和 MaxHeapOSort 的性能的不同
    // 以此來檢驗 MaxHeap 進行ShiftUp和ShiftDown優化前後效率的不同
    public static void main(String[] args) {

        int N = 1000000;

        System.out.println("Test for random array, size = " + N + " , random range [0, " + N + "]");

        Integer[] arr1 = SortTestHelper.generateRandomArray(N, 0, N);
        Integer[] arr2 = Arrays.copyOf(arr1, arr1.length);

        SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapSort", arr1);
        SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapOSort", arr2);

        return;
    }
}
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MaxHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最大堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MaxHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大堆中的堆頂元素
    public Item getMax(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) < 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( data[k].compareTo(data[j]) >= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MaxHeap
    public static void main(String[] args) {

        MaxHeap<Integer> maxHeap = new MaxHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將maxheap中的數據逐漸使用extractMax取出來
        // 取出來的順序應該是按照從大到小的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = maxHeap.extractMax();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從大到小排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] >= arr[i];
    }
}
// 將 ShiftUp 和 ShiftDown 函數使用類似插入排序算法的方式進行優化的最大堆
public class MaxHeapO<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MaxHeapO(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最大堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MaxHeapO(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 像最大堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最大堆中取出堆頂元素, 即堆中所存儲的最大數據
    public Item extractMax(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最大堆中的堆頂元素
    public Item getMax(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最大堆核心輔助函數
    //********************
    private void shiftUp(int k){
        Item e = data[k];
        while( k > 1 && data[k/2].compareTo(e) < 0 ){
            //swap(k, k/2);
            data[k] = data[k/2];
            k /= 2;
        }
        data[k] = e;
    }

    private void shiftDown(int k){

        Item e = data[k];
        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) > 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最大值

            if( e.compareTo(data[j]) >= 0 ) break;
            //swap(k, j);
            data[k] = data[j];
            k = j;
        }
        data[k] = e;
    }

    // 測試 MaxHeapO
    public static void main(String[] args) {

        MaxHeapO<Integer> maxHeap = new MaxHeapO<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            maxHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將maxheap中的數據逐漸使用extractMax取出來
        // 取出來的順序應該是按照從大到小的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = maxHeap.extractMax();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從大到小排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] >= arr[i];
    }
}
// 使用將n個元素插入最大堆中, 再將這n個元素從最大堆中取出的方式, 來對n個元素進行排序
// 以此來檢驗 MaxHeap 進行ShiftUp和ShiftDown優化前後效率的不同
public class MaxHeapOSort {

    // 我們的算法類不允許產生任何實例
    private MaxHeapOSort(){}

    // 對整個arr數組使用MaxHeapOSort排序
    // MaxHeapOSort, 將所有的元素依次添加到堆中, 在將所有元素從堆中依次取出來, 即完成了排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeapO<Comparable> maxHeap = new MaxHeapO<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxHeap.insert(arr[i]);

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 MaxHeapOSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapOSort", arr);

        return;
    }
}
// 使用將n個元素插入最大堆中, 再將這n個元素從最大堆中取出的方式, 來對n個元素進行排序
// 以此來檢驗 MaxHeap 進行ShiftUp和ShiftDown優化前後效率的不同
public class MaxHeapSort {

    // 我們的算法類不允許產生任何實例
    private MaxHeapSort(){}

    // 對整個arr數組使用MaxHeapSort排序
    // MaxHeapSort, 將所有的元素依次添加到堆中, 在將所有元素從堆中依次取出來, 即完成了排序
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            maxHeap.insert(arr[i]);

        for( int i = n-1 ; i >= 0 ; i -- )
            arr[i] = maxHeap.extractMax();
    }

    // 測試 MaxHeapSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_extra1.MaxHeapSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
  • 補充2 最小堆
// 在堆的有關操作中,需要比較堆中元素的大小,所以Item需要extends Comparable
public class MinHeap<Item extends Comparable> {

    protected Item[] data;
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public MinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        count = 0;
        this.capacity = capacity;
    }

    // 構造函數, 通過一個給定數組創建一個最小堆
    // 該構造堆的過程, 時間複雜度爲O(n)
    public MinHeap(Item arr[]){

        int n = arr.length;

        data = (Item[])new Comparable[n+1];
        capacity = n;

        for( int i = 0 ; i < n ; i ++ )
            data[i+1] = arr[i];
        count = n;

        for( int i = count/2 ; i >= 1 ; i -- )
            shiftDown(i);
    }

    // 返回堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小堆中插入一個新的元素 item
    public void insert(Item item){

        assert count + 1 <= capacity;
        data[count+1] = item;
        count ++;
        shiftUp(count);
    }

    // 從最小堆中取出堆頂元素, 即堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;
        Item ret = data[1];

        swap( 1 , count );
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小堆中的堆頂元素
    public Item getMin(){
        assert( count > 0 );
        return data[1];
    }


    // 交換堆中索引爲i和j的兩個元素
    private void swap(int i, int j){
        Item t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    //********************
    //* 最小堆核心輔助函數
    //********************
    private void shiftUp(int k){

        while( k > 1 && data[k/2].compareTo(data[k]) > 0 ){
            swap(k, k/2);
            k /= 2;
        }
    }

    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k; // 在此輪循環中,data[k]和data[j]交換位置
            if( j+1 <= count && data[j+1].compareTo(data[j]) < 0 )
                j ++;
            // data[j] 是 data[2*k]和data[2*k+1]中的最小值

            if( data[k].compareTo(data[j]) <= 0 ) break;
            swap(k, j);
            k = j;
        }
    }

    // 測試 MinHeap
    public static void main(String[] args) {

        MinHeap<Integer> minHeap = new MinHeap<Integer>(100);
        int N = 100; // 堆中元素個數
        int M = 100; // 堆中元素取值範圍[0, M)
        for( int i = 0 ; i < N ; i ++ )
            minHeap.insert( new Integer((int)(Math.random() * M)) );

        Integer[] arr = new Integer[N];
        // 將minheap中的數據逐漸使用extractMin取出來
        // 取出來的順序應該是按照從小到大的順序取出來的
        for( int i = 0 ; i < N ; i ++ ){
            arr[i] = minHeap.extractMin();
            System.out.print(arr[i] + " ");
        }
        System.out.println();

        // 確保arr數組是從小到大排列的
        for( int i = 1 ; i < N ; i ++ )
            assert arr[i-1] <= arr[i];
    }
}
public class MinHeapSort {

    // 我們的算法類不允許產生任何實例
    private MinHeapSort(){}

    // 將所有的元素依次添加到最小堆中, 再在將所有元素從堆中依次取出來, 完成排序
    // 使用這樣的一個最小堆排序, 來檢驗我們的最小堆的正確性
    // 思考:使用最小堆可不可以編寫如第6小節所介紹的優化的快速排序算法?
    public static void sort(Comparable[] arr){

        int n = arr.length;
        MinHeap<Comparable> minHeap = new MinHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            minHeap.insert(arr[i]);

        for( int i = 0 ; i < n ; i ++ )
            arr[i] = minHeap.extractMin();
    }

    // 測試 MinHeapSort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_extra2.MinHeapSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
  • 補充3 最小索引堆
// 最小索引堆
public class IndexMinHeap<Item extends Comparable> {

    protected Item[] data;      // 最小索引堆中的數據
    protected int[] indexes;    // 最小索引堆中的索引, indexes[x] = i 表示索引i在x的位置
    protected int[] reverse;    // 最小索引堆中的反向索引, reverse[i] = x 表示索引i在x的位置
    protected int count;
    protected int capacity;

    // 構造函數, 構造一個空堆, 可容納capacity個元素
    public IndexMinHeap(int capacity){
        data = (Item[])new Comparable[capacity+1];
        indexes = new int[capacity+1];
        reverse = new int[capacity+1];
        for( int i = 0 ; i <= capacity ; i ++ )
            reverse[i] = 0;

        count = 0;
        this.capacity = capacity;
    }

    // 返回索引堆中的元素個數
    public int size(){
        return count;
    }

    // 返回一個布爾值, 表示索引堆中是否爲空
    public boolean isEmpty(){
        return count == 0;
    }

    // 向最小索引堆中插入一個新的元素, 新元素的索引爲i, 元素爲item
    // 傳入的i對用戶而言,是從0索引的
    public void insert(int i, Item item){

        assert count + 1 <= capacity;
        assert i + 1 >= 1 && i + 1 <= capacity;

        // 再插入一個新元素前,還需要保證索引i所在的位置是沒有元素的。
        assert !contain(i);

        i += 1;
        data[i] = item;
        indexes[count+1] = i;
        reverse[i] = count + 1;
        count ++;

        shiftUp(count);
    }

    // 從最小索引堆中取出堆頂元素, 即索引堆中所存儲的最小數據
    public Item extractMin(){
        assert count > 0;

        Item ret = data[indexes[1]];
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 從最小索引堆中取出堆頂元素的索引
    public int extractMinIndex(){
        assert count > 0;

        int ret = indexes[1] - 1;
        swapIndexes( 1 , count );
        reverse[indexes[count]] = 0;
        count --;
        shiftDown(1);

        return ret;
    }

    // 獲取最小索引堆中的堆頂元素
    public Item getMin(){
        assert count > 0;
        return data[indexes[1]];
    }

    // 獲取最小索引堆中的堆頂元素的索引
    public int getMinIndex(){
        assert count > 0;
        return indexes[1]-1;
    }

    // 看索引i所在的位置是否存在元素
    boolean contain( int i ){
        assert  i + 1 >= 1 && i + 1 <= capacity;
        return reverse[i+1] != 0;
    }

    // 獲取最小索引堆中索引爲i的元素
    public Item getItem( int i ){
        assert contain(i);
        return data[i+1];
    }

    // 將最小索引堆中索引爲i的元素修改爲newItem
    public void change( int i , Item newItem ){

        assert contain(i);

        i += 1;
        data[i] = newItem;

        // 有了 reverse 之後,
        // 我們可以非常簡單的通過reverse直接定位索引i在indexes中的位置
        shiftUp( reverse[i] );
        shiftDown( reverse[i] );
    }

    // 交換索引堆中的索引i和j
    // 由於有了反向索引reverse數組,
    // indexes數組發生改變以後, 相應的就需要維護reverse數組
    private void swapIndexes(int i, int j){
        int t = indexes[i];
        indexes[i] = indexes[j];
        indexes[j] = t;

        reverse[indexes[i]] = i;
        reverse[indexes[j]] = j;
    }

    //********************
    //* 最小索引堆核心輔助函數
    //********************

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftUp(int k){

        while( k > 1 && data[indexes[k/2]].compareTo(data[indexes[k]]) > 0 ){
            swapIndexes(k, k/2);
            k /= 2;
        }
    }

    // 索引堆中, 數據之間的比較根據data的大小進行比較, 但實際操作的是索引
    private void shiftDown(int k){

        while( 2*k <= count ){
            int j = 2*k;
            if( j+1 <= count && data[indexes[j+1]].compareTo(data[indexes[j]]) < 0 )
                j ++;

            if( data[indexes[k]].compareTo(data[indexes[j]]) <= 0 )
                break;

            swapIndexes(k, j);
            k = j;
        }
    }

    // 測試 IndexMinHeap
    public static void main(String[] args) {

        int N = 1000000;
        IndexMinHeap<Integer> indexMinHeap = new IndexMinHeap<Integer>(N);
        for( int i = 0 ; i < N ; i ++ )
            indexMinHeap.insert( i , (int)(Math.random()*N) );

    }
}
// 使用最小索引堆進行堆排序, 來驗證我們的最大索引堆的正確性
public class IndexMinHeapSort {

    // 我們的算法類不允許產生任何實例
    private IndexMinHeapSort(){}

    public static void sort(Comparable[] arr){

        int n = arr.length;

        IndexMinHeap<Comparable> indexMinHeap = new IndexMinHeap<Comparable>(n);
        for( int i = 0 ; i < n ; i ++ )
            indexMinHeap.insert( i , arr[i] );

        for( int i = 0 ; i < n ; i ++ )
            arr[i] = indexMinHeap.extractMin();
    }

    // 測試 Index Min Heap Sort
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
        SortTestHelper.testSort("com.imooc.ch4_extra3.IndexMinHeapSort", arr);

        return;
    }
}
public class SortTestHelper {

    // SortTestHelper不允許產生任何實例
    private SortTestHelper(){}

    // 生成有n個元素的隨機數組,每個元素的隨機範圍爲[rangeL, rangeR]
    public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

        assert rangeL <= rangeR;

        Integer[] arr = new Integer[n];

        for (int i = 0; i < n; i++)
            arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
        return arr;
    }

    // 生成一個近乎有序的數組
    // 首先生成一個含有[0...n-1]的完全有序數組, 之後隨機交換swapTimes對數據
    // swapTimes定義了數組的無序程度:
    // swapTimes == 0 時, 數組完全有序
    // swapTimes 越大, 數組越趨向於無序
    public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){

        Integer[] arr = new Integer[n];
        for( int i = 0 ; i < n ; i ++ )
            arr[i] = new Integer(i);

        for( int i = 0 ; i < swapTimes ; i ++ ){
            int a = (int)(Math.random() * n);
            int b = (int)(Math.random() * n);
            int t = arr[a];
            arr[a] = arr[b];
            arr[b] = t;
        }

        return arr;
    }

    // 打印arr數組的所有內容
    public static void printArray(Object[] arr) {

        for (int i = 0; i < arr.length; i++){
            System.out.print( arr[i] );
            System.out.print( ' ' );
        }
        System.out.println();

        return;
    }

    // 判斷arr數組是否有序
    public static boolean isSorted(Comparable[] arr){

        for( int i = 0 ; i < arr.length - 1 ; i ++ )
            if( arr[i].compareTo(arr[i+1]) > 0 )
                return false;
        return true;
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間打印在控制檯上
    public static void testSort(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    // 測試sortClassName所對應的排序算法排序arr數組所得到結果的正確性和算法運行時間
    // 將算法的運行時間以long類型返回, 單位爲毫秒(ms)
    public static long testSort2(String sortClassName, Comparable[] arr){

        // 通過Java的反射機制,通過排序的類名,運行排序函數
        try{
            // 通過sortClassName獲得排序函數的Class對象
            Class sortClass = Class.forName(sortClassName);
            // 通過排序函數的Class對象獲得排序方法
            Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
            // 排序參數只有一個,是可比較數組arr
            Object[] params = new Object[]{arr};

            long startTime = System.currentTimeMillis();
            // 調用排序函數
            sortMethod.invoke(null,params);
            long endTime = System.currentTimeMillis();

            assert isSorted( arr );

            return endTime - startTime;
        }
        catch(Exception e){
            e.printStackTrace();
        }

        return 0;
    }
}
第5章 二分搜索樹
5-1 二分查找法

二叉搜索樹 Binary Search Tree

查找問題 Searching Problem

查找問題是計算機中非常重要的基礎問題

二分查找法 Binary Search

對於有序數列,才能使用二分查找法(排序的作用)

二分查找法的思想在1946年提出

第一個沒有bug的二分查找法在1962年纔出現

// 非遞歸的二分查找算法
public class BinarySearch {

    // 我們的算法類不允許產生任何實例
    private BinarySearch() {}

    // 二分查找法,在有序數組arr中,查找target
    // 如果找到target,返回相應的索引index
    // 如果沒有找到target,返回-1
    public static int find(Comparable[] arr, Comparable target) {

        // 在arr[l...r]之中查找target
        int l = 0, r = arr.length-1;
        while( l <= r ){

            //int mid = (l + r)/2;
            // 防止極端情況下的整形溢出,使用下面的邏輯求出mid
            int mid = l + (r-l)/2;

            if( arr[mid].compareTo(target) == 0 )
                return mid;

            if( arr[mid].compareTo(target) > 0 )
                r = mid - 1;
            else
                l = mid + 1;
        }

        return -1;
    }

    // 測試非遞歸的二分查找算法
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 對於我們的待查找數組[0...N)
        // 對[0...N)區間的數值使用二分查找,最終結果應該就是數字本身
        // 對[N...2*N)區間的數值使用二分查找,因爲這些數字不在arr中,結果爲-1
        for(int i = 0 ; i < 2*N ; i ++) {
            int v = BinarySearch.find(arr, new Integer(i));
            if (i < N)
                assert v == i;
            else
                assert v == -1;
        }

        return;
    }
}
// 遞歸的二分查找算法
public class BinarySearch2 {

    // 我們的算法類不允許產生任何實例
    private BinarySearch2() {}

    private static int find(Comparable[] arr, int l, int r, Comparable target){

        if( l > r )
            return -1;

        //int mid = (l+r)/2;
        // 防止極端情況下的整形溢出,使用下面的邏輯求出mid
        int mid = l + (r-l)/2;

        if( arr[mid].compareTo(target) == 0 )
            return mid;
        else if( arr[mid].compareTo(target) > 0 )
            return find(arr, l, mid-1, target);
        else
            return find(arr, mid+1, r, target);
    }

    // 二分查找法,在有序數組arr中,查找target
    // 如果找到target,返回相應的索引index
    // 如果沒有找到target,返回-1
    public static int find(Comparable[] arr, Comparable target) {

        return find(arr, 0, arr.length-1, target);
    }

    // 測試遞歸的二分查找算法
    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 對於我們的待查找數組[0...N)
        // 對[0...N)區間的數值使用二分查找,最終結果應該就是數字本身
        // 對[N...2*N)區間的數值使用二分查找,因爲這些數字不在arr中,結果爲-1
        for(int i = 0 ; i < 2*N ; i ++) {
            int v = BinarySearch2.find(arr, new Integer(i));
            if (i < N)
                assert v == i;
            else
                assert v == -1;
        }

        return;
    }
}
// 比較非遞歸和遞歸寫法的二分查找的效率
// 非遞歸算法在性能上有微弱優勢
public class Main {

    private Main(){}

    public static void main(String[] args) {

        int N = 1000000;
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);


        // 測試非遞歸二分查找法
        long startTime = System.currentTimeMillis();

        // 對於我們的待查找數組[0...N)
        // 對[0...N)區間的數值使用二分查找,最終結果應該就是數字本身
        // 對[N...2*N)區間的數值使用二分查找,因爲這些數字不在arr中,結果爲-1
        for(int i = 0 ; i < 2*N ; i ++) {
            int v = BinarySearch.find(arr, new Integer(i));
            if (i < N)
                assert v == i;
            else
                assert v == -1;
        }
        long endTime = System.currentTimeMillis();

        System.out.println("Binary Search (Without Recursion): " + (endTime - startTime) + "ms");


        // 測試遞歸的二分查找法
        startTime = System.currentTimeMillis();

        // 對於我們的待查找數組[0...N)
        // 對[0...N)區間的數值使用二分查找,最終結果應該就是數字本身
        // 對[N...2*N)區間的數值使用二分查找,因爲這些數字不在arr中,結果爲-1
        for(int i = 0 ; i < 2*N ; i ++) {
            int v = BinarySearch2.find(arr, new Integer(i));
            if (i < N)
                assert v == i;
            else
                assert v == -1;
        }
        endTime = System.currentTimeMillis();
        System.out.println("Binary Search (With Recursion): " + (endTime - startTime) + "ms");

    }
}

遞歸實現通常思維起來更容易

遞歸在性能上會略差

二分查找法的變種

  • floor 找到第一次出現的位置
  • ceil 找到最後出現的位置
5-2 二分搜索樹基礎

二分搜索樹的優勢

查找表的實現-字典數據結構

查找元素 插入元素 刪除元素
普通數組 O(n) O(n)
順序數組 O(logn) O(n)
二分搜索樹 O(logn) O(logn)

高效
不僅可查找數據;還可以高效地插入,刪除數據-動態維護數據
可以方便地回答很多數據之間的關係
min,max,floor,ceil,rank,select

  • 二叉樹
  • 每個節點的鍵值大於左孩子
  • 每個節點的鍵值小於右孩子
  • 以左右孩子爲根節點的子樹仍爲二分搜索樹

不一定是完全二叉樹

// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }


    public static void main(String[] args) {
    }
}
5-3 二分搜索樹的節點插入
// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }


    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    public static void main(String[] args) {
    }
}
5-4 二分搜索書的查找

二分查找樹的包含contain和查找search同質

// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }


    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
// 文件相關操作
public class FileOperations {

    // 讀取文件名稱爲filename中的內容,並將其中包含的所有詞語放進words中
    public static boolean readFile(String filename, Vector<String> words){

        if (filename == null){
            System.out.println("filename is null");
            return false;
        }

        // 文件讀取
        Scanner scanner;

        try {
            File file = new File(filename);
            if( file.exists() ){
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                return false;
        }
        catch(IOException ioe){
            System.out.println("Cannot open " + filename);
            return false;
        }

        // 簡單分詞
        // 這個分詞方式相對簡陋, 沒有考慮很多文本處理中的特殊問題
        // 在這裏只做demo展示用
        if (scanner.hasNextLine()) {

            String contents = scanner.useDelimiter("\\A").next();

            int start = firstCharacterIndex(contents, 0);
            for (int i = start + 1; i <= contents.length(); )
                if (i == contents.length() || !Character.isLetter(contents.charAt(i))) {
                    String word = contents.substring(start, i).toLowerCase();
                    words.add(word);
                    start = firstCharacterIndex(contents, i);
                    i = start + 1;
                } else
                    i++;
        }

        return true;
    }

    // 尋找字符串s中,從start的位置開始的第一個字母字符的位置
    private static int firstCharacterIndex(String s, int start){

        for( int i = start ; i < s.length() ; i ++ )
            if( Character.isLetter(s.charAt(i)) )
                return i;
        return s.length();
    }
}
public class Main {

    // 測試二分搜索樹和順序查找表之間的性能差距
    // 二分搜索樹的性能遠遠優於順序查找表
    public static void main(String[] args) {

        // 使用聖經作爲我們的測試用例
        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch5_4\\bible.txt";
        Vector<String> words = new Vector<String>();
        if(FileOperations.readFile(filename, words)){
            System.out.println( "There are totally " + words.size() + " words in " + filename );
            System.out.println();

            // 測試 BST
            long startTime = System.currentTimeMillis();

            // 統計聖經中所有詞的詞頻
            // 注: 這個詞頻統計法相對簡陋, 沒有考慮很多文本處理中的特殊問題
            // 在這裏只做性能測試用
            BST<String, Integer> bst = new BST<String, Integer>();
            for (String word: words) {
                Integer res = bst.search(word);
                if (res == null)
                    bst.insert(word, new Integer(1));
                else
                    bst.insert(word, res + 1);
            }

            // 輸出聖經中god一詞出現的頻率
            if( bst.contain("god") )
                System.out.println("'god' : " + bst.search("god") );
            else
                System.out.println("No word 'god' in " + filename);

            long endTime = System.currentTimeMillis();
            System.out.println("BST , time: " + (endTime - startTime) + "ms.");

            System.out.println();


            // 測試順序查找表 SST
            startTime = System.currentTimeMillis();

            // 統計聖經中所有詞的詞頻
            // 注: 這個詞頻統計法相對簡陋, 沒有考慮很多文本處理中的特殊問題
            // 在這裏只做性能測試用
            SST<String, Integer> sst = new SST<String, Integer>();
            for (String word: words) {
                Integer res = sst.search(word);
                if (res == null)
                    sst.insert(word, new Integer(1));
                else
                    sst.insert(word, res + 1);
            }

            // 輸出聖經中god一詞出現的頻率
            if( sst.contain("god") )
                System.out.println("'god' : " + sst.search("god") );
            else
                System.out.println("No word 'god' in " + filename);

            endTime = System.currentTimeMillis();
            System.out.println("SST , time: " + (endTime - startTime) + "ms.");

        }
    }
}
// 順序查找表
public class SST<Key extends Comparable<Key>, Value> {

    // 順序查找表中的節點爲私有的類, 外界不需要了解順序查找法節點的具體實現
    // 我們的順序查找表, 內部本質是一個鏈表
    private class Node {
        private Key key;
        private Value value;
        private Node next;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            next = null;
        }
    }

    private Node head;  // 表頭
    private int count;  // 順序查找表中的節點個數

    // 構造函數
    public SST(){
        head = null;
        count = 0;
    }

    // 返回順序查找表中的節點個數
    public int size(){
        return count;
    }

    // 返回順序查找表是否爲空
    public boolean isEmpty(){
        return count == 0;
    };

    // 向順序查找表中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){

        // 查找一下整個順序表,肯是否存在同樣大小的key
        Node node = head;
        while( node != null ){
            // 若在順序表中找到了同樣大小key的節點
            // 則當前節點不需要插入,將該key所對應的值更新爲value後返回
            if( key.compareTo(node.key) == 0 ){
                node.value = value;
                return;
            }
            node = node.next;
        }

        // 若順序表中沒有同樣大小的key,則創建新節點,將新節點直接插在表頭
        Node newNode = new Node(key, value);
        newNode.next = head;
        head = newNode;
        count ++;
    }

    // 查看順序查找表中是否包含鍵值爲key的節點
    public boolean contain(Key key){

        Node node = head;
        while( node != null ){
            if( key.compareTo(node.key) == 0 )
                return true;
            node = node.next;
        }
        return false;
    }

    // 在順序查找表中查找key所對應的value, 若value不存在, 則返回NULL
    public Value search(Key key){

        Node node = head;
        while( node != null ){
            if( key.compareTo(node.key) == 0 )
                return node.value;
            node = node.next;
        }
        return null;
    }

    // 在順序查找表中刪除(key,value)所對應的節點
    public void remove(Key key){

        if(head == null)
            return;

        // 如果待刪除的節點就是頭結點, 則需要特殊處理
        // 思考: 對於鏈表, 可以使用什麼技術不去特殊處理頭結點的特殊情況?
        // 更多和鏈表相關的算法問題, 歡迎大家看我的《玩兒轉算法面試》課程 :)
        if( key.compareTo(head.key) == 0 ){
            Node delNode = head;
            head = head.next;
            delNode.next = null;
            count--;
            return;
        }

        Node node = head;
        while( node.next != null && node.next.key.compareTo(key) != 0 )
            node = node.next;

        if( node.next != null ){
            Node delNode = node.next;
            node.next = delNode.next;
            delNode.next = null;
            count --;
            return;
        }
    }
}
5-5 二分搜索樹的遍歷(深度優先遍歷)

前序遍歷:先訪問當前節點,再依次遞歸訪問左右子樹
中序遍歷:先遞歸訪問左子樹,再訪問自身,再遞歸訪問右子樹
後續遍歷:先遞歸訪問左右子樹,再訪問自身節點

// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
public class Main {

    // 測試二分搜索樹的前中後序遍歷
    public static void main(String[] args) {

        BST<Integer, Integer> bst = new BST<Integer, Integer>();

        // 取n個取值範圍在[0...m)的隨機整數放進二分搜索樹中
        int N = 10;
        int M = 100;
        for(int i = 0 ; i < N ; i ++){
            Integer key = new Integer((int)(Math.random()*M));
            // 爲了後續測試方便,這裏value值取和key值一樣
            bst.insert(key, key);
            System.out.print(key + " ");
        }
        System.out.println();

        // 測試二分搜索樹的size()
        System.out.println("size: " + bst.size());
        System.out.println();

        // 測試二分搜索樹的前序遍歷 preOrder
        System.out.println("preOrder: ");
        bst.preOrder();
        System.out.println();

        // 測試二分搜索樹的中序遍歷 inOrder
        System.out.println("inOrder: ");
        bst.inOrder();
        System.out.println();

        // 測試二分搜索樹的後序遍歷 postOrder
        System.out.println("postOrder: ");
        bst.postOrder();
        System.out.println();
    }
}
5-6 層序遍歷(廣度優先遍歷)

二分搜索樹的深度優先遍歷
二分搜索樹的廣度優先遍歷(層序)

// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
public class Main {

    // 測試二分搜索樹的前中後序遍歷以及層序遍歷
    public static void main(String[] args) {

        BST<Integer, Integer> bst = new BST<Integer, Integer>();

        // 取n個取值範圍在[0...m)的隨機整數放進二分搜索樹中
        int N = 10;
        int M = 100;
        for(int i = 0 ; i < N ; i ++){
            Integer key = new Integer((int)(Math.random()*M));
            // 爲了後續測試方便,這裏value值取和key值一樣
            bst.insert(key, key);
            System.out.print(key + " ");
        }
        System.out.println();

        // 測試二分搜索樹的size()
        System.out.println("size: " + bst.size());
        System.out.println();

        // 測試二分搜索樹的前序遍歷 preOrder
        System.out.println("preOrder: ");
        bst.preOrder();
        System.out.println();

        // 測試二分搜索樹的中序遍歷 inOrder
        System.out.println("inOrder: ");
        bst.inOrder();
        System.out.println();

        // 測試二分搜索樹的後序遍歷 postOrder
        System.out.println("postOrder: ");
        bst.postOrder();
        System.out.println();

        // 測試二分搜索樹的層序遍歷 levelOrder
        System.out.println("levelOrder: ");
        bst.levelOrder();
        System.out.println();
    }
}
5-7 刪除最大值,最小值
// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    // 尋找二分搜索樹的最小的鍵值
    public Key minimum(){
        assert count != 0;
        Node minNode = minimum( root );
        return minNode.key;
    }

    // 尋找二分搜索樹的最大的鍵值
    public Key maximum(){
        assert count != 0;
        Node maxNode = maximum(root);
        return maxNode.key;
    }

    // 從二分搜索樹中刪除最小值所在節點
    public void removeMin(){
        if( root != null )
            root = removeMin( root );
    }

    // 從二分搜索樹中刪除最大值所在節點
    public void removeMax(){
        if( root != null )
            root = removeMax( root );
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 返回以node爲根的二分搜索樹的最小鍵值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }

    // 返回以node爲根的二分搜索樹的最大鍵值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

        if( node.left == null ){

            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMax(Node node){

        if( node.right == null ){

            Node leftNode = node.left;
            node.left = null;
            count --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
public class Main {

    // 測試二分搜索樹中的removeMin和removeMax
    public static void main(String[] args) {

        BST<Integer, Integer> bst = new BST<Integer, Integer>();

        // 取n個取值範圍在[0...m)的隨機整數放進二分搜索樹中
        int N = 100;
        int M = 100;
        for(int i = 0 ; i < N ; i ++){
            Integer key = new Integer((int)(Math.random()*M));
            // 爲了後續測試方便,這裏value值取和key值一樣
            bst.insert(key, key);
        }
        // 注意, 由於隨機生成的數據有重複, 所以bst中的數據數量大概率是小於n的

        // 測試 removeMin
        // 輸出的元素應該是從小到大排列的
        System.out.println("Test removeMin: ");
        while( !bst.isEmpty() ){
            System.out.print("min: " + bst.minimum() + " , ");
            bst.removeMin();
            System.out.println("After removeMin, size = " + bst.size() );
        }
        System.out.println();


        for(int i = 0 ; i < N ; i ++){
            Integer key = new Integer((int)(Math.random()*M));
            // 爲了後續測試方便,這裏value值取和key值一樣
            bst.insert(key, key);
        }
        // 注意, 由於隨機生成的數據有重複, 所以bst中的數據數量大概率是小於n的

        // 測試 removeMax
        // 輸出的元素應該是從大到小排列的
        System.out.println("Test removeMax: ");
        while( !bst.isEmpty() ){
            System.out.print("max: " + bst.maximum() + " , ");
            bst.removeMax();
            System.out.println("After removeMax, size = " + bst.size() );
        }
    }
}
5-8 二分搜索樹的刪除
  • 刪除只有左孩子的節點
  • 刪除只有右孩子的節點
  • 刪除左右都有孩子的節點
    1962年,Hibbard提出-Hubbard Deletion

刪除左右都有孩子的節點 d
找到 s = min (d->right)
s 是 d 的後繼
s -> right = delMin(d->right)
s -> left = d->left
刪除d,s是新的子樹的根

找到 p = max(d->left)
p 是 d 的前驅

刪除二分搜索樹的任意一個節點 時間複雜度O(logn)

// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }

        public Node(Node node){
            this.key = node.key;
            this.value = node.value;
            this.left = node.left;
            this.right = node.right;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    // 尋找二分搜索樹的最小的鍵值
    public Key minimum(){
        assert count != 0;
        Node minNode = minimum( root );
        return minNode.key;
    }

    // 尋找二分搜索樹的最大的鍵值
    public Key maximum(){
        assert count != 0;
        Node maxNode = maximum(root);
        return maxNode.key;
    }

    // 從二分搜索樹中刪除最小值所在節點
    public void removeMin(){
        if( root != null )
            root = removeMin( root );
    }

    // 從二分搜索樹中刪除最大值所在節點
    public void removeMax(){
        if( root != null )
            root = removeMax( root );
    }

    // 從二分搜索樹中刪除鍵值爲key的節點
    public void remove(Key key){
        root = remove(root, key);
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 返回以node爲根的二分搜索樹的最小鍵值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }

    // 返回以node爲根的二分搜索樹的最大鍵值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

        if( node.left == null ){

            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMax(Node node){

        if( node.right == null ){

            Node leftNode = node.left;
            node.left = null;
            count --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
    // 返回刪除節點後新的二分搜索樹的根
    Node remove(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove( node.left , key );
            return node;
        }
        else if( key.compareTo(node.key) > 0 ){
            node.right = remove( node.right, key );
            return node;
        }
        else{   // key == node->key

            // 待刪除節點左子樹爲空的情況
            if( node.left == null ){
                Node rightNode = node.right;
                node.right = null;
                count --;
                return rightNode;
            }

            // 待刪除節點右子樹爲空的情況
            if( node.right == null ){
                Node leftNode = node.left;
                node.left = null;
                count--;
                return leftNode;
            }

            // 待刪除節點左右子樹均不爲空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = new Node(minimum(node.right));
            count ++;

            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;
            count --;

            return successor;
        }
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
public class Main {

    // 打亂數組順序
    private static void shuffle(Object[] arr){

        for(int i = arr.length-1 ; i >= 0 ; i --){
            int pos = (int) (Math.random() * (i+1));
            Object t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = t;
        }
    }

    // 測試二分搜索樹中的remove
    public static void main(String[] args) {

        BST<Integer, Integer> bst = new BST<Integer, Integer>();

        // 取n個取值範圍在[0...n)的隨機整數放進二分搜索樹中
        int N = 10000;
        for(int i = 0 ; i < N ; i ++){
            Integer key = new Integer((int)(Math.random()*N));
            // 爲了後續測試方便,這裏value值取和key值一樣
            bst.insert(key, key);
        }
        // 注意, 由於隨機生成的數據有重複, 所以bst中的數據數量大概率是小於n的

        // order數組中存放[0...n)的所有元素
        Integer order[] = new Integer[N];
        for( int i = 0 ; i < N ; i ++ )
            order[i] = new Integer(i);
        // 打亂order數組的順序
        shuffle( order );

        // 亂序刪除[0...n)範圍裏的所有元素
        for( int i = 0 ; i < N ; i ++ )
            if( bst.contain( order[i] )){
                bst.remove( order[i] );
                System.out.println("After remove " + order[i] + " size = " + bst.size() );
            }

        // 最終整個二分搜索樹應該爲空
        System.out.println( bst.size() );
    }
}
5-9 二分搜索樹的順序性

minimum、maximun
successor、predecessor
floor、ceil
rank、select

支持重複元素的二分搜索樹

5-10 二分搜索樹的侷限性
// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }

        public Node(Node node){
            this.key = node.key;
            this.value = node.value;
            this.left = node.left;
            this.right = node.right;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    // 尋找二分搜索樹的最小的鍵值
    public Key minimum(){
        assert count != 0;
        Node minNode = minimum( root );
        return minNode.key;
    }

    // 尋找二分搜索樹的最大的鍵值
    public Key maximum(){
        assert count != 0;
        Node maxNode = maximum(root);
        return maxNode.key;
    }

    // 從二分搜索樹中刪除最小值所在節點
    public void removeMin(){
        if( root != null )
            root = removeMin( root );
    }

    // 從二分搜索樹中刪除最大值所在節點
    public void removeMax(){
        if( root != null )
            root = removeMax( root );
    }

    // 從二分搜索樹中刪除鍵值爲key的節點
    public void remove(Key key){
        root = remove(root, key);
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 返回以node爲根的二分搜索樹的最小鍵值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }

    // 返回以node爲根的二分搜索樹的最大鍵值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

        if( node.left == null ){

            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMax(Node node){

        if( node.right == null ){

            Node leftNode = node.left;
            node.left = null;
            count --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
    // 返回刪除節點後新的二分搜索樹的根
    Node remove(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove( node.left , key );
            return node;
        }
        else if( key.compareTo(node.key) > 0 ){
            node.right = remove( node.right, key );
            return node;
        }
        else{   // key == node->key

            // 待刪除節點左子樹爲空的情況
            if( node.left == null ){
                Node rightNode = node.right;
                node.right = null;
                count --;
                return rightNode;
            }

            // 待刪除節點右子樹爲空的情況
            if( node.right == null ){
                Node leftNode = node.left;
                node.left = null;
                count--;
                return leftNode;
            }

            // 待刪除節點左右子樹均不爲空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = new Node(minimum(node.right));
            count ++;

            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;
            count --;

            return successor;
        }
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
// 文件相關操作
public class FileOperations {

    // 讀取文件名稱爲filename中的內容,並將其中包含的所有詞語放進words中
    public static boolean readFile(String filename, Vector<String> words){

        if (filename == null){
            System.out.println("filename is null");
            return false;
        }

        // 文件讀取
        Scanner scanner;

        try {
            File file = new File(filename);
            if( file.exists() ){
                FileInputStream fis = new FileInputStream(file);
                scanner = new Scanner(new BufferedInputStream(fis), "UTF-8");
                scanner.useLocale(Locale.ENGLISH);
            }
            else
                return false;
        }
        catch(IOException ioe){
            System.out.println("Cannot open " + filename);
            return false;
        }

        // 簡單分詞
        // 這個分詞方式相對簡陋, 沒有考慮很多文本處理中的特殊問題
        // 在這裏只做demo展示用
        if (scanner.hasNextLine()) {

            String contents = scanner.useDelimiter("\\A").next();

            int start = firstCharacterIndex(contents, 0);
            for (int i = start + 1; i <= contents.length(); )
                if (i == contents.length() || !Character.isLetter(contents.charAt(i))) {
                    String word = contents.substring(start, i).toLowerCase();
                    words.add(word);
                    start = firstCharacterIndex(contents, i);
                    i = start + 1;
                } else
                    i++;
        }

        return true;
    }

    // 尋找字符串s中,從start的位置開始的第一個字母字符的位置
    private static int firstCharacterIndex(String s, int start){

        for( int i = start ; i < s.length() ; i ++ )
            if( Character.isLetter(s.charAt(i)) )
                return i;
        return s.length();
    }
}
public class Main {

    // 實驗二分搜索樹的侷限性
    public static void main(String[] args) {

        // 我們使用文本量更小的共產主義宣言進行試驗:)
        String filename = "E:\\workspace\\Play-with-Algorithms\\src\\com\\imooc\\ch5_10\\communist.txt";
        Vector<String> words = new Vector<String>();

        if(FileOperations.readFile(filename, words)){

            System.out.println( "There are totally " + words.size() + " words in " + filename );
            System.out.println();


            // 測試1: 我們按照文本原有順序插入進二分搜索樹
            long startTime = System.currentTimeMillis();
            BST<String, Integer> bst = new BST<String, Integer>();
            for (String word: words) {
                Integer res = bst.search(word);
                if (res == null)
                    bst.insert(word, new Integer(1));
                else
                    bst.insert(word, res + 1);
            }

            // 我們查看unite一詞的詞頻
            if( bst.contain("unite") )
                System.out.println("'unite' : " + bst.search("unite") );
            else
                System.out.println("No word 'unite' in " + filename);

            long endTime = System.currentTimeMillis();

            System.out.println("BST , time: " + (endTime - startTime) + "ms.");
            System.out.println();


            // 測試2: 我們按照文本原有順序插入順序查找表
            startTime = System.currentTimeMillis();
            SST<String, Integer> sst = new SST<String, Integer>();
            for (String word: words) {
                Integer res = sst.search(word);
                if (res == null)
                    sst.insert(word, new Integer(1));
                else
                    sst.insert(word, res + 1);
            }

            // 我們查看unite一詞的詞頻
            if( sst.contain("unite") )
                System.out.println("'unite' : " + sst.search("unite") );
            else
                System.out.println("No word 'unite' in " + filename);

            endTime = System.currentTimeMillis();
            System.out.println("SST , time: " + (endTime - startTime) + "ms.");
            System.out.println();


            // 測試3: 我們將原文本排序後插入二分搜索樹, 查看其效率
            startTime = System.currentTimeMillis();
            BST<String, Integer> bst2 = new BST<String, Integer>();
            Collections.sort(words);
            for (String word: words) {
                Integer res = bst2.search(word);
                if (res == null)
                    bst2.insert(word, new Integer(1));
                else
                    bst2.insert(word, res + 1);
            }

            // 我們查看unite一詞的詞頻
            if( bst.contain("unite") )
                System.out.println("'unite' : " + bst2.search("unite") );
            else
                System.out.println("No word 'unite' in " + filename);

            endTime = System.currentTimeMillis();
            System.out.println("BST2 , time: " + (endTime - startTime) + "ms.");
        }
    }
}
// 順序查找表
public class SST<Key extends Comparable<Key>, Value> {

    // 順序查找表中的節點爲私有的類, 外界不需要了解順序查找法節點的具體實現
    // 我們的順序查找表, 內部本質是一個鏈表
    private class Node {
        private Key key;
        private Value value;
        private Node next;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            next = null;
        }
    }

    private Node head;  // 表頭
    private int count;  // 順序查找表中的節點個數

    // 構造函數
    public SST(){
        head = null;
        count = 0;
    }

    // 返回順序查找表中的節點個數
    public int size(){
        return count;
    }

    // 返回順序查找表是否爲空
    public boolean isEmpty(){
        return count == 0;
    };

    // 向順序查找表中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){

        // 查找一下整個順序表,肯是否存在同樣大小的key
        Node node = head;
        while( node != null ){
            // 若在順序表中找到了同樣大小key的節點
            // 則當前節點不需要插入,將該key所對應的值更新爲value後返回
            if( key.compareTo(node.key) == 0 ){
                node.value = value;
                return;
            }
            node = node.next;
        }

        // 若順序表中沒有同樣大小的key,則創建新節點,將新節點直接插在表頭
        Node newNode = new Node(key, value);
        newNode.next = head;
        head = newNode;
        count ++;
    }

    // 查看順序查找表中是否包含鍵值爲key的節點
    public boolean contain(Key key){

        Node node = head;
        while( node != null ){
            if( key.compareTo(node.key) == 0 )
                return true;
            node = node.next;
        }
        return false;
    }

    // 在順序查找表中查找key所對應的value, 若value不存在, 則返回NULL
    public Value search(Key key){

        Node node = head;
        while( node != null ){
            if( key.compareTo(node.key) == 0 )
                return node.value;
            node = node.next;
        }
        return null;
    }

    // 在順序查找表中刪除(key,value)所對應的節點
    public void remove(Key key){

        if(head == null)
            return;

        // 如果待刪除的節點就是頭結點, 則需要特殊處理
        // 思考: 對於鏈表, 可以使用什麼技術不去特殊處理頭結點的特殊情況?
        // 更多和鏈表相關的算法問題, 歡迎大家看我的《玩兒轉算法面試》課程 :)
        if( key.compareTo(head.key) == 0 ){
            Node delNode = head;
            head = head.next;
            delNode.next = null;
            count--;
            return;
        }

        Node node = head;
        while( node.next != null && node.next.key.compareTo(key) != 0 )
            node = node.next;

        if( node.next != null ){
            Node delNode = node.next;
            node.next = delNode.next;
            delNode.next = null;
            count --;
            return;
        }
    }
}

同樣的數據,可以對應不同的二分搜索樹

二分搜索樹可能退化成鏈表

平衡二叉樹:紅黑樹
2-3 tree
AVL tree
Splay tree

平衡二叉樹和堆的結合:Treap

5-11 樹形問題和更多樹

遞歸法天然的樹形性質

歸併排序

快速排序

搜索問題

一條龍游戲

8數碼

8皇后

搬運工

各種各樣的樹
KD樹
區間樹
哈夫曼樹

補充1 二分搜索法的floor和ceil
// 非遞歸的二分查找算法
public class BinarySearch {

    // 我們的算法類不允許產生任何實例
    private BinarySearch() {}

    // 二分查找法,在有序數組arr中,查找target
    // 如果找到target,返回相應的索引index
    // 如果沒有找到target,返回-1
    public static int find(Comparable[] arr, Comparable target) {

        // 在arr[l...r]之中查找target
        int l = 0, r = arr.length-1;
        while( l <= r ){

            //int mid = (l + r)/2;
            // 防止極端情況下的整形溢出,使用下面的邏輯求出mid
            int mid = l + (r-l)/2;

            if( arr[mid].compareTo(target) == 0 )
                return mid;

            if( arr[mid].compareTo(target) > 0 )
                r = mid - 1;
            else
                l = mid + 1;
        }

        return -1;
    }

    // 二分查找法, 在有序數組arr中, 查找target
    // 如果找到target, 返回第一個target相應的索引index
    // 如果沒有找到target, 返回比target小的最大值相應的索引, 如果這個最大值有多個, 返回最大索引
    // 如果這個target比整個數組的最小元素值還要小, 則不存在這個target的floor值, 返回-1
    static int floor(Comparable[] arr, Comparable target){

        // 尋找比target小的最大索引
        int l = -1, r = arr.length-1;
        while( l < r ){
            // 使用向上取整避免死循環
            int mid = l + (r-l+1)/2;
            if( arr[mid].compareTo(target) >= 0 )
                r = mid - 1;
            else
                l = mid;
        }

        assert l == r;

        // 如果該索引+1就是target本身, 該索引+1即爲返回值
        if( l + 1 < arr.length && arr[l+1] == target )
            return l + 1;

        // 否則, 該索引即爲返回值
        return l;
    }


    // 二分查找法, 在有序數組arr中, 查找target
    // 如果找到target, 返回最後一個target相應的索引index
    // 如果沒有找到target, 返回比target大的最小值相應的索引, 如果這個最小值有多個, 返回最小的索引
    // 如果這個target比整個數組的最大元素值還要大, 則不存在這個target的ceil值, 返回整個數組元素個數n
    static int ceil(Comparable[] arr, Comparable target){

        // 尋找比target大的最小索引值
        int l = 0, r = arr.length;
        while( l < r ){
            // 使用普通的向下取整即可避免死循環
            int mid = l + (r-l)/2;
            if( arr[mid].compareTo(target) <= 0 )
                l = mid + 1;
            else // arr[mid] > target
                r = mid;
        }

        assert l == r;

        // 如果該索引-1就是target本身, 該索引+1即爲返回值
        if( r - 1 >= 0 && arr[r-1] == target )
            return r-1;

        // 否則, 該索引即爲返回值
        return r;
    }

    // 測試我們用二分查找法實現的floor和ceil兩個函數
    // 請仔細觀察在我們的測試用例中,有若干的重複元素,對於這些重複元素,floor和ceil計算結果的區別:)
    public static void main(String[] args){

        Integer arr[] = new Integer[]{1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 5, 5, 5, 6, 6, 6};
        for( int i = 0 ; i <= 8 ; i ++ ){

            int floorIndex = floor(arr, i);
            System.out.println("the floor index of " + i + " is " + floorIndex + ".");
            if( floorIndex >= 0 && floorIndex < arr.length )
                System.out.println("The value is " + arr[floorIndex] + ".");
            System.out.println();

            int ceilIndex = ceil(arr, i);
            System.out.println("the ceil index of " + i + " is " + ceilIndex + ".");
            if( ceilIndex >= 0 && ceilIndex < arr.length )
                System.out.println("The value is " + arr[ceilIndex] + ".");
            System.out.println();

            System.out.println();
        }

    }
}
補充2 二分搜索樹中的floor和ceil
// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }

        public Node(Node node){
            this.key = node.key;
            this.value = node.value;
            this.left = node.left;
            this.right = node.right;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        return search( root , key );
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    // 尋找二分搜索樹的最小的鍵值
    public Key minimum(){
        assert count != 0;
        Node minNode = minimum( root );
        return minNode.key;
    }

    // 尋找二分搜索樹的最大的鍵值
    public Key maximum(){
        assert count != 0;
        Node maxNode = maximum(root);
        return maxNode.key;
    }

    // 從二分搜索樹中刪除最小值所在節點
    public void removeMin(){
        if( root != null )
            root = removeMin( root );
    }

    // 從二分搜索樹中刪除最大值所在節點
    public void removeMax(){
        if( root != null )
            root = removeMax( root );
    }

    // 從二分搜索樹中刪除鍵值爲key的節點
    public void remove(Key key){
        root = remove(root, key);
    }

    // 尋找key的floor值, 遞歸算法
    // 如果不存在key的floor值(key比BST中的最小值還小), 返回NULL
    public Key floor(Key key){

        if( count == 0 || key.compareTo(minimum()) < 0 )
            return null;

        Node floorNode = floor(root, key);
        return floorNode.key;
    }

    // 尋找key的ceil值, 遞歸算法
    // 如果不存在key的ceil值(key比BST中的最大值還大), 返回NULL
    Key ceil(Key key){

        if( count == 0 || key.compareTo(maximum()) > 0 )
            return null;

        Node ceilNode = ceil(root, key);
        return ceilNode.key;
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Value search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node.value;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 返回以node爲根的二分搜索樹的最小鍵值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }

    // 返回以node爲根的二分搜索樹的最大鍵值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

        if( node.left == null ){

            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMax(Node node){

        if( node.right == null ){

            Node leftNode = node.left;
            node.left = null;
            count --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
    // 返回刪除節點後新的二分搜索樹的根
    private Node remove(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove( node.left , key );
            return node;
        }
        else if( key.compareTo(node.key) > 0 ){
            node.right = remove( node.right, key );
            return node;
        }
        else{   // key == node->key

            // 待刪除節點左子樹爲空的情況
            if( node.left == null ){
                Node rightNode = node.right;
                node.right = null;
                count --;
                return rightNode;
            }

            // 待刪除節點右子樹爲空的情況
            if( node.right == null ){
                Node leftNode = node.left;
                node.left = null;
                count--;
                return leftNode;
            }

            // 待刪除節點左右子樹均不爲空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = new Node(minimum(node.right));
            count ++;

            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;
            count --;

            return successor;
        }
    }

    // 在以node爲根的二叉搜索樹中, 尋找key的floor值所處的節點, 遞歸算法
    private Node floor(Node node, Key key){

        if( node == null )
            return null;

        // 如果node的key值和要尋找的key值相等
        // 則node本身就是key的floor節點
        if( node.key.compareTo(key) == 0 )
            return node;

        // 如果node的key值比要尋找的key值大
        // 則要尋找的key的floor節點一定在node的左子樹中
        if( node.key.compareTo(key) > 0 )
            return floor( node.left , key );

        // 如果node->key < key
        // 則node有可能是key的floor節點, 也有可能不是(存在比node->key大但是小於key的其餘節點)
        // 需要嘗試向node的右子樹尋找一下
        Node tempNode = floor( node.right , key );
        if( tempNode != null )
            return tempNode;

        return node;
    }


    // 在以node爲根的二叉搜索樹中, 尋找key的ceil值所處的節點, 遞歸算法
    Node ceil(Node node, Key key){

        if( node == null )
            return null;

        // 如果node的key值和要尋找的key值相等
        // 則node本身就是key的ceil節點
        if( node.key.compareTo(key) == 0 )
            return node;

        // 如果node的key值比要尋找的key值小
        // 則要尋找的key的ceil節點一定在node的右子樹中
        if( node.key.compareTo(key) < 0 )
            return ceil( node.right , key );

        // 如果node->key > key
        // 則node有可能是key的ceil節點, 也有可能不是(存在比node->key小但是大於key的其餘節點)
        // 需要嘗試向node的左子樹尋找一下
        Node tempNode = ceil( node.left , key );
        if( tempNode != null )
            return tempNode;

        return node;
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res == Integer.toString(i);
            else
                assert res == null;
        }
    }
}
// 測試二分搜索樹中的floor和ceil兩個函數
public class Main {

    // 打亂數組順序
    private static void shuffle(ArrayList arr){

        for(int i = arr.size()-1 ; i >= 0 ; i --){
            int pos = (int) (Math.random() * (i+1));
            Object t = arr.get(pos);
            arr.set(pos, arr.get(i));
            arr.set(i, t);
        }
    }

    // 測試二分搜索樹中的floor和ceil兩個函數
    public static void main(String[] args) {

        BST<Integer, Integer> bst = new BST<Integer, Integer>();

        //將[0, N)之間的偶數保存在nums中
        int N = 1000;
        ArrayList<Integer> nums = new ArrayList<Integer>();
        for(int i = 0 ; i < N ; i += 2)
            nums.add(i);
        int minNum = nums.get(0);
        int maxNum = nums.get(nums.size()-1);

        // 將nums亂序處理
        shuffle(nums);

        // 向二分搜索樹中插入[0, N)之間的所有偶數
        for(Integer num: nums)
            bst.insert(num, num);

        // 對[0...N]區間裏的N+1個數, 調用二分搜索樹的floor和ceil, 查看其結果
        for( int i = 0 ; i < N ; i ++ ){

            // 測試floor
            Integer floorKey = bst.floor(new Integer(i));
            if(i % 2 == 0){
                if(i >= 0 && i < N) assert floorKey == i;
                else if(i < 0)      assert floorKey == null;
                else                assert floorKey == maxNum;
            }
            else{
                if(i - 1 >= 0 && i - 1 < N) assert floorKey == i - 1;
                else if(i - 1 < 0)          assert floorKey == null;
                else                        assert floorKey == maxNum;
            }

            System.out.print( "The floor of " + i + " is ");
            if( floorKey == null )
                System.out.println("NULL");
            else
                System.out.println(floorKey);


            // 測試ceil
            Integer ceilKey = bst.ceil(new Integer(i));
            if(i % 2 == 0) {
                if( i >= 0 && i < N ) assert ceilKey == i;
                else if(i < 0)        assert ceilKey == minNum;
                else                  assert ceilKey == null;
            }
            else{
                if(i + 1 >= 0 && i + 1 < N) assert ceilKey == i + 1;
                else if(i + 1 < 0)          assert ceilKey == minNum;
                else                        assert ceilKey == null;
            }

            System.out.print( "the ceil of " + i + " is ");
            if( ceilKey == null )
                System.out.println("NULL");
            else
                System.out.println(ceilKey);

            System.out.println();
        }
    }
}
補充3 二分搜索樹中的前驅和後繼
// 二分搜索樹
// 由於Key需要能夠進行比較,所以需要extends Comparable<Key>
public class BST<Key extends Comparable<Key>, Value> {

    // 樹中的節點爲私有的類, 外界不需要了解二分搜索樹節點的具體實現
    private class Node {
        private Key key;
        private Value value;
        private Node left, right;

        public Node(Key key, Value value) {
            this.key = key;
            this.value = value;
            left = right = null;
        }

        public Node(Node node){
            this.key = node.key;
            this.value = node.value;
            this.left = node.left;
            this.right = node.right;
        }
    }

    private Node root;  // 根節點
    private int count;  // 樹種的節點個數

    // 構造函數, 默認構造一棵空二分搜索樹
    public BST() {
        root = null;
        count = 0;
    }

    // 返回二分搜索樹的節點個數
    public int size() {
        return count;
    }

    // 返回二分搜索樹是否爲空
    public boolean isEmpty() {
        return count == 0;
    }

    // 向二分搜索樹中插入一個新的(key, value)數據對
    public void insert(Key key, Value value){
        root = insert(root, key, value);
    }

    // 查看二分搜索樹中是否存在鍵key
    public boolean contain(Key key){
        return contain(root, key);
    }

    // 在二分搜索樹中搜索鍵key所對應的值。如果這個值不存在, 則返回null
    public Value search(Key key){
        Node node = search( root , key );
        return node == null ? null : node.value;
    }

    // 二分搜索樹的前序遍歷
    public void preOrder(){
        preOrder(root);
    }

    // 二分搜索樹的中序遍歷
    public void inOrder(){
        inOrder(root);
    }

    // 二分搜索樹的後序遍歷
    public void postOrder(){
        postOrder(root);
    }

    // 二分搜索樹的層序遍歷
    public void levelOrder(){

        // 我們使用LinkedList來作爲我們的隊列
        LinkedList<Node> q = new LinkedList<Node>();
        q.add(root);
        while( !q.isEmpty() ){

            Node node = q.remove();

            System.out.println(node.key);

            if( node.left != null )
                q.add( node.left );
            if( node.right != null )
                q.add( node.right );
        }
    }

    // 尋找二分搜索樹的最小的鍵值
    public Key minimum(){
        assert count != 0;
        Node minNode = minimum( root );
        return minNode.key;
    }

    // 尋找二分搜索樹的最大的鍵值
    public Key maximum(){
        assert count != 0;
        Node maxNode = maximum(root);
        return maxNode.key;
    }

    // 從二分搜索樹中刪除最小值所在節點
    public void removeMin(){
        if( root != null )
            root = removeMin( root );
    }

    // 從二分搜索樹中刪除最大值所在節點
    public void removeMax(){
        if( root != null )
            root = removeMax( root );
    }

    // 從二分搜索樹中刪除鍵值爲key的節點
    public void remove(Key key){
        root = remove(root, key);
    }

    // 尋找key的floor值, 遞歸算法
    // 如果不存在key的floor值(key比BST中的最小值還小), 返回NULL
    public Key floor(Key key){

        if( count == 0 || key.compareTo(minimum()) < 0 )
            return null;

        Node floorNode = floor(root, key);
        return floorNode.key;
    }

    // 尋找key的ceil值, 遞歸算法
    // 如果不存在key的ceil值(key比BST中的最大值還大), 返回NULL
    public Key ceil(Key key){

        if( count == 0 || key.compareTo(maximum()) > 0 )
            return null;

        Node ceilNode = ceil(root, key);
        return ceilNode.key;
    }

    // 查找key的前驅
    // 如果不存在key的前驅(key不存在, 或者key是整棵二叉樹中的最小值), 則返回NULL
    public Key predecessor(Key key){

        Node node = search(root, key);
        // 如果key所在的節點不存在, 則key沒有前驅, 返回NULL
        if(node == null)
            return null;

        // 如果key所在的節點左子樹不爲空,則其左子樹的最大值爲key的前驅
        if(node.left != null)
            return maximum(node.left).key;

        // 否則, key的前驅在從根節點到key的路徑上, 在這個路徑上尋找到比key小的最大值, 即爲key的前驅
        Node preNode = predecessorFromAncestor(root, key);
        return preNode == null ? null : preNode.key;
    }

    // 查找key的後繼, 遞歸算法
    // 如果不存在key的後繼(key不存在, 或者key是整棵二叉樹中的最大值), 則返回NULL
    public Key successor(Key key){

        Node node = search(root, key);
        // 如果key所在的節點不存在, 則key沒有前驅, 返回NULL
        if(node == null)
            return null;

        // 如果key所在的節點右子樹不爲空,則其右子樹的最小值爲key的後繼
        if(node.right != null)
            return minimum(node.right).key;

        // 否則, key的後繼在從根節點到key的路徑上, 在這個路徑上尋找到比key大的最小值, 即爲key的後繼
        Node sucNode = successorFromAncestor(root, key);
        return sucNode == null ? null : sucNode.key;
    }

    //********************
    //* 二分搜索樹的輔助函數
    //********************

    // 向以node爲根的二分搜索樹中, 插入節點(key, value), 使用遞歸算法
    // 返回插入新節點後的二分搜索樹的根
    private Node insert(Node node, Key key, Value value){

        if( node == null ){
            count ++;
            return new Node(key, value);
        }

        if( key.compareTo(node.key) == 0 )
            node.value = value;
        else if( key.compareTo(node.key) < 0 )
            node.left = insert( node.left , key, value);
        else    // key > node->key
            node.right = insert( node.right, key, value);

        return node;
    }

    // 查看以node爲根的二分搜索樹中是否包含鍵值爲key的節點, 使用遞歸算法
    private boolean contain(Node node, Key key){

        if( node == null )
            return false;

        if( key.compareTo(node.key) == 0 )
            return true;
        else if( key.compareTo(node.key) < 0 )
            return contain( node.left , key );
        else // key > node->key
            return contain( node.right , key );
    }

    // 在以node爲根的二分搜索樹中查找key所對應的value, 遞歸算法
    // 若value不存在, 則返回NULL
    private Node search(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) == 0 )
            return node;
        else if( key.compareTo(node.key) < 0 )
            return search( node.left , key );
        else // key > node->key
            return search( node.right, key );
    }

    // 對以node爲根的二叉搜索樹進行前序遍歷, 遞歸算法
    private void preOrder(Node node){

        if( node != null ){
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行中序遍歷, 遞歸算法
    private void inOrder(Node node){

        if( node != null ){
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    // 對以node爲根的二叉搜索樹進行後序遍歷, 遞歸算法
    private void postOrder(Node node){

        if( node != null ){
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.key);
        }
    }

    // 返回以node爲根的二分搜索樹的最小鍵值所在的節點
    private Node minimum(Node node){
        if( node.left == null )
            return node;

        return minimum(node.left);
    }

    // 返回以node爲根的二分搜索樹的最大鍵值所在的節點
    private Node maximum(Node node){
        if( node.right == null )
            return node;

        return maximum(node.right);
    }

    // 刪除掉以node爲根的二分搜索樹中的最小節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMin(Node node){

        if( node.left == null ){

            Node rightNode = node.right;
            node.right = null;
            count --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中的最大節點
    // 返回刪除節點後新的二分搜索樹的根
    private Node removeMax(Node node){

        if( node.right == null ){

            Node leftNode = node.left;
            node.left = null;
            count --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    // 刪除掉以node爲根的二分搜索樹中鍵值爲key的節點, 遞歸算法
    // 返回刪除節點後新的二分搜索樹的根
    private Node remove(Node node, Key key){

        if( node == null )
            return null;

        if( key.compareTo(node.key) < 0 ){
            node.left = remove( node.left , key );
            return node;
        }
        else if( key.compareTo(node.key) > 0 ){
            node.right = remove( node.right, key );
            return node;
        }
        else{   // key == node->key

            // 待刪除節點左子樹爲空的情況
            if( node.left == null ){
                Node rightNode = node.right;
                node.right = null;
                count --;
                return rightNode;
            }

            // 待刪除節點右子樹爲空的情況
            if( node.right == null ){
                Node leftNode = node.left;
                node.left = null;
                count--;
                return leftNode;
            }

            // 待刪除節點左右子樹均不爲空的情況

            // 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
            // 用這個節點頂替待刪除節點的位置
            Node successor = new Node(minimum(node.right));
            count ++;

            successor.right = removeMin(node.right);
            successor.left = node.left;

            node.left = node.right = null;
            count --;

            return successor;
        }
    }

    // 在以node爲根的二叉搜索樹中, 尋找key的floor值所處的節點, 遞歸算法
    private Node floor(Node node, Key key){

        if( node == null )
            return null;

        // 如果node的key值和要尋找的key值相等
        // 則node本身就是key的floor節點
        if( node.key.compareTo(key) == 0 )
            return node;

        // 如果node的key值比要尋找的key值大
        // 則要尋找的key的floor節點一定在node的左子樹中
        if( node.key.compareTo(key) > 0 )
            return floor( node.left , key );

        // 如果node->key < key
        // 則node有可能是key的floor節點, 也有可能不是(存在比node->key大但是小於key的其餘節點)
        // 需要嘗試向node的右子樹尋找一下
        Node tempNode = floor( node.right , key );
        if( tempNode != null )
            return tempNode;

        return node;
    }

    // 在以node爲根的二叉搜索樹中, 尋找key的ceil值所處的節點, 遞歸算法
    private Node ceil(Node node, Key key){

        if( node == null )
            return null;

        // 如果node的key值和要尋找的key值相等
        // 則node本身就是key的ceil節點
        if( node.key.compareTo(key) == 0 )
            return node;

        // 如果node的key值比要尋找的key值小
        // 則要尋找的key的ceil節點一定在node的右子樹中
        if( node.key.compareTo(key) < 0 )
            return ceil( node.right , key );

        // 如果node->key > key
        // 則node有可能是key的ceil節點, 也有可能不是(存在比node->key小但是大於key的其餘節點)
        // 需要嘗試向node的左子樹尋找一下
        Node tempNode = ceil( node.left , key );
        if( tempNode != null )
            return tempNode;

        return node;
    }

    // 在以node爲根的二叉搜索樹中, 尋找key的祖先中,比key小的最大值所在節點, 遞歸算法
    // 算法調用前已保證key存在在以node爲根的二叉樹中
    Node predecessorFromAncestor(Node node, Key key){

        if(node.key.compareTo(key) == 0)
            return null;

        Node maxNode;
        if(key.compareTo(node.key) < 0)
            // 如果當前節點大於key, 則當前節點不可能是比key小的最大值
            // 向下搜索到的結果直接返回
            return predecessorFromAncestor(node.left, key);
        else{
            assert key.compareTo(node.key) > 0;
            // 如果當前節點小於key, 則當前節點有可能是比key小的最大值
            // 向下搜索結果存儲到maxNode中
            maxNode = predecessorFromAncestor(node.right, key);
            if(maxNode != null)
                // maxNode和當前節點node取最大值返回
                return maxNode.key.compareTo(node.key) > 0 ? maxNode : node;
            else
                // 如果maxNode爲空, 則當前節點即爲結果
                return node;
        }
    }

    // 在以node爲根的二叉搜索樹中, 尋找key的祖先中,比key大的最小值所在節點, 遞歸算法
    // 算法調用前已保證key存在在以node爲根的二叉樹中
    Node successorFromAncestor(Node node, Key key){

        if(node.key.compareTo(key) == 0)
            return null;

        Node minNode;
        if(key.compareTo(node.key) > 0)
            // 如果當前節點小於key, 則當前節點不可能是比key大的最小值
            // 向下搜索到的結果直接返回
            return successorFromAncestor(node.right, key);
        else{
            assert(key.compareTo(node.key) < 0);
            // 如果當前節點大於key, 則當前節點有可能是比key大的最小值
            // 向下搜索結果存儲到minNode中
            minNode = predecessorFromAncestor(node.left, key);
            if(minNode != null)
                // minNode和當前節點node取最小值返回
                return minNode.key.compareTo(node.key) < 0 ? minNode : node;
            else
                // 如果minNode爲空, 則當前節點即爲結果
                return node;
        }
    }


    // 測試二分搜索樹
    public static void main(String[] args) {

        int N = 1000000;

        // 創建一個數組,包含[0...N)的所有元素
        Integer[] arr = new Integer[N];
        for(int i = 0 ; i < N ; i ++)
            arr[i] = new Integer(i);

        // 打亂數組順序
        for(int i = 0 ; i < N ; i ++){
            int pos = (int) (Math.random() * (i+1));
            Integer t = arr[pos];
            arr[pos] = arr[i];
            arr[i] = arr[pos];
        }
        // 由於我們實現的二分搜索樹不是平衡二叉樹,
        // 所以如果按照順序插入一組數據,我們的二分搜索樹會退化成爲一個鏈表
        // 平衡二叉樹的實現,我們在這個課程中沒有涉及,
        // 有興趣的同學可以查看資料自學諸如紅黑樹的實現
        // 以後有機會,我會在別的課程裏向大家介紹平衡二叉樹的實現的:)


        // 我們測試用的的二分搜索樹的鍵類型爲Integer,值類型爲String
        // 鍵值的對應關係爲每個整型對應代表這個整型的字符串
        BST<Integer,String> bst = new BST<Integer,String>();
        for(int i = 0 ; i < N ; i ++)
            bst.insert(new Integer(arr[i]), Integer.toString(arr[i]));

        // 對[0...2*N)的所有整型測試在二分搜索樹中查找
        // 若i在[0...N)之間,則能查找到整型所對應的字符串
        // 若i在[N...2*N)之間,則結果爲null
        for(int i = 0 ; i < 2*N ; i ++){
            String res = bst.search(new Integer(i));
            if( i < N )
                assert res.equals(Integer.toString(i));
            else
                assert res == null;
        }
    }
}
// 測試二分搜索樹中的floor和ceil兩個函數
public class Main {

    // 打亂數組順序
    private static void shuffle(ArrayList arr){

        for(int i = arr.size()-1 ; i >= 0 ; i --){
            int pos = (int) (Math.random() * (i+1));
            Object t = arr.get(pos);
            arr.set(pos, arr.get(i));
            arr.set(i, t);
        }
    }

    // 測試二分搜索樹中的predecessor和successor兩個函數
    public static void main(String[] args) {

        // 生成 0 到 N-1 一共 N 個數字的數組
        int N = 1000;
        ArrayList<Integer> nums = new ArrayList<Integer>();
        for( int i = 0 ; i < N ; i ++)
            nums.add(i);

        // 將數組中的數組亂序
        shuffle(nums);

        // 將這個N個數插入到二叉樹中
        BST<Integer, Integer> bst = new BST<Integer, Integer>();
        for(Integer num: nums)
            bst.insert(num, num);

        // 測試前驅算法, 除了數字0沒有前驅, 每個數字x的前驅應該爲x-1
        for(int i = 0 ; i < N ; i ++) {
            if (i == 0) {
                assert bst.predecessor(i) == null;
                System.out.println("The predesessor of 0 is NULL");
            } else {
                assert bst.predecessor(i) == i - 1;
                System.out.println("The predesessor of " + i + " is " + (i - 1));
            }
        }

        System.out.println();

        // 測試後繼算法, 除了數字沒有N-1後繼, 每個數字x的後繼應該爲x+1
        for(int i = 0 ; i < N ; i ++){
            if( i == N-1 ){
                assert bst.successor(i) == null;
                System.out.println("The successor of " + i + " is NULL");
            }
            else{
                assert bst.successor(i) == i+1;
                System.out.println("The successor of " + i + " is " + (i+1));
            }
        }
    }
}

《算法與數據結構》示例代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章