第六章 排序算法

6.1 排序算法介紹

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

術語說明:

  • 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面;
  • 不穩定:如果a原本在b的前面,而a=b,排序之後a可能會出現在b的後面;
  • 內排序:所有排序操作都在內存中完成;
  • 外排序:由於數據太大,因此把數據放在磁盤中,而排序通過磁盤和內存的數據傳輸才能進行;
  • 時間複雜度:一個算法執行所耗費的時間。
  • 空間複雜度:運行完一個程序所需內存的大小。

6.2 排序算法分類

內部排序:

指將需要處理的所有數據都加載到內部存儲器(內存)中進行排序。

外部排序法:

數據量過大無法全部加載到內存中,需要藉助外部存儲(文件等)進行排序。

常見的排序算法分類:
常見的排序算法

6.3 排序算法的時間複雜度

比較和非比較的區別:

常見的快速排序、歸併排序、堆排序、冒泡排序等屬於比較排序。在排序的最終結果裏,元素之間的次序依賴於它們之間的比較。每個數都必須和其他數進行比較,才能確定自己的位置。

在冒泡排序之類的排序中,問題規模爲n,又因爲需要比較n次,所以平均時間複雜度爲O(n²)。在歸併排序、快速排序之類的排序中,問題規模通過分治法消減爲logN次,所以時間複雜度平均O(nlogn)。

比較排序的優勢是,適用於各種規模的數據,也不在乎數據的分佈,都能進行排序。可以說,比較排序適用於一切需要排序的情況。

計數排序、基數排序、桶排序則屬於非比較排序。非比較排序是通過確定每個元素之前,應該有多少個元素來排序。針對數組arr,計算arr[i]之前有多少個元素,則唯一確定了arr[i]在排序後數組中的位置。

非比較排序只要確定每個元素之前的已有的元素個數即可,所有一次遍歷即可解決。算法時間複雜度O(n)。

非比較排序時間複雜度底,但由於非比較排序需要佔用空間來確定唯一位置。所以對數據規模和數據分佈有一定的要求。

6.4 排序算法的空間複雜度

6.5 冒泡排序

算法思想:

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

算法優化:

因爲排序的過程中,各元素不斷接近自己的位置,如果一趟比較下來沒有進行過交換,就說明序列有序,因此要在排序過程中設置一個標誌 flag 判斷元素是否進行過交換,從而減少不必要的比較。

代碼實現:

public class BubbleSortTest {

    public static void main(String[] args) {
        int[] array = CreateArrayUtils.createArray(8);
        System.err.println(Arrays.toString(array));
        bubbleSort(array);
    }

    private static void bubbleSort(int[] array) {
        int length = array.length - 1;
        boolean flag = false;
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length - i; j++) {
                if (array[j] > array[j + 1]) {
                    flag = true;
                    // 交換相鄰的值
                    array[j] = array[j] + array[j + 1];
                    array[j + 1] = array[j] - array[j + 1];
                    array[j] = array[j] - array[j + 1];
                }
            }
            // 優化
            if (!flag) {
                break;
            } else {
                // 重置標誌位
                flag = false;
            }
        }
        System.err.println(Arrays.toString(array));
    }
}

6.6 選擇排序

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

算法思想:

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

public class SelectSortTest {

    public static void main(String[] args) {
        int[] array = CreateArrayUtils.createArray(10);
        System.err.println(Arrays.toString(array));
        selectSort(array);
    }

    private static void selectSort(int[] array) {
        int length = array.length;
        for (int i = 0; i < length - 1; i++) {
            int minIndex = i;
            int min = array[i];
            for (int j = i + 1; j < length; j++) {
                if (min > array[j]) {
                    minIndex = j;
                    min = array[j];
                }
            }
            if (minIndex != i) {
                array[minIndex] = array[i];
                array[i] = min;
            }
        }
        System.err.println(Arrays.toString(array));
    }
}

6.7 插入排序

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

算法思路:

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

public class InsertSortTest {

    public static void main(String[] args) {
        int[] array = CreateArrayUtils.createArray(8);
        System.err.println(Arrays.toString(array));
        insetSort(array);
    }

    private static void insetSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            // 定義插入的值
            int insert = array[i];
            // 定義插入的下標
            int insertIndex = i - 1;
            // insertIndex >= 0 保證插入的位置不會數組越界
            // insert < array[insertIndex] 當要插入的值小於插入下標位置的值時
            // 源數組 {2,3,4,5,6,1}
            // 第一輪 {2,3,4,5,6,6}
            // 第二輪 {2,3,4,5,5,6}
            // 第三輪 {2,3,4,4,5,6}
            // 第四輪 {2,3,3,4,5,6}
            // 第五輪 {2,2,3,4,5,6}
            while (insertIndex >= 0 && insert < array[insertIndex]) {
                // 將插入下標位置的值複製到下標的下一個空間
                array[insertIndex + 1] = array[insertIndex];
                // 將插入下標後移
                insertIndex--;
            }
            // 找到要插入的位置 {1,2,3,4,5,6}
            if (insertIndex + 1 != i) {
                array[insertIndex + 1] = insert;
            }
        }
        System.err.println(Arrays.toString(array));
    }
}

6.8 希爾排序

希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高效的版本,也稱爲縮小增量排序。

算法思路:

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

希爾排序時,,對有序序列在插入時可以採用交換法移動法

public class ShellSortTest {

    public static void main(String[] args) {
        int[] array = CreateArrayUtils.createArray(10);
        System.err.println(Arrays.toString(array));
        shellSort2(array);
    }

    /**
     * 交換法
     *
     * @param array
     */
    private static void shellSort1(int[] array) {
        int length = array.length;
        // 按下標的一定增量分組,增量 gap,並逐步的縮小增量
        for (int gap = length / 2; gap > 0; gap /= 2) {
            // 遍歷所有的分組
            for (int i = gap; i < length; i++) {
                // 遍歷各組中所有的元素(共 gap 組,每組有 length/gap 個元素),步長 gap
                for (int j = i - gap; j >= 0; j -= gap) {
                    // 交換法:如果當前元素大於加上步長後的那個元素則交換
                    if (array[j] > array[j + gap]) {
                        array[j] = array[j] + array[j + gap];
                        array[j + gap] = array[j] - array[j + gap];
                        array[j] = array[j] - array[j + gap];
                    }
                }
            }
        }
        System.err.println(Arrays.toString(array));
    }

    /**
     * 位移法
     *
     * @param array
     */
    private static void shellSort2(int[] array) {
        int length = array.length;
        // 按下標的一定增量分組,增量 gap,並逐步的縮小增量
        for (int gap = length / 2; gap > 0; gap /= 2) {
            // 遍歷所有的分組
            for (int i = gap; i < length; i++) {
                // 從第 gap 個元素,逐個對其所在的組進行直接插入排序
                int insert = array[i];
                int insertIndex = i;
                //if (array[insertIndex] < array[insertIndex - gap]) {
                // insertIndex - gap >= 0 插入位置不能越界
                while (insertIndex - gap >= 0 && insert < array[insertIndex - gap]) {
                    array[insertIndex] = array[insertIndex - gap];
                    insertIndex -= gap;
                }
                // 當退出 while 後,就給 insert 找到插入的位置
                array[insertIndex] = insert;
                //}
            }
        }
        System.err.println(Arrays.toString(array));
    }
}

6.9 快速排序

6. 10 歸併排序

6.11 基數排序

6.12 插入排序

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