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快速排序的实际表现要快一些。