7.排序算法

目錄

1.排序算法的介紹
2.排序的分類
3.冒泡排序
4.選擇排序
5.插入排序
6.希爾排序
7.快速排序
8.基數排序
9.堆排序
10.常用排序算法總結和對比

1.排序算法的介紹

排序也稱排序算法(Sort Algorithm),排序是將一組數據,依指定的順序進行排列的過程。

2.排序的分類

(1) 內部排序:指將需要處理的所有數據都加載到內部存儲器(內存)中進行排序。
(2) 外部排序:數據量過大,無法全部加載到內存中,需要藉助外部存儲(文件等)進行排序。
(3)常見的排序算法分類如下圖:
在這裏插入圖片描述
下述排序算法默認均將無序序列排序爲升序。

3.冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通過對 待排序 序列從前向後(從下標較小的元素開始),依次比較相鄰元素的值,若發現逆序則交換,使值較大的元素逐漸從前移向後部,就象水底下的氣泡一樣逐漸向上冒。
在這裏插入圖片描述

示例演示冒泡的過程:
在這裏插入圖片描述
小結上面的示例過程:

(1) 一共進行了序列的大小-1 趟排序;
(2) 每一趟排序比較的次數在逐漸的減少;
(3) (1)和(2)又可以總結爲,第一趟後最大的數被放到了序列最後一個位置,第二趟後第二大的數放到了倒數第二個位置,以此類推,直到最小的數無需比較,就在序列第一個位置,實現了升序排序;
(4) 實際在上述示例中,第二趟結束後序列則有序了,後續的幾趟只進行了比較,沒有進行交換。所以如果我們發現在某趟排序中,沒有發生一次交換,則表示 序列已經有序了,就可以提前結束冒泡排序,避免後續無效的冒泡。這樣可以實現對冒泡排序的優化。

代碼實現:

package sort;

import java.util.Arrays;

public class BubbleSort {
    
    public static void main(String[] args) {
        int[] array = {3, 9, -1, 10, 20};
        System.out.println("初始序列:"+ Arrays.toString(array));
        //冒泡排序的時間複雜度 O(n^2)
        for (int i = 0; i < array.length; i++) {
            boolean isExchange = false; //記錄該趟冒泡是否有做交換
            //依次交換逆序的相鄰兩個元素
            for (int j = 0; j < array.length-i-1; j++) {
                int temp = 0;
                //滿足相鄰元素逆序,則交換位置
                if (array[j]>array[j+1]){
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    isExchange = true;
                }
            }
            //該趟冒泡沒有做交換,則序列已經有序,無需做無用循環了
            if (!isExchange){ 
                break;
            }
            System.out.println("第"+(i+1)+"趟排序後的序列:"+ Arrays.toString(array));
        }
    }
}

4.選擇排序

選擇排序介紹:

選擇式排序也屬於內部排序法,是從欲排序的數據中,按指定的規則選出某一元素,再依規定交換位置後達到排序的目的。

選擇排序的思想:

選擇排序(select sorting)也是一種簡單的排序方法。它的基本思想是:第一次從 arr[0]~arr[n-1]中選取最小值, 與 arr[0]交換,第二次從 arr[1]~arr[n-1]中選取最小值,與 arr[1]交換,第三次從 arr[2]~arr[n-1]中選取最小值,與 arr[2] 交換,…,第 i 次從 arr[i-1]~arr[n-1]中選取最小值,與 arr[i-1]交換,…, 第 n-1 次從 arr[n-2]~arr[n-1]中選取最小值, 與 arr[n-2]交換,總共通過 n-1 次,得到一個按排序碼從小到大排列的有序序列。

示例演示選擇排序的過程:
在這裏插入圖片描述

說明:

  1. 選擇排序一共有 數組大小 - 1 輪排序;
  2. 每一輪排序中,又是一個循環, 循環的規則:
    2.1 先假定當前這個數是最小數;
    2.2 然後和後面的每個數進行比較,如果發現有比當前數更小的數,就重新確定最小數,並得到下標;
    2.3 當遍歷到數組的最後時,就得到本輪最小數和下標;
    2.4 交換。

代碼實現:

package sort;

import java.util.Arrays;

public class SelectSort {
    public static void main(String[] args) {
        int[] array = {101, 34 ,119 ,1};
        System.out.println("初始序列:"+ Arrays.toString(array));
        //選擇排序的時間複雜度 O(n^2)
        for (int i = 0; i < array.length-1; i++) {
            int min = array[i];
            int minIndex = i;
            // 查找當前第i個元素後面的元素是否有比它小的
            for (int j = i+1; j < array.length; j++) {
                if (array[j] < min){
                    min = array[j];
                    minIndex = j;
                }
            }
            //如果當前即爲最小值則不需要交換,否則將最小值交換到當前第i個元素的位置
            if (minIndex != i){
                array[minIndex] = array[i];
                array[i] = min;
            }
            System.out.println("第"+(i+1)+"輪選擇排序後的序列:"+ Arrays.toString(array));
        }
    }
}

5.插入排序

插入式排序介紹:

插入式排序屬於內部排序法,是對於欲排序的元素以插入的方式找尋該元素的適當位置,以達到排序的目的。

插入排序法思想:

插入排序(Insertion Sorting)的基本思想是:把n個待排序的元素看成爲一個有序表和一個無序表,開始時有序表中只包含一個元素,無序表中包含有n-1個元素,排序過程中每次從無序表中取出第一個元素,把它的排序碼依次與有序表元素的排序碼進行比較,將它插入到有序表中的適當位置,使之成爲新的有序表。(將待插入元素取出後,該元素的位置便空出來了,便於後續插入適當位置時其他元素後移)

示例演示選擇排序的過程:
在這裏插入圖片描述
代碼實現:

package sort;

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] array = {101, 34 ,119 ,1};
        System.out.println("初始序列:"+ Arrays.toString(array));
        //插入排序
        for (int i = 1; i < array.length; i++) {
            int insertVal = array[i];
            // 用待插入數與有序列表中元素從後往前比較,每比較一個如果比待插入數更大,則表示待插入數應該在比較數前面,將比較數後移,爲插入數讓出位置
            // 以此類推直到找到第一個比待插入數小的元素,表示應該插入到該元素後面,結束後移,插入帶插入數。
            for (int j = i; j >= 0; j--) {
                if (j==0 || insertVal>=array[j-1]){//若沒有比帶插入數小的數,即下標爲0時,則帶插入數爲最小數,直接插入到下標0處
                    array[j] = insertVal;
                    break;
                }
                array[j] = array[j-1];
            }
            System.out.println("插入第"+(i)+"個元素後的序列:"+ Arrays.toString(array));
        }
    }
}

6.希爾排序

在瞭解上述的簡單插入排序後,我們容易發現:當需要插入的數是較小的數時,後移的次數明顯增多,對效率有影響。 所以提出了希爾排序的方案,對簡單插入排序進行了優化。

希爾排序介紹:

希爾排序是希爾(Donald Shell)於1959年提出的一種排序算法。希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱爲縮小增量排序

希爾排序基本思想:

希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止。

希爾排序的示意圖:
在這裏插入圖片描述
在這裏插入圖片描述
代碼實現:

package sort;

import java.util.Arrays;

public class ShellSort {
    public static void main(String[] args) {
        int[] array = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
        System.out.println("初始序列:"+ Arrays.toString(array));
        int count = 0;
        //希爾排序
        for (int gap = array.length/2; gap > 0; gap /= 2) {//分組
            //各個分組中第一個元素爲插入排序時的初始有序序列,所以從gap開始,根據增量遍歷
            for (int i = gap; i < array.length; i++) {
                int insertVal = array[i];
                int j = 0;
                //從待插入元素前一個元素開始判斷,比待插入元素大則後移,爲待插入元素留出空間。
                // 直到找到比待插入數小的數,結束後移,插入到該數後面
                for (j = i - gap; j >=0 && insertVal < array[j] ; j -= gap) {
                    array[j+gap] = array[j];
                }
                array[j+gap] = insertVal;
            }

            /* 下面的實現方式更容易理解,但代碼沒那麼簡潔。因爲沒必要非要在單獨每個組中進行插入排序,
                可以直接在原序列中根據增量進項插入排序,同樣能實現對各個組分別進行插入排序
            //對gap個組分別進行直接插入排序
            for (int i = 0; i < gap; i++) {
                //注意每一組並不是連續的,是按照gap增量跳躍的
                for (int j = i + gap; j < array.length; j += gap) {
                    int insertVal = array[j];
                    for (int k = j; k >= 0 ; k -= gap) {
                        if (k==i || insertVal>=array[k-gap]){
                            array[k] = insertVal;
                            break;
                        }
                        array[k] = array[k-gap];
                    }
                }
            } */
            System.out.println("希爾排序第"+ (++count) +"次分組插入排序後的序列:"+ Arrays.toString(array));
        }

    }
}

7.快速排序

快速排序基本思想:

快速排序(Quicksort)是對冒泡排序的一種改進。基本思想是:通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

實現原理:

快速排序,說白了就是給基準數據找其正確索引位置的過程,基準數隨機指定。這樣基準數左邊都是小於它的數,基準數右邊都是大於它的數,然後分別再對左右兩部分進行上述操作,遞歸實現。

如何找基準數位置:

默認指定基準數爲序列第一個元素,取出放到pivot中。然後分別從序列的兩端掃描數據,設兩個掃描指針:left指向起始位置,right指向末尾,掃描過程中,小於基準數的統一放到序列左邊部分,大於基準數的放到右邊部分。由於取出了基準數,所以爲掃描時數據移動、交換提供了輔助空間,最後再將基準數放到這左右兩部分數的中間,即找到基準數的位置。
在這裏插入圖片描述
首先從右半部分開始,如果掃描到的值大於基準數據就讓right減1,表示該數在合適的半區,繼續掃描下一個值;如果發現有元素比該基準數據的值小(如上圖中18<=pivot),就將right位置的值賦值給left位置(此時left指向基準數取出後的空閒位置) ,實現將較小值放到左半區(放到左半區後,right的位置的空間又空閒出來了),結果如下:
在這裏插入圖片描述
然後開始從左半區向後掃描,如果掃描到的值小於基準數據就讓left加1,表示該數在合適的半區,繼續掃描下一個值;如果發現有元素大於基準數據的值(如上圖46=>pivot),就再將left位置的值賦值到right位置,指針移動並且數據移動後的結果如下:
在這裏插入圖片描述
然後再開始從右半部分開始掃描,以此類推:
在這裏插入圖片描述
直到 left=right 時結束循環,此時left或right的下標就是基準數據23在該數組中的正確索引位置,將之前取出保存的基準數放到該位置。如下圖所示:
在這裏插入圖片描述
這樣一遍走下來,就實現了找到基準數的位置。然後採用遞歸的方式分別對前半部分和後半部分用同樣的方式定基準數,分左右兩部分。當所有的前半部分和後半部分均有序時該數組就自然整體有序了。

代碼實現:

package sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] array = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
        System.out.println("初始序列:"+ Arrays.toString(array));

        //快速排序
        quickSort(array,0,array.length-1);
        System.out.println(Arrays.toString(array));
    }

    /**
     * 遞歸快速排序
     * @param array
     * @param left
     * @param right
     */
    public static void quickSort(int[] array, int left, int right){
        if (left < right){
            int index = getPivotIndex(array,left,right);
            //遞歸左右兩部分
            quickSort(array,left,index-1);
            quickSort(array,index+1,right);
        }
    }

    /**
     * 查找基準數下標
     * @param array
     * @param left
     * @param right
     * @return
     */
    public static int getPivotIndex(int[] array, int left, int right){
        int pivot = array[left];//基準數
        while (left < right){
            //從序列最右邊開始查找,如果比基準數大,其符合放在基準數右邊的要求,則將右下標往前移,直到在右邊發現一個比基準數小的數
            while (left < right && array[right] >= pivot){
                right--;
            }
            //將較小數交換到左邊去,基準數提前保存到pivot,所以預留出了一個空間,
            // 將右邊較小的數放到左邊去後,右邊的那個位置則空閒出來了,供左邊較大的數放過來
            array[left] = array[right];
            //同理,從左邊開始查找
            while (left < right && array[left] <= pivot){
                left++;
            }
            array[right] = array[left];
            //直到left=right時結束循環
        }
        // 跳出循環時left和right相等,此時的left或right就是基準數的正確索引位置
        array[left] = pivot;
        return left;
    }

}

7.歸併排序

歸併排序介紹:

歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略。分治法將問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之。

歸併排序基本思想示意圖:

在這裏插入圖片描述
說明:可以看到這種結構很像一棵完全二叉樹,本文的歸併排序我們採用遞歸去實現(也可採用迭代的方式去實現)。分階段可以理解爲就是遞歸拆分子序列的過程。

治階段,我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8],實現步驟(類似將兩個有序鏈表合併成一個新的有序鏈表的感覺):
在這裏插入圖片描述
在這裏插入圖片描述
可見合併時用到了一個輔助序列放置合併後的數據,然後再將包含合併後元素的輔助序列拷貝回原序列。

代碼實現:

package sort;

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        int[] array = { 8, 4, 5, 7, 1, 3, 6, 2 };
        int[] tempArray = new int[array.length];
        System.out.println("初始序列:"+ Arrays.toString(array));
        //快速排序
        mergeSort(array,0,array.length-1, tempArray);
        System.out.println(Arrays.toString(array));
    }

    /**
     * 歸併排序遞歸實現分和治
     * @param array
     * @param left
     * @param right
     * @param tempArray
     */
    public static void mergeSort(int[] array, int left, int right, int[] tempArray){
        if (left<right){
            int lEnd = (left+right)/2;
            //向左遞歸分解
            mergeSort(array, left, lEnd, tempArray);
            //向右遞歸分解
            mergeSort(array, lEnd+1, right, tempArray);
            //合併
            merge(array, left, lEnd, right, tempArray);
        }
    }

    /**
     * 遞歸排序合併(治)階段
     * @param array 排序的原始數組
     * @param left 左邊有序序列的初始索引
     * @param lEnd 邊有序序列的末尾索引
     * @param right 右邊有序序列的初始索引
     * @param tempArray 中轉的輔助數組
     */
    public static void merge(int[] array, int left, int lEnd, int right,int[] tempArray){
        int i = left;//左邊序列的當前下標,初始爲最左邊
        int j = lEnd+1;//右邊序列的當前下標,初始爲右邊序列最左邊
        int t = 0;//輔助序列的當前下標
        //將兩個有序序列合併爲一個新的有序序列
        while (i<=lEnd && j<=right){
            if (array[i] <= array[j]){
                tempArray[t] = array[i++];
            }else {
                tempArray[t] = array[j++];
            }
            t++;
        }
        //經過上面合併後,更長的序列直接插入到合併序列後面
        while (i<=lEnd){
            tempArray[t++] = array[i++];
        }
        while (j<=right){
            tempArray[t++] = array[j++];
        }
        //將合併在輔助數組中的序列拷貝回原數組
        t = 0;
        int arrayIndex = left;
        while (arrayIndex<=right){
            array[arrayIndex++] = tempArray[t++];
        }
    }
}

8.基數排序

基數排序(桶排序)介紹:

(1) 基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是通過鍵值的各個位的值,將要排序的元素分配至某些“桶”中,達到排序的作用;
(2) 基數排序法是屬於穩定性的排序,基數排序法是效率高的穩定性排序法;
(3) 基數排序(Radix Sort)是桶排序的擴展;
(4) 基數排序是1887年赫爾曼·何樂禮發明的。它的基本實現思路是:將整數按位數切割成不同的數字,然後按每個位數分別比較。

基數排序基本思想:

將所有待比較數值統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位(個位)開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後, 數列就變成一個有序序列。

圖解基數排序思路:

在這裏插入圖片描述
準備10個桶(10個一維數組),分別存放元素數位(個、十、百…)爲0~9的對應元素。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
最高數位放完並依次取出後,發現序列已經有序。

代碼實現:

package sort;

import java.util.Arrays;

public class RadixSort {

    public static void main(String[] args) {
        int[] array = {53, 3, 542, 748, 14, 214};
        System.out.println("初始序列:"+ Arrays.toString(array));
        //初始10個桶,並且每個桶第一個空間存放的數據爲桶中存放的數據個數,序列數據從下標1開始存放。
        int[][] buckets = new int[10][array.length+1];
        //首先找到序列中最大數的數位長度
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max){
                max = array[i];
            }
        }
        int maxLength = (""+max).length();
        //從個位開始對各個數位放入桶中,並依次取出排列成新序列
        for (int i = 0, n = 1; i < maxLength; i++ , n *= 10) {
            //取出每個元素的對應數位放入桶中
            for (int j = 0; j < array.length; j++) {
                int digitOfElement = array[j] / n % 10;
                buckets[digitOfElement][buckets[digitOfElement][0]+1] = array[j];
                buckets[digitOfElement][0] = buckets[digitOfElement][0]+1;
            }
            //按照10個桶的順序,分別取出裏面的所有元素放入到原數組,實現一輪重新排序
            int index = 0;//原數組當前下標
            for (int j = 0; j < buckets.length; j++) {
                for (int k = 0; k < buckets[j][0]; k++) {
                    array[index++] = buckets[j][k+1];
                }
                //取完該桶數據後將該桶的當前存放數量置零
                buckets[j][0] = 0;
            }
            System.out.println("第"+(i+1)+"輪放入桶中並依次取出排列後的序列爲:"+Arrays.toString(array));
        }
    }


}

基數排序的說明:

(1) 基數排序是對傳統桶排序的擴展,速度很快;
(2) 基數排序是經典的空間換時間的方式,佔用內存很大, 當對海量數據排序時,容易造成OutOfMemoryError ;
(3) 基數排序時穩定的。(注:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序後的序列中,r[i]仍在r[j]之前,則稱這種排序算法是穩定的;否則稱爲不穩定的);
(4) 有負數的數組,我們一般不用基數排序來進行排序, 如果非要支持負數,可以參考該如下方案:
方案一:將數組中的負數整數分開,使它們成爲正數,然後使用基數排序爲正值,然後反轉並附加排序後的非負數組。
方案二:將所有的數加一個正數,使得所有的數變爲正數進行基數排序;排序完之後再將所有數減去最初加的正數值。該方法還可以用來解決小數的情況,所有數乘以一個數後全部變成整數,排序完後再除以。

9.堆排序

堆排序用到二叉樹的相關知識,後續更新到二叉樹時再補充。

10.常用排序算法總結和對比

在這裏插入圖片描述
相關術語解釋:

(1) 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;
(2) 不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;
(3) 內排序:所有排序操作都在內存中完成;
(4) 外排序:由於數據太大,因此把數據放在磁盤中,而排序通過磁盤和內存的數據傳輸才能進行;
(5) 時間複雜度: 一個算法執行所耗費的時間。
(6) 空間複雜度:運行完一個程序所需內存的大小。
(7) n: 數據規模
(8) k: “桶”的個數
(9) In-place: 不佔用額外內存
(10) Out-place: 佔用額外內存

時間效率比較測試代碼:
在這裏插入圖片描述

package sort;

import java.util.Arrays;
import java.util.Collections;

public class SortUtils {
    /**
     * 以10萬個數據的數組爲例測試
     * 各種排序運行時間效率比較:冒泡排序 < 選擇排序 < 插入排序 < 希爾排序 < 快速排序 <= 歸併排序 < 基數排序
     * 冒泡排序思路最簡單,但是效率最低
     *
     * @param args
     */
    public static void main(String[] args) {
        int num = 100000;//序列大小
        //初始化序列
        int[] array = new int[num];
        for(int i =0; i < num;i++) {
            array[i] = (int)(Math.random() * num*10); //生成一個[0, num*10) 的數,增大10倍增加序列元素的可選範圍
        }
        //記錄算法運行的起始時間
        long startTime = 0, endTime = 0;
        //冒泡排序
        int[] tempArray = array.clone();
        System.out.println("冒泡排序進行中...");
        startTime = System.currentTimeMillis();
        bubbleSort(tempArray);
        endTime = System.currentTimeMillis();
        System.out.println("冒泡排序運行時間:"+ (endTime-startTime) + "毫秒");
        //選擇排序
        tempArray = array.clone();
        System.out.println("選擇排序進行中...");
        startTime = System.currentTimeMillis();
        selectSort(tempArray);
        endTime = System.currentTimeMillis();
        System.out.println("選擇排序運行時間:"+ (endTime-startTime) + "毫秒");
        //插入排序
        tempArray = array.clone();
        System.out.println("插入排序進行中...");
        startTime = System.currentTimeMillis();
        insertSort(tempArray);
        endTime = System.currentTimeMillis();
        System.out.println("插入排序運行時間:"+ (endTime-startTime) + "毫秒");
        //希爾排序
        tempArray = array.clone();
        System.out.println("希爾排序進行中...");
        startTime = System.currentTimeMillis();
        shellSort(tempArray);
        endTime = System.currentTimeMillis();
        System.out.println("希爾排序運行時間:"+ (endTime-startTime) + "毫秒");
        //快速排序
        tempArray = array.clone();
        System.out.println("快速排序進行中...");
        startTime = System.currentTimeMillis();
        quickSort(tempArray,0,tempArray.length-1);
        endTime = System.currentTimeMillis();
        System.out.println("快速排序運行時間:"+ (endTime-startTime) + "毫秒");
        //歸併排序
        tempArray = array.clone();
        int[] temp = new int[array.length];
        System.out.println("歸併排序進行中...");
        startTime = System.currentTimeMillis();
        mergeSort(tempArray,0,tempArray.length-1, temp);
        endTime = System.currentTimeMillis();
        System.out.println("歸併排序運行時間:"+ (endTime-startTime) + "毫秒");
        //基數排序
        tempArray = array.clone();
        System.out.println("基數排序進行中...");
        startTime = System.currentTimeMillis();
        radixSort(tempArray);
        endTime = System.currentTimeMillis();
        System.out.println("基數排序運行時間:"+ (endTime-startTime) + "毫秒");
    }

    /**
     * 冒泡排序的時間複雜度 O(n^2)
     * @param array
     */
    public static void bubbleSort(int[] array){
        for (int i = 0; i < array.length; i++) {
            boolean isExchange = false; //記錄該趟冒泡是否有做交換
            //依次交換逆序的相鄰兩個元素
            for (int j = 0; j < array.length-i-1; j++) {
                int temp = 0;
                //滿足相鄰元素逆序,則交換位置
                if (array[j]>array[j+1]){
                    temp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = temp;
                    isExchange = true;
                }
            }
            //該趟冒泡沒有做交換,則序列已經有序,無需做無用循環了
            if (!isExchange){
                break;
            }
        }
    }

    /**
     * 選擇排序的時間複雜度 O(n^2)
     * @param array
     */
    public static void selectSort(int[] array){
        for (int i = 0; i < array.length-1; i++) {
            int min = array[i];
            int minIndex = i;
            // 查找當前第i個元素後面的元素是否有比它小的
            for (int j = i+1; j < array.length; j++) {
                if (array[j] < min){
                    min = array[j];
                    minIndex = j;
                }
            }
            //如果當前即爲最小值則不需要交換,否則將最小值交換到當前第i個元素的位置
            if (minIndex != i){
                array[minIndex] = array[i];
                array[i] = min;
            }
        }
    }

    /**
     * 插入排序
     * @param array
     */
    public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int insertVal = array[i];
            // 用待插入數與有序列表中元素從後往前比較,每比較一個如果比待插入數更大,則表示待插入數應該在比較數前面,將比較數後移,爲插入數讓出位置
            // 以此類推直到找到第一個比待插入數小的元素,表示應該插入到該元素後面,結束後移,插入帶插入數。
            for (int j = i; j >= 0; j--) {
                if (j==0 || insertVal>=array[j-1]){//若沒有比帶插入數小的數,即下標爲0時,則帶插入數爲最小數,直接插入到下標0處
                    array[j] = insertVal;
                    break;
                }
                array[j] = array[j-1];
            }
        }
    }

    /**
     * 希爾排序
     * @param array
     */
    public static void shellSort(int[] array){
        for (int gap = array.length/2; gap > 0; gap /= 2) {//分組
            //各個分組中第一個元素爲插入排序時的初始有序序列,所以從gap開始,根據增量遍歷
            for (int i = gap; i < array.length; i++) {
                int insertVal = array[i];
                int j = 0;
                //從待插入元素前一個元素開始判斷,比待插入元素大則後移,爲待插入元素留出空間。
                // 直到找到比待插入數小的數,結束後移,插入到該數後面
                for (j = i - gap; j >=0 && insertVal < array[j] ; j -= gap) {
                    array[j+gap] = array[j];
                }
                array[j+gap] = insertVal;
            }

            /* 下面的實現方式更容易理解,但代碼沒那麼簡潔。因爲沒必要非要在單獨每個組中進行插入排序,
                可以直接在原序列中根據增量進項插入排序,同樣能實現對各個組分別進行插入排序
            //對gap個組分別進行直接插入排序
            for (int i = 0; i < gap; i++) {
                //注意每一組並不是連續的,是按照gap增量跳躍的
                for (int j = i + gap; j < array.length; j += gap) {
                    int insertVal = array[j];
                    for (int k = j; k >= 0 ; k -= gap) {
                        if (k==i || insertVal>=array[k-gap]){
                            array[k] = insertVal;
                            break;
                        }
                        array[k] = array[k-gap];
                    }
                }
            } */
        }
    }

    /**
     * 快速排序
     * @param array
     * @param left
     * @param right
     */
    public static void quickSort(int[] array, int left, int right){
        if (left < right){
            int index = getPivotIndex(array,left,right);
            //遞歸左右兩部分
            quickSort(array,left,index-1);
            quickSort(array,index+1,right);
        }
    }

    /**
     * 快速排序查找基準數下標
     * @param array
     * @param left
     * @param right
     * @return
     */
    public static int getPivotIndex(int[] array, int left, int right){
        int pivot = array[left];//基準數
        while (left < right){
            //從序列最右邊開始查找,如果比基準數大,其符合放在基準數右邊的要求,則將右下標往前移,直到在右邊發現一個比基準數小的數
            while (left < right && array[right] >= pivot){
                right--;
            }
            //將較小數交換到左邊去,基準數提前保存到pivot,所以預留出了一個空間,
            // 將右邊較小的數放到左邊去後,右邊的那個位置則空閒出來了,供左邊較大的數放過來
            array[left] = array[right];
            //同理,從左邊開始查找
            while (left < right && array[left] <= pivot){
                left++;
            }
            array[right] = array[left];
            //直到left=right時結束循環
        }
        // 跳出循環時left和right相等,此時的left或right就是基準數的正確索引位置
        array[left] = pivot;
        return left;
    }

    /**
     * 歸併排序遞歸實現分和治
     * @param array
     * @param left
     * @param right
     * @param tempArray
     */
    public static void mergeSort(int[] array, int left, int right, int[] tempArray){
        if (left<right){
            int lEnd = (left+right)/2;
            //向左遞歸分解
            mergeSort(array, left, lEnd, tempArray);
            //向右遞歸分解
            mergeSort(array, lEnd+1, right, tempArray);
            //合併
            merge(array, left, lEnd, right, tempArray);
        }
    }

    /**
     * 遞歸排序合併(治)階段
     * @param array 排序的原始數組
     * @param left 左邊有序序列的初始索引
     * @param lEnd 邊有序序列的末尾索引
     * @param right 右邊有序序列的初始索引
     * @param tempArray 中轉的輔助數組
     */
    public static void merge(int[] array, int left, int lEnd, int right,int[] tempArray){
        int i = left;//左邊序列的當前下標,初始爲最左邊
        int j = lEnd+1;//右邊序列的當前下標,初始爲右邊序列最左邊
        int t = 0;//輔助序列的當前下標
        //將兩個有序序列合併爲一個新的有序序列
        while (i<=lEnd && j<=right){
            if (array[i] <= array[j]){
                tempArray[t] = array[i++];
            }else {
                tempArray[t] = array[j++];
            }
            t++;
        }
        //經過上面合併後,更長的序列直接插入到合併序列後面
        while (i<=lEnd){
            tempArray[t++] = array[i++];
        }
        while (j<=right){
            tempArray[t++] = array[j++];
        }
        //將合併在輔助數組中的序列拷貝回原數組
        t = 0;
        int arrayIndex = left;
        while (arrayIndex<=right){
            array[arrayIndex++] = tempArray[t++];
        }
    }

    public static  void radixSort(int[] array){
        //初始10個桶,並且每個桶第一個空間存放的數據爲桶中存放的數據個數,序列數據從下標1開始存放。
        int[][] buckets = new int[10][array.length+1];
        //首先找到序列中最大數的數位長度
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max){
                max = array[i];
            }
        }
        int maxLength = (""+max).length();
        //從個位開始對各個數位放入桶中,並依次取出排列成新序列
        for (int i = 0, n = 1; i < maxLength; i++ , n *= 10) {
            //取出每個元素的對應數位放入桶中
            for (int j = 0; j < array.length; j++) {
                int digitOfElement = array[j] / n % 10;
                buckets[digitOfElement][buckets[digitOfElement][0]+1] = array[j];
                buckets[digitOfElement][0] = buckets[digitOfElement][0]+1;
            }
            //按照10個桶的順序,分別取出裏面的所有元素放入到原數組,實現一輪重新排序
            int index = 0;//原數組當前下標
            for (int j = 0; j < buckets.length; j++) {
                for (int k = 0; k < buckets[j][0]; k++) {
                    array[index++] = buckets[j][k+1];
                }
                //取完該桶數據後將該桶的當前存放數量置零
                buckets[j][0] = 0;
            }
        }
    }
}

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