Java實現八種排序算法(代碼詳細解釋)

通過一個多星期的學習、收集、整理,又對數據結構的八大排序算法進行了一個回顧,在測試過程當中也遇到了不少問題,解決了不少問題。代碼全都是通過小弟運行的,若是有問題,但願能給小弟提出來,共同進步。java

參考:數據結構(c語言版 第2版)、小甲魚數據結構視頻。算法

package 八大排序算法;

import java.util.Arrays;

import org.junit.Test;

/**
 * 一、插入排序     直接插入排序、希爾排序             折半插入排序 
 * 二、交換排序     冒泡排序、快速排序 
 * 三、選擇排序     直接選擇排序、堆排序 
 * 四、歸併排序 
 * 五、分配排序    基數排序                 箱排序
 * 
 * 
 * 
 * 八大排序算法。
 * 
 * 
 * @author 劉陽陽
 *
 *         2017年2月25日
 */
public class InsertSort {

    // 此類用到的排序數組
    int[] a = { 0, 9, 5, 6, 12, 31, 23, 15, 100 };
    // int[] b = {0,9,5,6,12,31,23,15,100};

    /**
     * * ==============================================================
     * 一、插入排序之->直接插入排序 
     * 特色: 
     * 一、穩定排序
     * 二、算法簡便,且容易實現 三、可適用於鏈式存儲結構
     * 四、更適合於初始記錄基本有序(正序),當初始記錄無序,n較大時,此算法時間複雜度較高,不宜採用。
     * ================================================================
     */
    @Test
    public void IInsertSort() {
        System.out.println("一、插入排序之->直接插入排序");
        int j = 0;

        for (int i = 2; i < a.length; i++) { // 此處i=2,是由於直接插入排序默認1爲拍好的序列,i!=0
                                                // 是由於預留0的空間來暫存每次的結果
            if (a[i] < a[i - 1]) {// 當a[i] < a[i-1]時才須要操做,不然由於原本就是好的序列,直接跳過
                a[0] = a[i];
                a[i] = a[i - 1];
                // 開始挪窩
                for (j = i - 2; a[0] < a[j]; j--) { // j=i-2 爲何-2
                                                    // 是由於-2以後當前就是從後往前遍歷了,-》a[i]
                                                    // = a[i-1] 與 j=i-2 是本題一個關鍵
                    a[j + 1] = a[j];
                }
                a[j + 1] = a[0];
            }
        }
        print(a);
    }

    /**
     * * ====================================== 一、插入排序之->折半插入排序 特色: 一、穩定排序
     * 二、由於要進行折半查找,因此只能用於順序結構,不能用於鏈式結構 三、適合初始記錄無序,且n較大的狀況
     * ======================================
     */
    @Test
    public void BInsertSort() {
        int low;
        int height;
        int j;
        int m;

        for (int i = 2; i < a.length; i++) { // i=2,表明從2開始
            a[0] = a[i];// 先把當前值存到a[0]
            low = 1;
            height = i - 1; // 給low和height賦值,low從1開始,height從比 當前值i小1,開始
            while (low <= height) { // 一直循環的條件是 low<=height,在這以後的第3行的 height =
                                    // m-1,能夠改變結束循環的條件。
                m = (low + height) / 2; // 首先算出來m;
                if (a[0] < a[m]) { // 若是a[0]<a[m] 表明a[0]比中間的值小 說明插入點應該在前半段,
                    height = m - 1; // 全部把中間m-1點複製給height,爲什麼是m-1,由於m已經用過了,全部是m-1;
                } else {
                    low = m + 1; // 不然表明 插入點後半段,則把m+1複製給low,來繼續去搜索後半段
                }
            }
            // 開始挪窩
            for (j = i - 1; j >= height + 1; j--) { // 和直接插入排序相比,此處j=j-1
                                                    // 仍是從後往前遍歷
                a[j + 1] = a[j]; // 把當前j給j+1
            }
            a[j + 1] = a[0]; // 最後把a[0]給j+1,由於j是全局變量,因此j的值會隨着開始挪窩循環的變化減少,直至見到最佳
        }
        System.out.println("一、插入排序之->折半插入排序");
        print(a);
    }

    /**
     * 
     * ====================================== 
     * 一、插入排序之->希爾排序方法一 
     * 特色:
     * 一、記錄跳躍式移動致使排序方法是不穩定的 
     * 二、只能用於順序結構,不能用於鏈式結構
     * 三、綜合來講,n越大,效果越明顯,因此適合初始記錄無序,n較大時的狀況~。
     * ======================================
     */
    @Test
    public void shellInsertSort() {
        System.out.println("一、插入排序之->希爾排序");
        int[] a = { 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 };
        System.out.println("最初結果");
        printXiEr(a);

        int j = 0;
        int temp; // 初始化兩個值
                    // j時爲了第二層循環,temp爲了存儲當前值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲
        for (int gap = a.length / 2; gap > 0; gap /= 2) {// 本次循環的是增量的值 5 2 1
            System.out.println("本次循環增量爲" + gap);
            for (int i = gap; i < a.length; i++) {// 記錄每次的變化,i=gap
                                                    // 至關於第一遍先拿a[5]也就是13 來進行
                temp = a[i]; // temp存儲當前的值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲

                for (j = i - gap; j >= 0; j -= gap) { // 本處循環是最重要的循環,也就是移動位置的循環
                                                        // j=i-gap,第一遍j就等於0
                                                        // 也就是a【0】=26
                    if (temp < a[j]) { // temp = a[5] = 13
                                        // ,temp確定是小於13的,因此執行下邊語句
                        a[j + gap] = a[j]; // 將a[j] = 26 放到 j+gap的位置,也就是 0+5
                                            // a[5]的位置
                    } else { // 不然跳過本層循環,記錄執行 i=gap的循環
                        break;
                    }
                }

                a[j + gap] = temp; // 最後把temp的值還原
            }
            printXiEr(a);
        }

        // 輸出結果
        System.out.println("最終結果:");
        printXiEr(a);
    }

    /**
     * 
     * ====================================== 
     * 一、插入排序之->希爾排序 方法二
     * 
     * 方法二修改方法一的存儲元素的方案,和插入排序前兩個同樣,採用a[0]來存儲。
     * ======================================
     */
    @Test
    public void shellInsertSort2() {
        System.out.println("一、插入排序之->希爾排序2");
        int[] a = { 0, 26, 53, 67, 48, 57, 13, 48, 32, 60, 50 };
        System.out.println("最初結果");
        print(a);
        System.out.println();
        int j = 0;
        // int temp; //初始化兩個值
        // j時爲了第二層循環,temp爲了存儲當前值,與其餘兩種插入排序不一樣的時,本處使用temp,上面兩處使用數組的第0個位置存儲
        for (int gap = a.length / 2; gap > 0; gap /= 2) {// 本次循環的是增量的值 5 2 1
            System.out.println("本次循環增量爲" + gap);
            for (int i = gap; i < a.length; i++) {// 記錄每次的變化,i=gap
                                                    // 至關於第一遍先拿a[5]也就是13 來進行
                a[0] = a[i]; // a[0]存儲當前的值

                for (j = i - gap; j > 0; j -= gap) { // 本處循環是最重要的循環,也就是移動位置的循環
                                                        // j=i-gap,第一遍j就等於0
                                                        // 也就是a【0】=26 注意此處改成>0
                    if (a[0] < a[j]) { // temp = a[5] = 13
                                        // ,temp確定是小於13的,因此執行下邊語句
                        a[j + gap] = a[j]; // 將a[j] = 26 放到 j+gap的位置,也就是 0+5
                                            // a[5]的位置
                    } else { // 不然跳過本層循環,記錄執行 i=gap的循環
                        break;
                    }
                }

                a[j + gap] = a[0]; // 最後把a[0]的值還原
            }
            printXiEr(a);
        }

        // 輸出結果
        System.out.println("最終結果:");
        print(a);
    }

    /**
     * 
     * ====================================== 
     * 二、交換排序->冒泡排序
     * 冒泡排序最爲一種經典的排序算法,是咱們應該隨後都能寫出來的。 
     * 特色: 一、穩定排序 
     * 二、可用於鏈式存儲結構
     * 三、移動記錄次數較多,算法平均時間性能比直接插入排序查。 
     * 四、當記錄無序,n較大時,此算法不宜採用。
     * 
     * ======================================
     */
    @Test
    public void BulleSort() {
        System.out.println("二、交換排序->冒泡排序");
        printXiEr(a);
        for (int i = 0; i < a.length - 1; i++) { // 採用第一層循環來控制循環的次數,一共循環a.length-1次
                                                    // 這樣會循環到倒數第二個元素
            for (int j = i + 1; j < a.length; j++) {// 第二層循環來交換位置,j在i的基礎上+1是由於當前的值要和他身後的元素比較大小,直至最後一個。(
                                                    // 由於第二次循環直至最後一個因此第一層循環纔會a.length-1)
                if (a[i] > a[j]) {
                    int temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                }
            }
        }
        printXiEr(a);
    }

    /**
     * 
     * ====================================== 
     * 二、交換排序->快速排序 
     * 特色: 一、屬於不穩定排序
     * 二、排序過程當中須要肯定上界和下屆,因此只能用於順序結構,很難用於鏈式
     * 三、在n較大時,在平均狀況下快速排序是全部內部排序中速度最快的一種,因此適合初始記錄無序,n較大的狀況
     * ======================================
     */
    @Test
    public void QuickSort() {
        System.out.println("待排序序列");
        print(a);
        Qsort(a, 1, a.length - 1);
        System.out.println();
        print(a);
    }

    private void Qsort(int[] a, int low, int height) {
        if (low < height) {
            int point;
            point = Partition(a, low, height);// 查找中間點
            Qsort(a, low, point - 1);// 遞歸排序左邊
            Qsort(a, point + 1, height);// 遞歸排序右邊
        }
    }

    private int Partition(int[] a, int low, int height) {
        a[0] = a[low];// 將中間點保存在a[0]這個位置中。
        int point = a[low];// 把中間點保存在point中
        while (low < height) {// 循環條件是隻要low<height
            // 如下兩個while就是核心之處
            while (low < height && a[height] >= point) {// low<height就不說了,原本是a[height]<poin就移動,
                                                        // 如今改變一下
                                                        // 當》=的時候就--;
                height--;
            }
            a[low] = a[height]; // 循環結束後,交換位置,把右邊小的,交換到中間點的左邊
            while (low < height && a[low] <= point) { // 相反同上, 原本是a[low]>=point
                                                        // 改變寫法 改爲a<=point就跳過++;
                low++;
            }
            a[height] = a[low];
        }
        a[low] = a[0];
        return low;
    }
    // 三、選擇排序 直接選擇排序、堆排序、 樹形排序

    /**
     * 
     * ====================================== 
     * 三、選擇排序->直接選擇排序 
     * 特色: 
     * 一、是一種穩定的排序算法。
     * 二、可用於鏈式存儲結構 ======================================
     */
    @Test
    public void selectSort() {
        System.out.println("待排序序列");
        print(a);
        for (int i = 1; i < a.length; i++) {
            int k = i;
            for (int j = i + 1; j < a.length; j++) {
                if (a[k] > a[j]) {
                    k = j;
                }
            }
            if (k != i) {
                int temp = a[i];
                a[i] = a[k];
                a[k] = temp;
            }
        }
        System.out.println();
        print(a);
    }

    /**
     * 
     * ====================================== 
     * 三、選擇排序->堆排序 
     * 特色:
     * 一、不穩定排序
     * 二、只能用於順序存儲結構
     *  ======================================
     */
    @Test
    public void HeapSort() {
        CreateHeap();// 首先須要建立根堆
        for (int i = a.length - 1; i > 1; --i) {// 這個循環是把最大值a[1]放到末尾 ,
            int temp = a[1];
            a[1] = a[i]; // 此時i表明最後一個元素
            a[i] = temp;
            HeapAdjust(a, 1, i - 1);// 調整一次後繼續,把數組,第一個位置,和最後一個位置 此處爲什麼i-1,
                                    // 是由於循環已經把最大值放到最後一個了,因此下次就方法最後一個-1的位置
        }
        print(a);
    }

    private void CreateHeap() {
        int n = a.length - 1; // 獲得數組的最大值
        for (int i = n / 2; i > 0; i--) { // 從最後一個非終端結點開始,而後一次--
            HeapAdjust(a, i, n);
        }
    }

    // 調整堆
    private void HeapAdjust(int[] a, int s, int m) {// s表明當前 m表明最後
        int temp = a[s]; // 先把a[s]的值賦給temp保存起來
        for (int j = 2 * s; j <= m; j *= 2) {
            if (j < m && a[j] < a[j + 1]) { // 判斷是s大仍是s+1大,若是s+1大 就++j,把j換成當前最大
                ++j;
            }
            if (temp >= a[j]) { // 若是temp中比最大值還大,表明自己就是一個根堆,break
                break;// 若是大於,就表明當前爲大跟對,退出
            }
            a[s] = a[j];// 不然就把最大給[s]
            s = j;// 而後把最大下標給s,繼續循環,檢查是否由於調整根堆而破壞了子樹
        }
        a[s] = temp;
    }

    /**
     * 
     * ====================================== 
     * 四、歸併排序 
     * 特色:
     * 一、屬於穩定排序
     * 二、可用於鏈式存儲
     * ======================================
     */
    @Test
    public void MSortM1() {
        print(a);
        System.out.println();
        Msort(a, 1, a.length - 1);
        print(a);
    }

    private void Msort(int[] nums, int low, int hight) {
        int mid = (low + hight) / 2; // 求得中間點
        if (low < hight) { // 判斷條件 若是low<hight就繼續進行
            Msort(nums, low, mid); // 遞歸左
            Msort(nums, mid + 1, hight); // 遞歸又
            Merge(nums, low, mid, hight); // 最後合併
        }

    }

    private void Merge(int[] nums, int low, int mid, int hight) {
        int[] temp = new int[hight - low + 1]; // 臨時數組,存放本次排序的序列
        int i = low; // i表明作序列開頭 j表明又序列開頭 k的做用是針對temp數組使用的
        int j = mid + 1;
        int k = 0;
        while (i <= mid && j <= hight) { // while條件必須知足兩個&&條件,只要有一個不知足就退出
            // 本次循環的最後結果就是 左右兩個序列,其中一個序列徹底賦值給temp,而後結束
            if (nums[i] < nums[j]) { // 比較
                temp[k++] = nums[i++]; // 賦值
            } else {
                temp[k++] = nums[j++]; // 賦值
            }
        }
        // 如下兩個while是針對上面的while,由於上面的while最後結束的結果爲
        // 左右兩個序列,其中一個序列徹底賦值給temp,而後結束,這樣其中一個序列還有值沒有賦給temp
        // 若是是左序列 就對左序列進行賦值
        while (i <= mid) {
            temp[k++] = nums[i++];
        }
        // 若是是又序列 就對又序列進行賦值
        while (j <= hight) {
            temp[k++] = nums[j++];
        }
        // 最後把本次排好序的temp,按照合併前的其實位置開始,從新賦值給nums; 這種處理方法比數據結構書上給的demo處理方式要好。
        for (int m = 0; m < temp.length; m++) {
            nums[low + m] = temp[m];
        }
    }

    /**
     * 
     * 基數排序
     * 
     */
    @Test
    public void radixSortTest(){
        radixSort(a,4,3);
        print(a);
    }
    
    private static void radixSort(int[] array, int radix, int distance) {
        // radix,表明基數
        // distance表明排序元素的位數 也就是進行幾回 分配 收集,,由於待排序序列中最大值爲100   三位數 因此distance爲3
        int length = array.length;
        int[] temp = new int[length];// 用於暫存元素
        int[] count = new int[radix];// 用於統計基數內元素個數
        int divide = 1;

        for (int i = 0; i < distance; i++) {

            System.arraycopy(array, 0, temp, 0, length);
            Arrays.fill(count, 0);

            for (int j = 0; j < length; j++) {
                int tempKey = (temp[j] / divide) % radix;
                count[tempKey]++; // 基數選中計數
            }

            for (int j = 1; j < radix; j++) {
                count[j] = count[j] + count[j - 1];// 累計計數
            }
            for (int j = length - 1; j >= 0; j--) {
                int tempKey = (temp[j] / divide) % radix;
                count[tempKey]--;// 從後往前賦值
                array[count[tempKey]] = temp[j];
            }
            divide = divide * radix;
        }
    }


    
    
    
    
    
    
    
    
    
    
    

    /**
     * 輔助方法,輸出當前數組的值。
     * 
     * @param temp
     *            接受傳過來的數組
     */
    void print(int[] temp) {
        System.out.println("排序結果爲:");
        for (int i = 1; i < temp.length; i++) {
            System.out.print(temp[i] + " ");
        }
    }

    /**
     * 第一種希爾排序的專用抽出方法
     * 
     * @param temp
     */
    void printXiEr(int[] temp) {
        for (int i = 0; i < temp.length; i++) {
            System.out.print(temp[i] + " ");
        }
        System.out.println();
    }

    /**
     * 小測試,測試return,break,continue的區別
     */
    @Test
    public void aaa() {
        for (int i = 1; i <= 3; i++) {
            for (int j = 1; j <= 3; j++) {
                if (i == 2) {
                    break;
                }
                System.out.println(i + " " + j);
            }
        }
    }
}
相關文章
相關標籤/搜索