http://blog.hyoung.me/cn/2017/02/quick-sort/
Quick Sort - 快速排序
核心:快排是一种采用分治思想的排序算法,大致分为三个步骤。
定基准——首先随机选择一个元素最为基准
划分区——所有比基准小的元素置于基准左侧,比基准大的元素置于右侧,
递归调用——递归地调用此切分过程。
递归实现
容易实现和理解的一个方法是采用递归,Python 的实现如下所示:
#!/usr/bin/env python
def qsort(alist):
print\(alist\)
if len\(alist\) <= 1:
return alist
else:
pivot = alist\[0\]
return qsort\(\[x for x in alist\[1:\] if x < pivot\]\) + \
\[pivot\] + \
qsort\(\[x for x in alist\[1:\] if x >= pivot\]\)
unsortedArray = [6, 5, 3, 1, 8, 7, 2, 4]
print(qsort(unsortedArray))
输出如下所示:
[6, 5, 3, 1, 8, 7, 2, 4]
[5, 3, 1, 2, 4]
[3, 1, 2, 4]
[1, 2]
[]
[2]
[4]
[]
[8, 7]
[7]
[]
[1, 2, 3, 4, 5, 6, 7, 8]
递归 + not-in-place 的实现虽然简单易懂,但是如此一来『快速排序』便不再是『最快』的排序算法了,因为递归调用过程中空间复杂度颇高。
复杂度分析
在最好情况下,快速排序的基准元素正好是整个数组的中位数,可以近似为二分,那么最好情况下递归的层数为 logn, 咋看一下每一层的元素个数都是 n, 那么空间复杂度为 O(n) 无疑了,不过这只答对了一半,从结论上来看是对的,但分析方法是错的。
首先来看看什么叫空间复杂度——简单来讲可以认为是程序在运行过程中所占用的存储空间大小。那么对于递归的 out-in-place 调用而言,排除函数调用等栈空 间,最好情况下,每往下递归调用一层,所需要的存储空间是上一层中的一半。完成最底层的调用后即向上返回执行出栈操作,故并不需要保存每层所有元素的值。所以 需要的总的存储空间就是 ∑i=02in=2n
不是特别理解的可以结合下图的非严格分析和上面 Python 的代码,递归调用的第一层保存8个元素的值,那么第二层调用时实际需要保存的其实仅为4个元素,逐层往下递归,而不是自左向右保存每一层的所有元素。
那么在最坏情况下 out-in-place 需要耗费多少额外空间呢?最坏情况下第 i 层需要 i−1 次交换,故总的空间复杂度:
∑i=0n(n−i+1)=O(n2)
Quicksort
Recursive
in-place - 原地快排
先来一张动图看看快速排序的过程。
Quick Sort
Animation
选中3作为基准
lo指针指向元素6, hi指针指向4, 移动lo直至其指向的元素大于等于3, 移动hi直至其指向的元素小于3。找到后交换lo和hi指向的元素——交换元素6和2。
lo递增,hi递减,重复步骤2,此时lo指向元素为5, hi指向元素为1. 交换元素。
lo递增,hi递减,发现其指向元素相同,此轮划分结束。递归排序元素3左右两边的元素。
与归并排序的区别:
归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序。递归调用发生在处理整个数组之前。
快速排序将一个数组分成两个子数组并对这两个子数组独立地排序,两个子数组有序时整个数组也就有序了。递归调用发生在处理整个数组之后。
Robert Sedgewick 在其网站上对Quicksort做了较为完整的介绍,建议去围观下。
public class Solution {
/**
* @param A an integer array
* @return void
*/
public void sortIntegers2(int[] A) {
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) {
// key point 3: A[left] < pivot not A[left] <= pivot
while (left <= right && A[left] < pivot) {
left++;
}
// key point 3: A[right] > pivot not A[right] >= pivot
while (left <= right && A[right] > pivot) {
right--;
}
if (left <= right) {
int temp = A[left];
A[left] = A[right];
A[right] = temp;
left++;
right--;
}
}
quickSort(A, start, right);
quickSort(A, left, end);
}
}