【面試筆記系列】排序算法彙總

摘要

排序算法已是面試中被問爛的題目了,能夠說常常面試都會被問到排序算法。通常面試官的問題比較寬泛,好比:"說說排序算法?"。 拋出這樣的一個問題有的人可能就直接回答了排序算法有哪些,而後沒有深刻分析。這種方式並不可取,緣由以下:面試

  1. 直接說出8個面試題,這樣致使這道題的面試時間比較短,面試官就有機會問更多的問題,如下上升爲壓力面試。狂轟亂炸的題目若是基礎不是特別紮實會死的比較慘。
  2. 給面試官的感受是對排序算法的理解不是特別深入,算法基礎不夠紮實。

我的以爲若是問到排序算法的題能夠從如下排序算法的幾個特色進行:思想,時間複雜度(最壞、最好、平均), 空間複雜度,穩定性。每一個算法題從這些方面入手可使得回答很豐滿,讓面試官以爲本身對排序算法瞭解的比較深入,以爲不過哦,算法基礎還挺紮實的嘛。另外一方面,這樣回答下來估計須要20、30分鐘,能夠拖延時間減小面試官問的更多問題。算法

插入排序

1.直接插入排序

思想:每次將一個待排序的數據按照其關鍵字的大小插入到前面已經排序好的數據中的適當位置,直到所有數據排序完成。
時間複雜度:O(n2) O(n) O(n2) (最壞 最好 平均)
空間複雜度:O(1)
穩定性: 穩定 每次都是在前面已排好序的序列中找到適當的位置,只有小的數字會往前插入,因此原來相同的兩個數字在排序後相對位置不變。
代碼:shell

/**
 * 插入排序
 * @param array
 */
public static void insertSort(int[] array) {
    for (int i = 2; i < array.length; i++ ) {
        int val = array[i];
        int j = i -1;
        while (j >= 0 && array[j] > val) {  // array[j] > val
            array[j+1] = array[j];
            j--;
        }
        array[j+1] = val; //  array[j+1] 不是array[j]
    }
}

2.希爾排序

思想:希爾排序根據增量值對數據按下表進行分組,對每組使用直接插入排序算法排序;隨着增量逐漸減小,每組包含的關鍵詞愈來愈多,當增量減至1時,總體採用直接插入排序獲得有序數組,算法終止。
時間複雜度:O(n2) O(n1.3) O(n) (最壞,最好,平均)
空間複雜度:O(1)
穩定性:不穩定 由於是分組進行直接插入排序,原來相同的兩個數字可能會被分到不一樣的組去,可能會使得後面的數字會排到前面,使得兩個相同的數字排序先後位置發生變化。
不穩定舉例: 4 3 3 2 按2爲增量分組,則第二個3會跑到前面
代碼數組

public static void shellSort(int[] array) {
    int len;
    len = array.length / 2; // 分紅n/2組
    while (len >= 1) {
        for (int i = len; i < array.length; ++i) { //對每組進行直接插入排序
            int temp = array[i];
            int j = i - len;
            while (j >= 0 && array[j] > temp) {
                array[j + len] = array[j];
                j -= len;
            }
            array[j + len] = temp;
        }
        len /= 2;
    }

}

交換排序

3.冒泡排序

思想:對待排序元素的關鍵字從後往前進行多遍掃描,遇到相鄰兩個關鍵字次序與排序規則不符時,就將這兩個元素進行交換。這樣關鍵字較小的那個元素就像一個泡泡同樣,從最後面冒到最前面來。
時間複雜度:最壞:O(n2) 最好: O(n) 平均: O(n2)
空間複雜度:O(1)
穩定性:穩定,相鄰的關鍵字兩兩比較,若是相等則不交換。因此排序先後的相等數字相對位置不變。
代碼dom

public static void bubbleSort(int[] array) {
    boolean flag; // 用來判斷當前這一輪是否有交換數值,若沒有則表示已經排好許了
    for (int i = 0; i < array.length; i++) {
        flag = false;
        /**
         * 這邊要注意 for (int j = array.length -1; j >= i + 1; j--)。 不要寫成
         * for (int j =  i + 1; j < array.length ; j++)
         */
        for (int j = array.length -1; j >= i + 1; j--) {
            if (array[j -1 ] > array[j]) {
                //數據交換
                int temp = array[j - 1];
                array[j - 1] = array[j];
                array[j] = temp;
                //設置標誌位
                flag = true;
            }
        }
        if (!flag) {
            break;
        }
    }
}

4.快速排序

思想:該算法是分治算法,首先選擇一個基準元素,根據基準元素將待排序列分紅兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,而後再用一樣的方法遞歸地排序劃分的兩部分。基準元素的選擇對快速排序的性能影響很大,全部通常會想打亂排序數組選擇第一個元素或則隨機地從後面選擇一個元素替換第一個元素做爲基準元素。
時間複雜度:最壞:O(n2) 最好: O(nlogn) 平均: O(nlogn)
空間複雜度:O(nlogn)用於方法棧
穩定性:不穩定 快排會將大於等於基準元素的關鍵詞放在基準元素右邊,加入數組 1 2 2 3 4 5 選擇第二個2 做爲基準元素,那麼排序後 第一個2跑到了後面,相對位置發生變化。
代碼性能

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

private static void partition(int[] array, int low, int high) {
    if (low < high) {
        int p = quickSort(array, low, high);
        partition(array, low, p - 1);
        partition(array, p + 1, high);
    }
}

private static int quickSort(int[] array, int left, int right) {
    Random random = new Random(System.currentTimeMillis());
    int idx = random.nextInt(right - left + 1) + left;  // 這邊須要注意right - left + 1 (要加1)
    exch(array, idx, left);
    int val = array[left];
    while (left < right) {
        while (left < right && array[right] > val) {
            right--;
        }
        if (left < right) {
            array[left++] = array[right];
        }
        while (left < right && array[left] < val) {
            left++;
        }
        if (left < right) {
            array[right--] = array[left];
        }
    }
    array[left] = val;
    return left;
}

三向快速排序算法ui

/**
 * 三向切分快速排序, 適用於存在大量重複元素的數組
 *
 * @param array
 */
public static void quick2waySort(int[] array) {
    quick2waySort(array, 0, array.length - 1);
}

private static void quick2waySort(int[] array, int lo, int hi) {
    if (hi <= lo) {
        return;
    }
    int lt = lo, gt = hi, i = lo + 1;
    int val = array[lo];
    while (i <= gt) {
        if (array[i] < val) {
            exch(array, i++, lt++);
        } else if (array[i] > val) {
            exch(array, i, gt--);
        } else {
            i++;
        }
    }
    // lt 到 gt 之間的都是等於val 的. 若是存在大量重複元素的數組使用該算法能夠極大提高算法效率,
    quick2waySort(array, lo, lt - 1);
    quick2waySort(array, gt + 1, hi);
}

選擇排序

5.直接選擇排序

思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後每次從剩餘未排序元素中繼續尋找最小(大)元素放到已排序序列的末尾。以此類推,直到全部元素均排序完畢
時間複雜度:最壞:O(n2) 最好: O(n2) 平均: O(n2)
空間複雜度:O(1)
穩定性:不穩定 例如數組 2 2 1 3 第一次選擇的時候把第一個2與1交換使得兩個2的相對次序發生了改變。
代碼code

public static void selectSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int minIdx = i;
        for (int j = i + 1; j < array.length; j++) {
            if (array[j] < array[minIdx]) {
                minIdx = j;
            }
        }
        exch(array, i, minIdx);
    }
}

6.堆排序

思想:堆排序是利用堆的性質進行的一種選擇排序,先將排序元素構建一個最大堆,每次堆中取出最大的元素並調整堆。將該取出的最大元素放到已排好序的序列前面。這種方法相對選擇排序,時間複雜度更低,效率更高。
時間複雜度:最壞:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n)
空間複雜度:O(1)
穩定性:不穩定 例如 5 10 15 10。 若是堆頂5先輸出,則第三層的10(最後一個10)的跑到堆頂,而後堆穩定,繼續輸出堆頂,則剛纔那個10跑到前面了,因此兩個10排序先後的次序發生改變。
代碼排序

public static void heapSort(int[] array) {
    int N = array.length -1;
    for (int k = N / 2; k >= 1; k--) {
        sink(array, k, N);
    }
    while (N > 1) {
        // 最大堆, 選擇最大值放在最後
        exch(array, 1, N --);
        sink(array, 1, N);
    }
}

private static void sink(int[] array, int k, int N) {
    while (2 * k <= N) {
        int j = 2 * k;
        if (j < N && array[j] < array[j+1]) { // <
            j++;
        }
        if (array[j] < array[k]) break;  // <
        exch(array, k, j);
        k = j;
    }
}

7.歸併排序

思想:歸併排序採用了分治算法,首先遞歸將原始數組劃分爲若干子數組,對每一個子數組進行排序。而後將排好序的子數組遞歸合併成一個有序的數組。
時間複雜度:最壞:O(nlog2n) 最好: O(nlog2n) 平均: O(nlog2n)
空間複雜度:O(n)
穩定性:穩定
代碼遞歸

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

private static void sort(int[] array, int left, int right) {
    if (left < right) {
        int middle = (left + right) >> 1;
        //遞歸處理相關的合併事項
        sort(array, left, middle);
        sort(array, middle + 1, right);
        merge(array, left, middle, right);
    }
}

private static void merge(int[] array, int lo, int mid, int hi) {
    //建立一個臨時數組用來存儲合併後的數據
    int[] temp = new int[array.length];
    int left = lo;
    int right = mid + 1;
    int k = lo;
    while (left <= mid && right <= hi) {
        if (array[left] < array[right])
            temp[k++] = array[left++];
        else
            temp[k++] = array[right++];
    }
    //處理剩餘未合併的部分
    while (left <= mid)  temp[k++] = array[left++];
    while (right <= hi)  temp[k++] = array[right++];
    //將臨時數組中的內容存儲到原數組中
    while (lo <= hi) array[lo] = temp[lo++];

}

8.基數排序算法

思想:基數排序是經過「分配」和「收集」過程來實現排序,首先根據數字的個位的數將數字放入0-9號桶中,而後將全部桶中所盛數據按照桶號由小到大,桶中由頂至底依次從新收集串起來,獲得新的元素序列。而後遞歸對十位、百位這些高位採用一樣的方式分配收集,直到沒各位都完成分配收集獲得一個有序的元素序列。
時間複雜度:最壞:O(d(r+n)) 最好:O(d(r+n)) 平均: O(d(r+n))
空間複雜度:O(dr+n) n個記錄,d個關鍵碼,關鍵碼的取值範圍爲r
穩定性:穩定 基數排序基於分別排序,分別收集,因此其是穩定的排序算法。
代碼

相關文章
相關標籤/搜索