堆排序&快速排序
阿新 • • 發佈:2018-12-18
一、堆排序
構建堆
無序陣列建立堆最直接的方法是從左到右遍歷陣列進行上浮操作。一個更高效的方法是從右至左進行下沉操作,如果一個節點的兩個節點都已經是堆有序,那麼進行下沉操作可以使得這個節點為根節點的堆有序。葉子節點不需要進行下沉操作,可以忽略葉子節點的元素,因此只需要遍歷一半的元素即可。
交換堆頂元素與最後一個元素
交換之後需要進行下沉操作維持堆的有序狀態。
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。為了防止陣列最開始就是有序的,在進行快速排序時需要隨機打亂陣列。