資料結構與演算法(八):排序

山貓大戰響尾蛇發表於2020-06-30

什麼是排序?

排序是計算機內經常進行的一種操作,其目的是將一組“無序”的記錄序列調整為“有序”的記錄序列。

1.排序的分類

排序分為兩類:

  • 內部排序:若整個排序過程不需要訪問外存便能完成,則稱此類排序問題為內部排序。
  • 外部排序:若參加排序的記錄數量很大,整個序列的排序過程不可能在記憶體中完成,則稱此類排序問題為外部排序。

一般來說,外部排序只有資料量極大時會使用,一般情況下排序指的都是內部排序。

image-20200627110216492

2.空間複雜度

1.類似於時間複雜度的討論,一個演算法的空間複雜度(Space Complexity)定義為該演算法所耗費的儲存空間,它也是問題規模n的函式。

2.空間複雜度(Space Complexity)是對一個演算法在執行過程中臨時佔用儲存空間大小的量度。有的演算法需要佔用的臨時工作單元數與解決問題的規模n有關,它隨著n的增大而增大,當n較大時,將佔用較多的儲存單元,例如快速排序和歸併排序演算法就屬於這種情況。

3.在做演算法分析時,主要討論的是時間複雜度。從使用者使用體驗上看,更看重的程式執行的速度。一些快取產品(redis, memcache)和演算法(基數排序)本質就是用空間換時間。

3.排序的穩定

待排序的記錄序列中可能存在兩個或兩個以上關鍵字相等的記錄。排序前的序列中arr[i]領先於arr[j](即i<j)。若在排序後的序列中arr[i]仍然領先於arr[j],則稱所用的方法是穩定的。

比如int陣列[1,1,1,6,4]中arr[0],arr[1],arr[2]的值相等,在排序時不改變其序列,則稱所用的方法是穩定的。

  • 穩定的排序:氣泡排序,插入排序,歸併排序,基數排序,計數排序
  • 不穩定的排序:快速排序,希爾排序,選擇排序,堆排序

穩定性設計到排序的現實意義,舉個例子:

例如要排序的內容是一組原本按照價格高低排序的物件,如今需要按照銷量高低排序,使用穩定性演算法,可以使得想同銷量的物件依舊保持著價格高低的排序展現,只有銷量不同的才會重新排序。

更多關於穩定性的理解可以參考這個

4.各排序時間複雜度概覽

排序法 平均時間 最差情形 是否穩定 優先選擇條件
氣泡排序 O(n^2) O(n^2) 穩定 n小時較好
交換排序 O(n^2) O(n^2) 不穩定 n小時較好
選擇排序 O(n^2) O(n^2) 不穩定 n小時較好
插入排序 O(n^2) O(n^2) 穩定 大部分已排序時較好
基數排序 O(logRB) O(logRB) 穩定 B是真數(0-9),R是基數(個十百)
希爾排序 O(nlogn) O(ns)1<s<2 不穩定 s是所選分組
快速排序 O(nlogn) O(n2) 不穩定 n大時較好
歸併排序 O(nlogn) O(nlogn) 穩定 n大時較好
堆排序 O(nlogn) O(nlogn) 不穩定 n大時較好

一、氣泡排序

氣泡排序是一種簡單的排序演算法,它也是一種穩定排序演算法。其實現原理是重複掃描待排序序列,並比較每一對相鄰的元素,當該對元素順序不正確時進行交換。一直重複這個過程,直到沒有任何兩個相鄰元素可以交換,就表明完成了排序。

1.舉個例子

要對10,-1,8,3這四個數進行排序:

第一次排序:

  • -1,10,8,3 //比較10和-1,逆序則交換

  • -1,8,10,3 //比較10和8

  • -1,8,3,10 //比較10和3

    第一次排序結束,確定了四個數裡最大的數的位置

第二次排序:

  • -1,8,3,10 //比較-1和8,不逆序所以不需要移動

  • -1,3,8,10 //比較8和3

    由於已經確定了10為最大數,所以只需要比較到倒數第二位。

    第二次排序結束,確定了第二大的數的位置

第三次排序:

  • -1,3,8,10 //比較-1和3

    由於已經確定了第三和第四大的數,所以只需要比較到倒數第三位。

    第三次排序結束,確定了第三大的數,故第一大的數也隨之確定

2.思路

  • 比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
  • 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
  • 針對所有的元素重複以上的步驟,除了最後一個;
  • 重複步驟1~3,直到排序完成。

3.實現程式碼

/**
 * 輸入一串無序陣列,對其進行氣泡排序
 * @param arr
 * @return
 */
public static int[] sort(int[] arr) {
    //如果某次排序不發生交換,說明上一次排序前已經為有序
    boolean isChange = false;

    //根據陣列長度決定一共需要排序幾次
    int round = arr.length - 1;
    for (int i = 0; i < round; i++) {
        //每次排序需要對比到第幾位
        for (int j = 0; j < round - i; j++) {
            //對比大小
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;

                //發生交換
                isChange = true;
            }
        }

        //判斷本次排序是否發生交換
        if (!isChange) {
            System.out.println("第" + (i + 1) + "次排序無交換,第" + i + "次排序已為有序!");
            return arr;
        }else {
            isChange = false;
        }
        System.out.println("第" + (i + 1) + "次排序:" + Arrays.toString(arr));
    }
    return arr;
}

//{-1, 52, 9, 13, -5, 7}排序執行結果
第1次排序:[-1, 9, 13, -5, 7, 52]
第2次排序:[-1, 9, -5, 7, 13, 52]
第3次排序:[-1, -5, 7, 9, 13, 52]
第4次排序:[-5, -1, 7, 9, 13, 52]
第5次排序無交換,第4次排序已為有序!

二、選擇排序

選擇排序,是從欲排序的資料中,按指定的規則選出某一元素,再依規定交換位置後達到排序的目的。

1.舉個例子

選擇排序(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次,得到一個按排序碼從小到大排列的有序序列。

2.思路

  • 需要進行n-1輪排序
  • 若要為第i個數排序,就先預設第i個元素為最小數,記錄其大小和下標
  • 接著從第i+1到第n個數開始依次比較,如果有數小於最小數,則用該數替換原最小數和其下標
  • 第i輪比較結束後,讓找出的最小數與第i個數交換位置

3.程式碼實現

/**
 * 輸入一串無序陣列,對其進行選擇排序
 * @param arr
 * @return
 */
public static int[] sort(int[] arr) {
    for (int i = 0; i < arr.length -1; i++) {
        //用於存放每次選擇中最小數的下標,最小值預設為第一個數為i
        int minNumIndex = i;

        //從(i+1,arr.length)的範圍中篩選最小的數
        for (int j = i + 1; j < arr.length; j++) {
            //如果範圍內有數比現有minNum小,則替換下標
            if (arr[j] < arr[minNumIndex]) {
                minNumIndex = j;
            }
        }
        //一次選擇結束,將(i+1,arr.length)的範圍中的最小數與arr[i]交換位置
        int temp = arr[minNumIndex];
        arr[minNumIndex] = arr[i];
        arr[i] = temp;

        System.out.println("第" + (i + 1) + "輪:" + Arrays.toString(arr));
    }

    return arr;
}

//{-1, 52, 9, 13, -5, 7}排序執行結果
第1輪後:[-5, 52, 9, 13, -1, 7]
第2輪後:[-5, -1, 9, 13, 52, 7]
第3輪後:[-5, -1, 7, 13, 52, 9]
第4輪後:[-5, -1, 7, 9, 52, 13]
第5輪後:[-5, -1, 7, 9, 13, 52]

4.與氣泡排序比較

同樣對長度80000的數字進行排序,選擇排序比氣泡排序快不少,原因在於選擇排序每次排序只移動指標,找到位置後才進行一次元素交換,而冒泡需要多次交換。

換句話說,要排序的陣列越長,冒泡每次排序元素要移動的次數就越多,與選擇排序的差距就越明顯。

這個同樣能解釋希爾排序的兩種實現方式的速度差距。

//氣泡排序交換值,交換n次值
temp = arr[j];
arr[j + gap] = temp;
arr[j] = arr[j + gap]
    
//插入排序交換值,交換n次指標
arr[j] = arr[j - gap]
//然後交換1次值
temp = arr[j];
arr[j + gap] = temp;
arr[j] = arr[j + gap]

三、插入排序

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

1.舉個例子

有陣列{5,2,4,6,1,3}要進行排序,

  • 從第二位往前看,5比2大,且5為第一個數,於是5後移,把2插入5前。現在是{2,5,4,6,1,3}
  • 從第三位往前看,5比4大,於是5後移,繼續往前看,2比4小,所以把4插入原先5的位置。現在是{2,4,5,6,1,3}
  • 從第四位往前看,4比6小,於是6就不動了。現在是{2,4,5,6,1,3}
  • 從第五位往前看,6比1小,於是6後移,繼續往前看,5比1小,5後移,繼續往前看.....2比1小,2後移,又2為第一個數,於是把1插入原本2的位置。現在是{1,2,4,5,6,3}
  • 從第六位往前看,6比3小,於是6後移,繼續往前看,.....3比2大,於是把3插入原先4的位置。排序完成。

2.思路分析

  • 從第一個元素開始,該元素可以認為已經被排序;
  • 取出下一個元素,在已經排序的元素序列中從後向前掃描;
  • 如果該元素(已排序)大於新元素,將該元素移到下一位置;
  • 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
  • 將新元素插入到該位置後;
  • 重複步驟2~5。

3.程式碼實現

/**
 * 輸入一串無序陣列,對其進行插入排序
 * @param arr
 * @return
 */
public static int[] sort(int[] arr) {
    //需要從第二位開始,避免i-1出現控制針
    for (int i = 1; i < arr.length; i++) {
        //指向當前插入位置前一個元素的指標
        int pre = i - 1;
        //當前處理元素的值
        int val = arr[i];

        // 讓比當前元素大的元素不斷的後移,直到到頭了或者找到了比當前元素小的元素
        while (pre >= 0 && arr[pre] > val) {
            //前一個元素往後移
            arr[pre + 1] = arr[pre];
            //繼續往前移動
            pre--;
        }

        //跳出迴圈時即找到了當前元素的正確插入位置
        //將該位置的值賦成處理元素的值
        arr[pre + 1] = val;

        System.out.println("第" + (i + 1) + "輪:" + Arrays.toString(arr));
    }

    return arr;
}

//{-1, 52, 9, 13, -5, 7}排序執行結果
第2輪:[-1, 52, 9, 13, -5, 7]
第3輪:[-1, 9, 52, 13, -5, 7]
第4輪:[-1, 9, 13, 52, -5, 7]
第5輪:[-5, -1, 9, 13, 52, 7]
第6輪:[-5, -1, 7, 9, 13, 52]

四、希爾排序

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

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

1.舉個例子

2.思路

  • 將陣列除於2進行分組,得到gap組數字
  • 對gap組數字進行插入排序,由於資料共分為gap組,所以同一組相鄰的數字在陣列中的位置總是相隔gap
  • 遍歷gap組數字,表現在陣列上就是從gap遍歷到arr.length

3.程式碼實現

有兩種實現思路,一種是交換法,一種是移位法

先說結論:移位法比交換法快,原因在於:

交換法思路有點類似於氣泡排序,需要不斷的比較並交換數值,

而移位法即選擇排序,僅僅遍歷賦值後移動指標,找到插入位置後,再把元素插入到有序表,而不是多次交換加入有序表。

//交換法交換n次值
temp = arr[j];
arr[j + gap] = temp;
arr[j] = arr[j + gap]
    
//位移法移動n次指標後只交換一次值
arr[j] = arr[j - gap]

3.1交換法實現

/**
 * 輸入一串無序陣列,對其進行希爾排序
 * 注意,此方法為移位法
 * @param arr
 * @return
 */
public static int[] sort(int[] arr) {
    int temp = 0;
    int count = 0;
    // 將陣列每次對半分分成多組,組數逐漸縮小
    // gap為每次分組後的同組元素的間隔,比如分成5組,那同組元素間隔即為5,即位置是i和i+5的元素是一組
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        // 從第gap個元素開始,逐步遍歷其所在的組,即從第一組開始向後遍歷
        // 第gap個元素即為第一組的最後一個元素,也就是i即表示某組的最後一個位置
        for (int i = gap; i < arr.length; i++) {
            // 接著遍歷同組元素,即一組有幾個元素就遍歷幾次
            // j=i-gap即獲得第某組的倒數第二個元素位置
            // 向前遍歷,每隔gap個元素就對比一次大小
            for (int j = i - gap; j >= 0; j = j - gap) {
                //如果當前元素大於後一個元素,就交換位置
                if (arr[j] > arr[j + gap]) {
                    temp = arr[j];
                    arr[j] = arr[j + gap];
                    arr[j + gap] = temp;
                }
            }
        }

        System.out.println("第" + (++count) + "輪:" + Arrays.toString(arr));
    }


    return arr;
}

//{-1, 52, 9, 13, -5, 7}排序執行結果
第1輪:[-1, -5, 7, 13, 52, 9]
第2輪:[-5, -1, 7, 9, 13, 52]

3.2 移位法實現

下面是通過移位法實現的排序,比交換法更快更穩定:

/**
 * 輸入一串無序陣列,對其進行希爾排序
 * 注意,此方法為移位法
 * @param arr
 * @return
 */
public static int[] sortByMove(int[] arr) {
    int count = 0;
    // 將陣列每次對半分分成多組,組數逐漸縮小
    // gap為每次分組後的同組元素的間隔,比如分成5組,那同組元素間隔即為5,即位置是i和i+5的元素是一組
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {

        //從第gap個元素開始,逐個對其所在組進行插入排序
        for (int i = gap; i < arr.length; i++) {
            int j = i;
            int temp = arr[j];
            //如果某組最後一個元素比前一個元素小
            if (arr[j] < arr[j - gap]) {
                //將同組元素不斷後移,直到該元素找到位置或者到頭為止
                while (j - gap >= 0 && arr[j - gap] > temp) {
                    arr[j] = arr[j - gap];
                    j = j - gap;
                }

                //當找到位置時插入元素
                arr[j] = temp;
            }
        }

        System.out.println("第" + (++count) + "輪:" + Arrays.toString(arr));

    }

    return arr;
}

五、快速排序

快速排序的基本思想:通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

1.舉個例子

  • 先選擇一箇中間位置數11,分別將陣列中比11大和比11小的數放到左右兩個陣列中
  • 對兩個陣列分別選一箇中間位置數,也就是5和21,各自再根據比中間數小或者比中間數大再次分為兩個陣列。以此類推

2.思路

  • 把長度為n的輸入序列分成兩個長度為n/2的子序列
  • 對這兩個子序列分別採用歸併排序;
  • 將兩個排序好的子序列合併成一個最終的排序序列
  • 對左支和右支可通過遞迴實現排序

3.程式碼實現

public static int[] sort(int[] arr) {
    sort(arr, 0, arr.length - 1);
    return arr;
}

/**
 * 輸入一串無序陣列,並根據給定的左右指標對指定的範圍其進行排序
 * @param arr
 * @param left
 * @param right
 * @return
 */
public static int[] sort(int[] arr, int left, int right) {

    //左右指標
    int l = left;
    int r = right;

    //找到中間數
    int pivot = arr[(left + right) / 2];
    //用於元素交換的臨時變數
    int temp;

    //將比中間數小的放左邊,比中間數大的放右邊
    while (l < r) {
        // 從左往右遍歷,尋找比中間數大的數
        while (arr[l] < pivot) {
            l++;
        }
        // 從右往左遍歷,尋找比中間數小的數
        while (arr[r] > pivot) {
            r--;
        }

        // 如果l > r,即左指標右指標都越過了中間數,說明兩邊數都已經有序
        // 如果l = r,即可能存在多個與中間數同值的元素的情況下,左右指標一起指向了同一邊的同一個元素,也說明兩邊數都已經有序
        if (l >= r) {
            break;
        }

        //交換元素
        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;

        //如果交換完後,發現現在右側有一個與中間數相同的數,右指標前移一位
        if (arr[l] == pivot) {
            r -= 1;
        }
        //如果交換完後,發現現在左側有一個與中間數相同的數,左指標後移一位
        if (arr[r] == pivot) {
            l += 1;
        }
    }

    //防止死迴圈
    if (l == r) {
        l += 1;
        r -= 1;
    }

    //向右遞迴
    if (left < r) {
        sort(arr, l, right);
    }
    //向左遞迴
    if (right > l) {
        sort(arr, left, r);
    }

    return arr;
}

六、歸併排序

歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。

1.舉個例子

我們以上圖最後一次合併為例:

以上多個有序陣列間的合併需要進行多次,通過遞迴完成

2.思路

  • 把長度為n的陣列分成兩個長度為n/2的子陣列;
  • 如果子陣列仍然長度大於1,就重複步驟1直到所有陣列都被拆分完畢
  • 將拆分後的元素兩兩合併為一個有序陣列,然後相鄰兩個陣列A,B進行合併:
    1. 建立一個新陣列,然後遍歷B陣列並與A陣列第一位進行比較,如果該數字比A陣列第一小則放入新陣列第一位
    2. 否則將A陣列第一位放入新陣列
  • 重複上面步驟3直到A,B陣列所有元素都有序放入新陣列,即合併完成
  • 重複步驟3,直到所有陣列都最終都被合併為一個有序陣列

3.程式碼實現

/**
 * @Author:黃成興
 * @Date:2020-06-29 21:45
 * @Description:歸併排序
 */
public class MergeSort {

    public static void main(String[] args) {
        int arr[] = {8, 4, 5, 7};
        System.out.println(Arrays.toString(sort(arr)));
    }

    public static int[] sort(int[] arr) {
        int temp[] = new int[arr.length];
        return sort(arr, 0, arr.length - 1, temp);
    }

    /**
     * 合併排序
     * @param arr 排序的原始陣列
     * @param left 左邊有序序列的初始索引
     * @param right 右邊索引
     * @param temp 臨時儲存的中轉陣列
     */
    public static int[] sort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            //獲取中間索引
            int mid = (left + right) / 2;
            //向左遞迴分解
            sort(arr, left, mid, temp);
            //向右遞迴分解
            sort(arr, mid + 1, right, temp);
            // 先左遍歷到最左邊,然後向右遍歷,當l=r時觸發排序
            merge(arr, left, mid, right, temp);
        }
        return arr;
    }

    /**
     * 合併的方法
     *
     * @param arr 排序的原始陣列
     * @param left 左邊有序序列的初始索引
     * @param mid 中間索引
     * @param right 右邊索引
     * @param temp 臨時儲存的中轉陣列
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        //左邊有序序列的初始索引
        int i = left;
        //中間索引
        int j = mid+1;
        //temp的索引
        int t = 0;

        //先把左右兩邊有序的資料按照規則填充到temp陣列,直到左右兩邊的有序序列,有一邊處理完畢為止
        while (i <= mid && j <= right) {
            //如果左邊的有序序列的當前元素小於等於右邊有序序列的當前元素
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else {
                //否則將右邊有序序列的當前元素填充到temp陣列
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }

        //左邊的有序序列還有剩餘的元素,就全部填充到temp陣列
        while (i <= mid) {
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        //右邊的有序序列還有剩餘的元素,就全部填充到temp陣列
        while (j <= right) {
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }

        //將temp陣列裡的有序元素拷貝回arr陣列
        //從左邊開始拷貝, 注意:不是每次都拷貝所有
        t = 0;
        int tempLeft = left;
        //第一次合併:templeft = 0,right = 1。 第二次合併:templeft = 2,right = 3。 最後一次:templeft = 0,right = 7
        while (tempLeft <= right) {
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }
}

七、基數排序

基數排序是按照低位先排序,然後收集;再按照高位排序,然後再收集;依次類推,直到最高位。有時候有些屬性是有優先順序順序的,先按低優先順序排序,再按高優先順序排序。最後的次序就是高優先順序高的在前,高優先順序相同的低優先順序高的在前。

1.舉個例子

2.思路

  • 取得陣列中的最大數,並取得位數
  • 準備一個長度為10,內部一位陣列長度為arr.length的二維陣列,可以理解為10個高度為arr.length的桶
  • 遍歷陣列,根據陣列中個位數決定要放在哪個桶,即如果個位數為1就放入1號桶,為2就放入2號桶,直到陣列所有元素分配完畢
  • 遍歷桶將桶中數字放回原陣列,然後清空桶。即完成了個位數的排序
  • 重複步驟3和步驟4,但是排序依據從個位數換成十位數,然後百位數.....以此類推,直到陣列最大位數

3.程式碼實現

package com.huang.example.sort;

import java.util.Arrays;

/**
 * @Author:黃成興
 * @Date:2020-06-30 21:39
 * @Description:基數排序
 */
public class RadixSort {

    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214 };
        System.out.println("最大位數:" + getMaxDigit(arr));
        System.out.println(Arrays.toString(sort(arr)));
    }

    /**
     * 獲取陣列中的最大數的位數
     * @param arr
     * @return
     */
    public static int getMaxDigit(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) {
                max = arr[i];
            }
        }
        return String.valueOf(max).length();
    }

    public static int[] sort(int[] arr) {
        //設定二維陣列用於表示桶
        //第一層陣列下標即表示存放某數位為x的數字的桶,比如下標為2的桶用於存放個位數為2的數字;下標為0的桶用於存放十位數為0的數字
        //第二層陣列即表示桶高度
        int[][] bucket = new int[10][arr.length];

        //表示某個桶內用幾個元素,比如bucketElementCount[0]即表示bucket[0]桶有幾個元素
        //由於數字下標從0開始,所以這同時也表示了桶下一個元素的插入下標,比如bucketElementCount[0]=1,就意味著bucket[0]下一個元素應該插到bucket[0][1]去
        int[] bucketElementCount = new int[10];

        //最大數有幾位就迴圈排序幾次
        for (int i = 0, n = 1; i <= getMaxDigit(arr); i++, n *= 10) {
            //遍歷元素並歸類到桶中
            for (int j = 0; j < arr.length; j++) {
                //獲取元素某數位的數字
                //根據遍歷,獲取數字的個位,十位數,百位數......
                int digitOfElement = arr[j] / n % 10;
                //將其歸類到桶中
                bucket[digitOfElement][bucketElementCount[digitOfElement]] = arr[j];
                //提高桶高度
                bucketElementCount[digitOfElement]++;
            }

            //按順序將每一個桶中元素取出並放入原集合
            int index = 0;
            for (int k = 0; k < bucketElementCount.length; k++) {
                //如果桶中有元素就取出
                if (bucketElementCount[k] != 0) {
                    //遍歷桶中元素並放入陣列
                    for (int l = 0; l < bucketElementCount[k]; l++) {
                        arr[index++] = bucket[k][l];
                    }
                }
                //清空桶
                bucketElementCount[k] = 0;
            }
        }

        return arr;
    }
}

4.基數排序注意事項:

  • 基數排序是典型的空間換時間,當排序的數字過多的時候可能會發生OutOfMemoryError(實測八千萬時報錯)

  • 基數排序要排負數的時候需要加以改進:

    將陣列中的負數單獨摘出並取絕對值後進行排序,然後倒序插入排序完的整數陣列,並且在插入過程加上負號

八、堆排序

參照二叉樹部分

相關文章