1. 程式人生 > >資料結構--氣泡排序、歸併排序、快速排序、選擇排序、插入排序(Java版)

資料結構--氣泡排序、歸併排序、快速排序、選擇排序、插入排序(Java版)

一、氣泡排序

1、思路

  • 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
  • 針對所有的元素重複以上的步驟,直到沒有任何一對元素需要比較。

2、實現

/**
 * 排序演算法的介面
 * @author hoaven
 */
public interface ISort {

    /**
     * 對陣列array進行升序排序
     * @param array
     */
    public void sort(int[] array);
}

/**
 * 氣泡排序
 * 時間複雜度: 平均情況與最差情況都是O(n^2)
 * 空間複雜度: O(1)
 *
 * @author
hoaven * @see ISort */
public class BubbleSort implements ISort { public void sort(int[] array) { int temp = 0; for (int i = 0; i < array.length - 1; i++) { for (int j = i + 1; j < array.length; j++) { if (array[i] > array[j]) { temp = array[i]; array[i] = array[j]; array[j] = temp; } } } } }

二、歸併排序

1、思路

  • 初始狀態:無序區為R[1..n],有序區為空。
  • 第1趟排序: 在無序區R[1..n]中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[1] 交換,使R[1..1]和R[2..n]分別變為記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。
  • 第i趟排序: 第i趟排序開始時,當前有序區和無序區分別為R[1..i-1]和R[i..n](1≤i≤n-1)。 該趟排序從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[i]交換,使R[1..i] 和R[i+1..n]分別變為記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。

2、實現

/**
 * 歸併排序<br>
 * 時間複雜度: 平均情況與最差情況都是O(nlog(n))<br>
 * 空間複雜度: It Depends
 * @author hoaven
 * @see ISort
 */
public class MergeSort implements ISort {

    public void sort(int[] array) {
        int[] auxArray = new int[array.length];
        mergeSort(array, auxArray, 0, array.length - 1);
    }

    /**
     * 基於分治思想,執行歸併排序
     * @param low 待排序陣列下標下界
     * @param high 待排序陣列下標上界
     */
    private void mergeSort(int[] array, int[] auxArray, int low, int high) {
        int dividedIndex = 0; // 分治位置索引變數
        if (low < high) {
            dividedIndex = (low + high) / 2; // 計算分治位置(採用簡單的二分思想)
            mergeSort(array, auxArray, low, dividedIndex); // 左側遞迴歸併排序
            mergeSort(array, auxArray, dividedIndex + 1, high); // 右側遞迴歸併排序
            merge(array, auxArray, low, dividedIndex, high); // 合併分治結果
        }
    }

    private void merge(int[] array, int[] auxArray, int low, int dividedIndex, int high) {
        int i = low; // 指向左半分割槽陣列的指標
        int j = dividedIndex + 1; // 指向右半分割槽陣列的指標
        int auxPtr = 0; // 指向輔助區陣列的指標
        // 合併兩個有序陣列:array[low..dividedIndex]與array[dividedIndex+1..high]。
        while (i <= dividedIndex && j <= high) { // 將兩個有序的數組合並,排序到輔助陣列auxArray中
            if (array[i] > array[j]) { // 左側陣列array[low..dividedIndex]中的array[i]大於右側陣列array[dividedIndex+1..high]中的array[j]
                auxArray[auxPtr++] = array[j++];
            } else {
                auxArray[auxPtr++] = array[i++];
            }
        }
        // 如果array[low..dividedIndex].length>array[dividedIndex+1..high].length,經過上面合併
        // array[low..dividedIndex]沒有合併完,則直接將array[low..dividedIndex]中沒有合併的元素複製到輔助陣列auxArray中去
        while (i <= dividedIndex) {
            auxArray[auxPtr++] = array[i++];
        }
        // 如果array[low..dividedIndex].length<array[dividedIndex+1..high].length,經過上面合併
        // array[dividedIndex+1..high]沒有合併完,則直接將array[dividedIndex+1..high]中沒有合併的元素複製到輔助陣列auxArray中去
        while (j <= high) {
            auxArray[auxPtr++] = array[j++];
        }
        // 最後把輔助陣列auxArray的元素複製到原來的陣列中去,歸併排序結束
        for (auxPtr = 0, i = low; i <= high; i++, auxPtr++) {
            array[i] = auxArray[auxPtr];
        }
    }
}

//假設待排序陣列為array = {94,12,34,76,26,9,0,37,55,76,37,5,68,83,90,37,12,65,76,49},陣列大小為20,我們以該陣列為例,執行歸併排序的具體過程:
            [94,12,34,76,26,9,0,37,55,76,    37,5,68,83,90,37,12,65,76,49]
            [94,12,34,76,26,    9,0,37,55,76]
            [94,12,34,    76,26]
            [94,12,    34]
            [94,    12]
            {12,    94}
            {12,34,    94}
            [76,    26]
            {26,    76}
            {12,26,34,    76,94}
            [9,0,37,    55,76]
            [9,0,    37]
            [9,    0]
            {0,    9}
            {0,9,    37}
            [55,    76]
            {55,    76}
            {0,9,37,    55,76}
            {0,9,12,26,34,    37,55,76,76,94}
            [37,5,68,83,90,    37,12,65,76,49]
            [37,5,68,    83,90]
            [37,5,    68]
            [37,    5]
            {5,    37}
            {5,37,    68}
            [83,    90]
            {83,    90}
            {5,37,68,    83,90}
            [37,12,65,    76,49]
            [37,12,    65]
            [37,    12 ]
            {12,    37 }
            {12,37,    65 }
            [76,    49 ]
            {49,    76}
            {12,37,49,    65,76}
            {5,12,37,37,49,    65,68,76,83,90}
            {0,5,9,12,12,26,34,37,37,37,    49,55,65,68,76,76,76,83,90,94}
//上面示例的排序過程中,方括號表示“分解”操作過程中,將原始陣列進行遞迴分解,直到不能再繼續分割為止;
//花括號表示“歸併”的過程,將上一步分解後的陣列進行歸併排序。
//因為採用遞迴分治的策略,所以從上面的排序過程可以看到,“分解”和“歸併”交叉出現。

三、快速排序

1、思路

  • 在R[low..high]中任選一個記錄作為基準Pivot;
  • 使左邊子區間中所有記錄的關鍵字均小於等於基準;
  • 右邊的子區間中所有記錄的關鍵字均大於等於基準;

2、實現

/**
 * 快速排序<br>
 * 時間複雜度: 平均情況是O(nlog(n)),最差情況是O(n^2)<br>
 * 空間複雜度: O(nlog(n))
 * @author hoaven
 * @see ISort
 */
public class QuickSort implements ISort {

    public void sort(int[] array) {
        quickSort(array, 0, array.length - 1);
    }

    /**
     * 通過劃分,基於分治思想,遞迴執行子任務排序最後合併
     * @param low 陣列首位置索引
     * @param high 陣列末位置索引
     */
    private void quickSort(int[] array, int low, int high) {
        int pivotPos; // 劃分基準元素索引
        if (low < high) {
            pivotPos = partition(array, low, high);
            quickSort(array, low, pivotPos - 1); // 左劃分遞迴快速排序
            quickSort(array, pivotPos + 1, high); // 右劃分遞迴快速排序
        }
    }
    /**
     * 簡單劃分方法:排列陣列array左邊的都小於它,右邊的都大於它
     * @param i
     * @param j
     * @return
     */
    private int partition(int[] array, int i, int j) {
        Integer pivot = array[i]; // 初始基準元素,如果quickSort方法第一次呼叫,pivot初始為陣列第一個元素
        while (i < j) { // 兩個指標從兩邊向中間靠攏,不能相交
            // 右側指標向左移動
            while (j > i && array[j] >= pivot) {
                j--;
            }
            if (i < j) { // 如果在沒有使指標i和j相交的情況下找到了array[j] >= 基準元素pivot
                array[i] = array[j]; // 基準元素放到了j指標處
                i++; // 左側i指標需要向右移動一個位置
            }
            // 左側指標向右移動
            while (i < j && array[i] <= pivot) {
                i++;
            }
            if (i < j) { // 如果在沒有使指標i和j相交的情況下找到了array[i] <= 基準元素pivot
                array[j] = array[i]; // 基準元素放到了i指標處
                j--; // 右側j指標需要向左移動一個位置
            }
        }
        array[i] = pivot; // 將基準元素放到正確的排序位置上
        return i;
    }
}

四、選擇排序

1、思路

每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。

2、實現

/**
 * 選擇排序<br>
 * 時間複雜度: 平均情況與最差情況都是O(n^2)<br>
 * 空間複雜度: O(1)
 * @author hoaven
 * @see ISort
 */
public class SelectionSort implements ISort {

    public void sort(int[] array) {
        int temp = 0;

        for(int i = 0; i < array.length; i++){
            temp = array[i];
            for(int j = i; j < array.length; j++){
                if(temp > array[j]){
                    temp = array[j];
                }
            }

            if(temp != array[i]){
                array[i] = temp;
            }
        }
    }
}

五、插入排序

1、思路

要求在一個已排好序的資料序列中插入一個數,但要求插入後此資料序列仍然有序。

2、實現

/**
 * 插入排序實現
 * @author hoaven
 *
 */
public class InsertSort implements ISort {

    public void sort(int[] array) {

        for(int i = 1; i < array.length; i++){
            int temp = array[i];
            int j = i - 1;
            while(j >= 0 && array[j] > temp){
                array[j + 1] = array[j];
                j--;
            }

            if(j != i - 1){
                array[j + 1] = temp;
            }
        }
    }
}