數據結構 JAVA描述(十三) 排序總結

時間複雜度

  • O(n ㏒₂ n):快速排序,堆排序,歸併排序。其中快排最好

  • O(n²):直接插入排序,冒泡排序,直接選擇排序。其中直接插入排序最好,特別是關鍵字近似有序的情況

  • O(n):基數排序

當關鍵字有序,直接插入和冒泡排序可以達到O(n);而快速排序會蛻化到 O(n²)。

空間複雜度

所有簡單排序(直接插入,冒泡,直接選擇)和堆排序爲O(1);快速排序爲O(㏒₂ n);歸併排序,爲O(n);鏈式基數排序爲O(rd)

穩定性

  • 穩定:直接插入,冒泡,歸併,樹形選擇,基數

  • 不穩定:希爾,直接選擇,快速,堆


這裏寫圖片描述

建議:

  • 若n較小(n≤50),應 直接插入排序,直接選擇排序

  • 若基本有序,應 直接插入排序,冒泡排序

  • 若n比較大,則應採用時間複雜度是O(n ㏒₂ n)的 快速排序,堆排序,歸併排序


代碼總彙

Sort

package Sort;

import java.util.Arrays;

/**
 * @Description 各種排序算法,都是以數組的存儲形式
 * @author liuquan
 * @time 2016年1月3日 下午1:39:28
 */
public class Sort { 

    public static void display(int[] a) { 
        for(int i = 0; i < a.length; i++){
            System.out.print(a[i]+" ");
        } 
        System.out.println();
    }


    /**
     * @description 不帶監視哨的直接插入排序算法
     * 
     * @time 2016年1月3日 下午1:39:56
     */
    public static int[] insertSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length); 
        int temp, i, j;
        // 循環n-1次  從下標 1 到 n-1
        for(i = 1; i < a.length; i++){
            temp = a[i];
            // 從i-1逆序比較,若temp<元素,則此元素後移(temp≥元素爲判斷條件)
            for(j = i - 1;j >= 0 && temp < a[j]; j--){ 
                a[j + 1] = a[j];
            }
            a[j + 1] = temp; // eg:若temp比前面元素都小,到此代碼處時j=-1了
        }   
        return a;
    }

    /**
     * @description 帶有監視哨的插入排序算法 
     * @time 2016年1月3日 下午2:10:34
     */
    public static int[] insertSortWithGuard(int[] before) {   
        int[] a= Arrays.copyOf(before, before.length); 
        int i, j;
        // 循環n-1次  從下標 1 到 n-1
        for(i = 1; i < a.length; i++){
            // 將待插入的第i條元素暫存在a[0]中,同時a[0]設置爲監視哨
            a[0] = a[i];
            // 從i-1逆序比較,若temp<元素,則此元素後移(temp≥元素爲判斷條件)
            for(j = i - 1;a[0] < a[j]; j--){ 
                a[j + 1] = a[j];
            }
            a[j + 1] = a[0]; // eg:若temp比前面元素都小,到此代碼處時j=-1了
        }   
        return a;
    }

    /**
     * @description 希爾排序算法 
     * @param d d[]爲增量數組
     * @param before 需要排序的元素
     * @return
     * @time 2016年1月3日 下午3:01:42
     */
    public static int[] shellSort(int[] d, int[] before){
        int[] a= Arrays.copyOf(before, before.length); 
        int temp, i, j;
        //控制增量,增量減半,若干趟掃描
        for(int k = 0; k < d.length; k++){          
            int dk = d[k];
            //一趟中若干個子表,每個元素在自己所屬的子表內進行直接插入排序  以下是直接插入排序,只不過增量不一定是1
            for(i = dk; i < a.length; i++){
                temp = a[i];
                for(j = i - dk; j >= 0 && temp < a[j]; j -= dk){
                    a[j + dk] = a[j]; 
                }
                a[j + dk] = temp;
            }
        }
        return a;       
    }

    /**
     * @description 冒泡排序算法 
     * @param before
     * @return
     * @time 2016年1月3日 下午9:06:50
     */
    public static int[] bubbleSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length); 
        boolean flag = true;
        for(int i = 1; i < a.length && flag; i++){
            flag = false; //記錄未交換
            for(int j = 0; j < a.length - 1; j++){
                if(a[j] > a[j + 1]){
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = true;
                }
            }           
        }
        return a;
    }

    /**
     * @description 這裏說明一下,爲了進入方法的數組before不被改變,所以就先賦值給a,
     *              然後再調用快速排序算法的代碼改變了數組a,因爲用到了遞歸,所有不能跟前面的代碼相同處理             
     * @return
     * @time 2016年1月4日 上午1:03:21
     */
    public static int[] qSort(int[] before) {
        int[] a= Arrays.copyOf(before, before.length);  
        qSort(a, 0, a.length - 1);
        return a;
    }

    /**
     * @description 快速排序算法
     * @param a
     * @param low
     * @param high
     * @time 2016年1月4日 上午1:28:34
     */
    private static void qSort(int[] a, int low, int high) {
        int i = low, j = high;
        if(i < j){
            int pivot = a[i]; //設定支點的值,初始位置是low
            int pivotLoc;  // 一趟排序後支點的位置

            // 一趟快排的算法,此時在支點前的數據≤支點,支點後的數據≥支點
            while(i < j){
                //high爲起點下標 從後往前和pivot支點比較
                while(i < j && pivot <= a[j]){
                    j --;
                }
                if(i < j){ //說明上面跳出循環是因爲有個high使得pivot > a[high]了
                    a[i] = a[j];
                    i++;
                }
                //low爲起點下標 從前往後和pivot支點比較
                while(i < j && pivot > a[i]){
                    i++;
                }
                if(i < j){ 
                    a[j] = a[i];
                    j--;
                }
            }
            a[i] = pivot; //此時low和high相等 
            pivotLoc = i;
            qSort(a, low, pivotLoc - 1);
            qSort(a, pivotLoc + 1, high);
        }       
    }

    /**
     * @description 直接選擇排序算法 
     * @return
     * @time 2016年1月5日 上午1:28:19
     */
    public static int[] selectSort(int[] before){
        int[] a = Arrays.copyOf(before, before.length);
        for(int i = 0; i < a.length - 1; i++){
            // 每趟從a[i]開始的自序列中尋找最小關鍵字值的元素下標
            int min = i;
            for(int j = i + 1; j < a.length; j++){
                if(a[j] < a[min]){
                    min = j;
                }
            }
            if(min != i){
                int temp = a[i];
                a[i] = a[min];
                a[min] = temp;
            }
        }
        return a;
    }

    /**
     * @description 樹形選擇排序算法,構造勝者樹的過程,取根結點是最小關鍵值 
     * @return
     * @time 2016年1月5日 上午1:28:19
     */
    public static int[] tournamentSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length);
        TreeNode[] tree; //勝者樹結點數組
        int leafSize = 1; //勝者樹葉子結點數
        //得到葉子結點的個數,該個數必須是2的次冪
        while(leafSize < a.length){
            leafSize *= 2;
        }
        int TreeSize =  2 * leafSize - 1; //勝者樹的所有結點數
        int loadIndex = leafSize - 1; //葉子結點存放位置的起始位置
        tree = new TreeNode[TreeSize];

        int j = 0;
        //把待排序結點複製到勝者樹的葉子結點中
        for(int i = loadIndex; i < TreeSize; i++){
            tree[i] = new TreeNode();
            tree[i].setIndex(i);
            if(j < a.length){
                tree[i].setActive(1);
                tree[i].setData(a[j++]);
            }
            else{
                tree[i].setActive(0);
            }
        }
        int i = loadIndex;  //進行初始化,比較查找關鍵字值最小的結點
        while(i > 0){
            j = i;
            //處理各對比賽者
            while(j < TreeSize - 1){
                if(tree[j + 1].getActive() == 0 || (tree[j].getData() <= tree[j + 1].getData())){
                    tree[(j - 1)/2] = tree[j];  //左孩子勝出
                }
                else{ 
                    tree[(j - 1)/2] = tree[j + 1]; //右孩子勝出
                }
                j += 2; //下一對比賽者 
            }
            i = (i - 1)/2; //處理上層結點,類似下一輪比賽(已經決出一輪了)
        }

        //處理剩餘的n-1個元素
        for(i = 0; i < a.length - 1; i++){
            a[i] = tree[0].getData(); //將勝者樹的根(最小值)存入數組
            tree[tree[0].getIndex()].setActive(0); //冠軍不再參加比賽
            updateTree(tree, tree[0].getIndex()); //調整有勝者樹
        }
        //最後一個元素只需賦值就結束了 不需要再調整(即再進行下一輪比賽)
        a[a.length - 1] = tree[0].getData(); 
        return a;
    }

    /**
     * @description 樹形選擇排序的調整算法
     *              從當前最小關鍵字的葉子結點開始到根結點路徑上的所有結點關鍵字的修改
     * @param tree
     * @param i i是當前最小關鍵字的下標 
     * @author liuquan
     * @date  2016年1月5日
     */
    private static void updateTree(TreeNode[] tree, int i){
        //因爲i是此時最小的關鍵字(已是冠軍),所以在葉子結點中要將其除去比賽資格,對手直接晉級(升爲父結點)
        if(i % 2 == 0){ //i爲偶數,自己是右結點,對手是左結點,左結點晉級
            tree[(i - 1)/2] = tree[i - 1];          
        }
        else{
            tree[(i - 1)/2] = tree[i + 1];
        }
        i = (i - 1) / 2;

        int j = 0;
        while(i > 0){
            if(i % 2 == 0){ //i爲偶數,自己是右結點,對手是左結點 
                j = i - 1;
            }
            else{
                j = i + 1;
            }

            //比賽對手中有一個爲空
            if(tree[i].getActive() == 0 || tree[j].getActive() == 0){
                if(tree[i].getActive() == 1){
                    tree[(i - 1) / 2] = tree[i];
                }
                else{
                    tree[(i - 1) / 2] = tree[j];
                }
            }

            //比賽對手都在
            if(tree[i].getData() < tree[j].getData()){
                tree[(i - 1) / 2] = tree[i];
            }
            else{
                tree[(i - 1) / 2] = tree[j];
            }

            i = (i - 1) / 2;     
        }
    }

    /**
     * @description 樹形選擇排序的勝者樹結點結構
     * 
     * @author liuquan
     * @date  2016年1月5日
     */
    private static class TreeNode{
        private int data; //數據域
        private int index; //待插入結點在滿二叉樹中的序號
        private int active; //參加選擇標誌,1表示參選,0表示不參選
        public int getData() {
            return data;
        }
        public void setData(int data) {
            this.data = data;
        }
        public int getIndex() {
            return index;
        }
        public void setIndex(int index) {
            this.index = index;
        }
        public int getActive() {
            return active;
        }
        public void setActive(int active) {
            this.active = active;
        }       
    }

    /**
     * @description 堆排序算法
     * @return
     * @author liuquan
     * @date  2016年1月5日
     */
    public static int[] heapSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length);
        int n = a.length;
        int temp;
        for(int i = n / 2 - 1; i >= 0; i--){ //創建初始堆
            sift(i, n, a);
        }
        for(int i = n - 1; i > 0; i--){ //每趟將最小關鍵字值交換到後面,再調整成堆
            temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            sift(0, i, a);
        }
        return a;
    }

    /**
     * @description 篩選法調整堆算法 ,以low爲根結點的子樹調整成小頂堆
     * @param low
     * @param high
     * @author liuquan
     * @date  2016年1月5日
     */
    private static void sift(int low, int high, int[] a){
        int i = low; //子樹的根結點
        int j = 2 * i + 1;
        int temp = a[i];
        while(j < high){
            //判斷條件j < high - 1 表示有右結點,即j+1 < high
            if(j < high - 1 && a[j] > a[j + 1])
                j++;

            if(temp > a[j]){
                a[i] = a[j]; //孩子結點中的較小值上移
                i = j;
                j = 2 * i + 1;
            }
            else
                j = high + 1;
        }
        a[i] = temp;
    }


    /**
     * @description 2-路歸併排序算法  歸併過程中引入數組temp[],第一趟由a歸併到temp,第二趟由temp歸併到a,如此反覆直到n個記錄爲一個有序表
     *              返回的是a[]。不論是偶數趟還是奇數趟,最後都會mergepas(temp, a, s, n); 數據都會在a中
     * @return
     * @author liuquan
     * @date  2016年1月5日
     */
    public static int[] mergeSort(int[] before){
        int[] a= Arrays.copyOf(before, before.length);
        int s = 1; //s爲已排序的子序列長度
        int n = a.length;
        int[] temp = new int[n];
        while(s < n){
            mergepas(a, temp, s, n);
            s *= 2;
            mergepas(temp, a, s, n);
            s *= 2;
        }
        return a;
    }

    /**
     * @description 一趟歸併排序的算法
     * @param a
     * @param b
     * @param s s是待歸併的有序子序列的長度
     * @param n n是待排序序列的長度
     * @author liuquan
     * @date  2016年1月5日
     */
    private static void mergepas(int[] a, int[] b, int s, int n){
        int p = 0;  //p爲每一對待合併表的第一個元素的下標
        //首先兩兩歸併長度均爲s的有序表
        while(p + 2 * s - 1 <= n - 1){
            merge(a, b, p, p + s - 1, p + 2 * s -1);
            p += 2 * s;
        }
        //歸併最後兩個長度不相等的有序表
        if(p * s - 1 < n - 1){
            merge(a, b, p, p + s - 1, n - 1);
        }
        else{ //只剩餘一個有序表了,直接複製到b中
            for(int i = p; i <= n - 1; i++){
                b[i] = a[i];
            }
        }
    }


    /**
     * @description 把兩個相鄰的有序表a[h……m]和a[m+1……t]歸併成一個有序表 b[h……t] 
     * @author liuquan
     * @date  2016年1月5日
     */
    private static void merge(int[] a, int[] b, int h, int m, int t){
        int i = h, j = m +1, k = h;
        //將a中兩個相鄰子序列歸併到b中
        while(i <= m && j <= t){
            if(a[i] <= a[j]){
                b[k++] = a[i++];
            }
            else{
                b[k++] = a[j++];
            }
        }
        //將剩餘子序列複製到b中
        while(i <= m){
            b[k++] = a[i++];
        }
        while(j <= t){
            b[k++] = a[j++];
        }
    }

}

Test

 package Sort;

public class Test { 

    public static void main(String[] args) {
        int[] a = new int[]{52, 39, 67, 95, 70, 8, 25, 52};
        int[] b; 

        System.out.println("排序前的序列:");
        Sort.display(a);

        System.out.println("不帶監視哨的插入排序算法");
        b = Sort.insertSort(a);
        Sort.display(b);        

        System.out.println("帶有監視哨的插入排序算法(第一個元素不算,作爲監視哨)");
        b = Sort.insertSortWithGuard(a);
        Sort.display(b);

        System.out.println("希爾排序算法");
        int[] d = new int[]{5, 3, 1};
        b = Sort.shellSort(d, a);
        Sort.display(b);

        System.out.println("冒泡排序算法");
        b = Sort.bubbleSort(a);
        Sort.display(b);

        System.out.println("快速排序算法");
        b = Sort.qSort(a);
        Sort.display(b);

        System.out.println("直接選擇排序算法");
        b = Sort.selectSort(a);
        Sort.display(b);

        System.out.println("樹形選擇排序算法");
        b = Sort.tournamentSort(a);
        Sort.display(b);

        System.out.println("小頂堆排序算法,由於每趟都是把最小關鍵字放到了數組末尾,所以輸出數組的時候是倒序的");
        b = Sort.heapSort(a);
        Sort.display(b);

        System.out.println("2-路歸併排序算法");
        b = Sort.mergeSort(a);
        Sort.display(b);         

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