排序算法(Java)

控制檯運行編譯java程序帶中文亂碼問題解決辦法: javac -encoding utf-8 Test.java

穩定性:排序算法需要保留數組中重複元素的相對位置。(具體詳見算法第四版P217)

冒泡排序

思想: 兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄爲止。

時間複雜度: O(N2)

空間複雜度: O(1)

穩定性: 穩定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        for (int i = 0; i < N-1; i++) {
            for (int j = 0; j < N-i-1; j++) {
                if (nums[j] > nums[j + 1]) {
                    temp = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = temp;
                }
            }
        }
    }
}

選擇排序

思想: 每一趟在n-i+1(i=1,2,…,n-1)個記錄中選取關鍵字最小的記錄作爲有序序列的第i個記錄。

時間複雜度: O(N2)

空間複雜度: O(1)

穩定性: 不穩定(說明:舉個例子,序列5 8 5 2 9, 我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序不是一個穩定的排序算法)

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        for (int i = 0; i < N; i++ ) {
            int min = i;
            for (int j = i+1; j < N; j++) {
                if (nums[j] < nums[min]) {
                    min = j;
                }
            }
            if (min != i) {
                temp = nums[i];
                nums[i] = nums[min];
                nums[min] = temp;   
            }       
        }
    }
}

插入排序

思想: 將一個記錄插入到已經排序好的有序表中,從而得到一個新的、記錄數增1的有序表

時間複雜度: O(N2)

空間複雜度: O(1)

穩定性: 穩定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        for (int i = 1; i < N; i++ ) {
            for (int j = i; j > 0 && nums[j] < nums[j-1]; j--) {
                temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

改進版本

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int i = 0; 
        int j = 0;
        for (i = 1; i < N; i++) {
            if (nums[i] < nums[i-1]) {
                int temp = nums[i];
                for (j = i-1; (j >= 0) && (nums[j] > temp); j--) {
                    nums[j+1] = nums[j];
                }
                nums[j+1] = temp;
            }
        }
    }
}

希爾排序(插入排序升級)

思想: 將數據分爲若干組記錄,然後分別對每一組做插入排序。

時間複雜度: O(NlgN)

空間複雜度: O(1)

穩定性: 不穩定

public class Sort {
    public static void sort(int[] nums) {
        int N = nums.length;
        int temp = 0;
        int h = 1;
        int i = 0; 
        int j = 0;
        //固定步長:1,4,13,40...
        while (h < N/3) {
            h = 3*h + 1;
        }
        while (h >= 1) {
            //下面操作和插入排序算法基本相同
            for (i = h; i < N; i++) {
                if (nums[i] < nums[i - h]) {
                    int temp = nums[i];
                    for (j = i-h; (j >= 0) && (nums[j] > temp); j = j-h) {
                        nums[j+h] = nums[j];
                    }
                    nums[j+h] = temp;
                }
            }
            h = h / 3;
        }
    }
}

快速排序(冒泡排序增強)

思想: 選取一個軸值(比較的基準),將待排序記錄分爲獨立的兩個部分,左側記錄都是小於或等於軸值,右側記錄都是大於或等於軸值,然後分別對左側部分和右側部分重複前面的過程,也就是左側部分又選擇一個軸值,又分爲兩個獨立的部分,這就使用了遞歸了。到最後,整個序列就變得有序了。

時間複雜度: O(NlgN)

空間複雜度: O(lgN)~O(N)

穩定性: 不穩定

public class Sort {
    public static void sort(int[] nums) {
        //消除對輸入的依賴
        //StdRandom.shuffle(a);   
        sort(nums, 0, nums.length - 1);
    }

    public static void sort (int[] nums, int lo, int hi) {
        if (hi <= lo) return;
        //切分
        int j = partition(nums, lo, hi);
        sort(nums, lo, j-1);
        sort(nums, j+1, hi);
    }

    public static int partition(int[] nums, int lo, int hi) {
        //定義左右掃描的指針
        int i = lo, j = hi + 1;
        //定義切分的元素d
        int point = nums[lo];
        int temp = 0;
        while (true) {
            while (nums[++i] < point) if (i == hi) break;
            while (point < nums[--j]) if (j == lo) break;
            if (i >= j) break;

            //交換兩個元素
            temp = nums[i];
            nums[i] = nums[j];
            nums[j] = temp;
        }
        //將切分元素放到數組中合適位置
        temp = nums[lo];
        nums[lo] = nums[j];
        nums[j] = temp;

        return j;
    }
}

堆排序(選擇排序增強)

思想: 利用優先隊列的刪除最大元素的特點,依次將將刪除的最大元素保存起來,就有序了。當然,這裏的優先隊列是用堆實現的。

第一步:需要保證堆有序,也就是每一個父節點要比它的任意子節點要大;

第二步:使用堆的下沉操作來實現排序;

時間複雜度: O(NlgN)

空間複雜度: O(1)

穩定性: 不穩定

public class Sort {
    public static void heapSort(int[] a) {
        int N = a.length;
        //第一步:通過下沉操作來實現堆有序,這裏只需要操作數組中前面的一半元素即可
        for (int k = N / 2; k >= 1; k--) {
            sink(a, k, N);
        }
        //第二步:將堆有序的數組使用下沉操作來得到有序數組
        while (N > 1) {
            exch(a, 1, N--);
            sink(a, 1, N);
        }
    }

    //堆下沉操作
    public static void sink(int[] a, int k, int N) {
        while (2*k <= N) {
            int j = 2*k;
            //取兩個子節點中較大的一個
            if (j < N && less(a, j, j+1)) j++;
            //比較如果父節點比子節點中較大的一個小,則交換
            if (!less(a, k, j)) break;
            exch(a, k, j);
            //繼續往下面遍歷
            k = j;
        }
    }
    //之所以取i-1,是因爲堆中下標是從1開始的,需要還原到數組中從0開始的。
    public static boolean less(int[] a, int i, int j) {
        return a[i-1] < a[j-1];
    }

    public static void exch(int[] a, int i, int j) {
        int temp = a[i-1];
        a[i-1] = a[j-1];
        a[j-1] = temp;
    }        
}

歸併排序

思想: 要將一個數組排序,可以先(遞歸地)將它們分成兩半分別排序,然後將它們的結果歸併起來。

時間複雜度: O(NlgN)

空間複雜度: O(N)

穩定性: 穩定

public class Sort {
    //1.將兩個有序數組合併爲一個有序數組
    public static void merge(int[] a, int lo, int mid, int hi) {
        //將a[lo..mid] 和 a[mid+1..hi]歸併
        //i代表左半邊索引,j代表右半邊索引
        int i = lo, j = mid + 1;
        //定義一個輔助數組
        int[] aux;
        //將a[lo..hi]複製到aux[lo..hi]
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        for (int k = lo; k <= hi; k++) {
            //左半邊元素用盡,取右半邊的元素
            if (i > mid) {
                a[k] = aux[j++];
            //右半邊元素用盡,取左半邊元素    
            } else if (j > hi) {
                a[k] = aux[i++];
            //右半邊的當前元素小於左半邊的當前元素,則取右半邊的元素
            } else if (aux[j] < aux[i]) {
                a[k] = aux[j++];
            //左半邊的當前元素小於等於右半邊的當前元素,則取左半邊的元素
            } else {
                a[k] = aux[i++];
            }
        }
    }
    //自頂向下
    pubic static void sort (int[] a, int lo, int hi) {
        if (hi <= lo) {
            return;
        }
        int mid = lo + (hi - lo) / 2;
        //將左半邊排序
        sort(a, lo, mid);
        //將右半部分排序
        sort(a, mid+1, hi);
        //歸併結果
        merger(a, lo, mid, hi);
    }

    //自底向上
    public static void sort (int[] a) {
        int N = a.length;
        int[] aux = new int[N];
        //定義子數組的大小
        for (int sz = 1; sz < N; sz = sz + sz) {
            //子數組的索引                        
            for (int lo = 0; lo < N-sz; lo += sz+sz) {  
                //lo+sz+sz-1可能越界,因此需要一個min函數來取邊界                
                merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));       
            }
        }
    }
}

總結

時間複雜度:
O(N)
計數排序、基數排序

O(N2)
冒泡排序、選擇排序、插入排序

O(NlogN)
希爾排序、堆排序、快速排序、歸併排序

空間複雜度:
O(1)
插入排序、選擇排序、冒泡排序、堆排序、希爾排序

O(logN) ~ O(N)
快速排序

O(N)
歸併排序

O(M) M爲桶的數量
計數排序、基數排序

穩定性:

穩定的排序算法:
冒泡排序、插入排序、歸併排序、計數排序、基數排序、桶排序

不穩定的排序算法:
選擇排序、快速排序、希爾排序、堆排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章