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


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

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

package 八大排序算法;

import java.util.Arrays;

import org.junit.Test;

/**
 * 1、插入排序     直接插入排序、希爾排序             折半插入排序 
 * 2、交換排序     冒泡排序、快速排序 
 * 3、選擇排序     直接選擇排序、堆排序 
 * 4、歸並排序 
 * 5、分配排序    基數排序                 箱排序
 * 
 * 
 * 
 * 八大排序算法。
 * 
 * 
 * @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};

    /**
     * * ==============================================================
     * 1、插入排序之->直接插入排序 
     * 特點: 
     * 1、穩定排序
     * 2、算法簡便,且容易實現 3、可適用於鏈式存儲結構
     * 4、更適合於初始記錄基本有序(正序),當初始記錄無序,n較大時,此算法時間復雜度較高,不宜采用。
     * ================================================================
     */
    @Test
    public void IInsertSort() {
        System.out.println("1、插入排序之->直接插入排序");
        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);
    }

    /**
     * * ====================================== 1、插入排序之->折半插入排序 特點: 1、穩定排序
     * 2、因為要進行折半查找,所以只能用於順序結構,不能用於鏈式結構 3、適合初始記錄無序,且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("1、插入排序之->折半插入排序");
        print(a);
    }

    /**
     * 
     * ====================================== 
     * 1、插入排序之->希爾排序方法一 
     * 特點:
     * 1、記錄跳躍式移動導致排序方法是不穩定的 
     * 2、只能用於順序結構,不能用於鏈式結構
     * 3、綜合來說,n越大,效果越明顯,所以適合初始記錄無序,n較大時的情況~。
     * ======================================
     */
    @Test
    public void shellInsertSort() {
        System.out.println("1、插入排序之->希爾排序");
        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);
    }

    /**
     * 
     * ====================================== 
     * 1、插入排序之->希爾排序 方法二
     * 
     * 方法二修改方法一的存儲元素的方案,和插入排序前兩個一樣,采用a[0]來存儲。
     * ======================================
     */
    @Test
    public void shellInsertSort2() {
        System.out.println("1、插入排序之->希爾排序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);
    }

    /**
     * 
     * ====================================== 
     * 2、交換排序->冒泡排序
     * 冒泡排序最為一種經典的排序算法,是我們應該隨后都能寫出來的。 
     * 特點: 1、穩定排序 
     * 2、可用於鏈式存儲結構
     * 3、移動記錄次數較多,算法平均時間性能比直接插入排序查。 
     * 4、當記錄無序,n較大時,此算法不宜采用。
     * 
     * ======================================
     */
    @Test
    public void BulleSort() {
        System.out.println("2、交換排序->冒泡排序");
        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);
    }

    /**
     * 
     * ====================================== 
     * 2、交換排序->快速排序 
     * 特點: 1、屬於不穩定排序
     * 2、排序過程中需要確定上界和下屆,所以只能用於順序結構,很難用於鏈式
     * 3、在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;
    }
    // 3、選擇排序 直接選擇排序、堆排序、 樹形排序

    /**
     * 
     * ====================================== 
     * 3、選擇排序->直接選擇排序 
     * 特點: 
     * 1、是一種穩定的排序算法。
     * 2、可用於鏈式存儲結構 ======================================
     */
    @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);
    }

    /**
     * 
     * ====================================== 
     * 3、選擇排序->堆排序 
     * 特點:
     * 1、不穩定排序
     * 2、只能用於順序存儲結構
     *  ======================================
     */
    @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;
    }

    /**
     * 
     * ====================================== 
     * 4、歸並排序 
     * 特點:
     * 1、屬於穩定排序
     * 2、可用於鏈式存儲
     * ======================================
     */
    @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);
            }
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM