Description
Given an integer array, sort it in ascending order. Use quick sort, merge sort, heap sort or any O(nlogn) algorithm.
Given [3, 2, 1, 4, 5]
, return [1, 2, 3, 4, 5]
.
Solution
1. 快速排序 Quick Sort
平均時間複雜度O(nlogn),最壞時間複雜度O(n^2),空間複雜度O(1),不穩定排序(排序之後原來相同元素的順序也會改變)
做法:隨便選擇一個數字作爲中心點P(一般取中點),小於等於P放左邊,大於等於P放右邊,然後遞歸左半部分和右半部分進行排序。
思想:先整體有序再局部有序,先看起來宏觀上有序再調整微觀有序。
關鍵點:
- pivot 取中間點int pivot = A[(start + end) / 2]
- left <= right,保證左右指針能交錯,防止StackOverFlow
- A[left] < pivot 而不是 A[left] <= pivot,否則如果所有元素都相等則會直到末尾,不能均分數組,複雜度會退化到O(n^2)
記憶邊界條件:
- left <= right;
- A[left] < pivot; A[right] > pivot;
public class Solution {
/**
* @param A: an integer array
* @return: nothing
*/
public void sortIntegers2(int[] A) {
// write your code here
if (A == null || A.length == 0) {
return;
}
quickSort(A, 0 ,A.length - 1);
}
private void quickSort(int[] A, int start, int end) {
if (start >= end) {
return;
}
int left = start, right = end;
//key point 1: pivot is the value, not the index
int pivot = A[(start + end) / 2];
// key point 2: every time you compare left & right, it should be
// left <= right not left < right
//保證兩個指針錯開,防止因重複排序,沒有降低問題規模而造成的內存越界
while (left <= right) {
while (left <= right && A[left] < pivot) {
//找到一個大於等於中心點的
left++;
}
while (left <= right && A[right] > pivot) {
//找到一個小於等於中心點的
right--;
}
if (left <= right) {
//左右交換,之後指針往後移
int temp = A[left];
A[left] = A[right];
A[right] = temp;
left++;
right--;
}
}
//先整體有序之後再遞歸quickSort使得局部也有序
//while結束之後right會到left的左面,所以是從start到right排序,再從left到end排序
quickSort(A, start, right);
quickSort(A, left, end);
}
}
2. 歸併排序 Merge Sort
時間複雜度穩定在O(nlogn),空間複雜度O(n),穩定排序(排序之後原來相同元素的順序不會改變)。
做法:上來先取半,左邊排序,再右邊排序,然後合併到一起。
思想:先局部有序再整體有序
public class Solution {
/**
* @param A: an integer array
* @return: nothing
*/
public void sortIntegers2(int[] A) {
// write your code here
if (A == null || A.length == 0) {
return;
}
int[] temp = new int[A.length];
mergeSort(A, 0 , A.length - 1, temp);
}
private void mergeSort(int[] A, int start, int end, int[] temp) {
if (start >= end) {
return;
}
//左右分別歸併排序
mergeSort(A, start, (start + end) / 2, temp);
mergeSort(A, (start + end) / 2 + 1, end, temp);
//合併左右結果
merge(A, start, end, temp);
}
private void merge(int[] A, int start, int end, int[] temp) {
int middle = (start + end) / 2;
int leftIndex = start;
int rightIndex = middle + 1;
int index = start;
while (leftIndex <= middle && rightIndex <= end) {
//左邊的索引小於中間,右邊的不超過end
if (A[leftIndex] < A[rightIndex]) {
//左邊小就存左邊的數到temp中
temp[index++] = A[leftIndex++];
} else {
//右邊小就存右邊的數到temp中
temp[index++] = A[rightIndex++];
}
}
//循環結束之後可能有一個數組還沒結束,把剩下的存到temp中
while (leftIndex <= middle) {
temp[index++] = A[leftIndex++];
}
while (rightIndex <= end) {
temp[index++] = A[rightIndex++];
}
//將temp中排好的數存回A數組中
for (int i = start; i <= end; i++) {
A[i] = temp[i];
}
}
}
總結:
二者都是將O(n)的問題分成兩個 O(n/2)的問題,分的過程和合的過程在這一層耗費O(n)的時間複雜度,即T(n) = 2 * T(n/2) + O(n)。
區別在於完成的先後順序不同:
- 快速排序先分割成左右兩半(小於中間點放左邊,大於的放右邊),完成了O(n)這件事情,再去遞歸完成2個T(n/2)的事情。
- 歸併排序是先遞歸到左右兩邊進行排序,分別完成左右兩邊的排序之後(完成2個T(n/2)的事情),再將結果進行合併(完成O(n)這件事情)。
Marge Sort歸併排序的劣勢在於需要額外使用O(n)的空間(合併兩個數組的時候),開闢和回收額外空間很浪費時間。對於穩定性的要求並不是時時刻刻都有,而且即使有穩定性的要求也可以通過修改compartor返回滿足條件的值來實現。所以Quick Sort快速排序的實際表現要快一些。