1. 程式人生 > >堆排序&快速排序

堆排序&快速排序

一、堆排序

構建堆

無序陣列建立堆最直接的方法是從左到右遍歷陣列進行上浮操作。一個更高效的方法是從右至左進行下沉操作,如果一個節點的兩個節點都已經是堆有序,那麼進行下沉操作可以使得這個節點為根節點的堆有序。葉子節點不需要進行下沉操作,可以忽略葉子節點的元素,因此只需要遍歷一半的元素即可。

交換堆頂元素與最後一個元素

交換之後需要進行下沉操作維持堆的有序狀態。

class Solution{
    public static void heapSort(int[] nums) {
        if (nums == null || nums.length == 0) {
            return ;
        }
        int n = nums.length;
        for (int i = n / 2 - 1; i >= 0; i--) {
            adjustHeap(nums, i, n);
        }
        for (int i = n - 1; i >= 0; i--) {
            int temp = nums[i];
            nums[i] = nums[0];
            nums[0] = temp;
            adjustHeap(nums, 0, i);
        }
    }

    public static void adjustHeap(int[] nums, int current, int n) {
        int temp = nums[current], left = 2 * current + 1;
        while (left < n) {
            if (left + 1 < n && nums[left] < nums[left + 1]) {
                left++;
            }
            if (temp < nums[left]) {
                nums[current] = nums[left];
                current = left;
            } else {
                break;
            }
            left = 2 * left + 1;
        }
        nums[current] = temp;
    }
}

效能分析

一個堆的高度為 logN,因此在堆中插入元素和刪除最大元素的複雜度都為 logN。對於堆排序,由於要對 N 個節點進行下沉操作,因此複雜度為 NlogN。堆排序是一種原地排序,沒有利用額外的空間。

二、快速排序

基本原理

  • 歸併排序將陣列分為兩個子陣列分別排序,並將有序的子陣列歸併使得整個陣列排序。
  • 快速排序通過一個切分元素將陣列分為兩個子陣列,左子陣列小於等於切分元素,右子陣列大於等於切分元素,將這兩個子陣列排序也就將整個陣列排序了。

切分

取 a[l] 作為切分元素,然後從陣列的左端向右掃描直到找到第一個大於等於它的元素,再從陣列的右端向左掃描找到第一個小於等於它的元素,交換這兩個元素。不斷進行這個過程,就可以保證左指標 i 的左側元素都不大於切分元素,右指標 j 的右側元素都不小於切分元素。當兩個指標相遇時,將切分元素 a[l] 和 a[j] 交換位置。

class Solution{
    public static void quickSort(int[] nums, int left, int right) {
        if (nums == null || nums.length == 0 || left >= right) {
            return ;
        }
        int basePostion = partition(nums, left, right);
        quickSort(nums, left, basePostion - 1);
        quickSort(nums, basePostion + 1, right);
    }

    public static int partition(int[] nums, int left, int right) {
        int i = left, j = right, base = nums[left];
        while (i < j) {
            while (i < j && base <= nums[j]) {
                j--;
            }
            if (i < j) {
                nums[i++] = nums[j];
            }
            while (i < j && base >= nums[i]) {
                i++;
            }
            if (i < j) {
                nums[j--] = nums[i];
            }
        }
        nums[i] = base;
        return j;
    }
}

效能分析

快速排序是原地排序,不需要輔助陣列,但是遞迴呼叫需要輔助棧。

快速排序最好的情況下是每次都正好能將陣列對半分,這樣遞迴呼叫次數才是最少的。這種情況下比較次數為 CN=2CN/2+N,複雜度為 O(NlogN)。

最壞的情況下,第一次從最小的元素切分,第二次從第二小的元素切分,如此這般。因此最壞的情況下需要比較 N2/2。為了防止陣列最開始就是有序的,在進行快速排序時需要隨機打亂陣列。