排序算法有很多,今天我来讲一讲其中实用性最强的一种算法——快速排序。
为什么说它实用性最强呢?因为快速排序的平均性能非常好,虽然它的最坏运行时间为O(n^2),但是平均运行时间是O(nlgn),而且里面隐含的常数因子很小。而且它是原地排序的,所谓原地排序,就是不需要开辟新的数组空间,就可以进行排序。
快速排序基于分治模式,所谓分治模式,就是把问题拆成一个个小问题,直到问题可以解决。以下是分治过程的三个步骤:
分解:数组A[p...r]被划分成两个子数组(可能空)A[p...q-1]和A[q+1...r],使得前者每个元素都小于等于A(q),后者中的每个元素都大于A(q)。
解决:通过递归调用快速排序,对子数组A[p...q-1]和A[q+1...r]排序。
合并:因为两个子数组是就地排序的,所以合并不需要操作。整个数组A[p...r]已排序。(这里没有合并过程,和合并排序有很大不同。关于合并排序)
快速排序代码如下:
void QuickSort(int[]A, int p, int r){
if(p < r){
int q = Partition(A, p, r);
QuickSort(A, p, q - 1);
QuickSort(A, q + 1, r);
}
}
快速排序的核心是Partition过程,代码如下:
/**对子数组A[p...r]进行就地重排
* @param A
* @param p
* @param r
* @return
*/
int Partition(int[] A, int p, int r){
int x = A[r];
int i = p - 1;
for(int j = p; j < r; j++){
if(A[j] <= x){
i++;
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
int temp = A[i + 1];
A[i + 1] = A[r];
A[r] = temp;
return i + 1;
}
下面的图片显示了Partition在一个包含了八个元素数组上的操作。
所以说,其实数组被分成了四个部分:
以上就是快速排序的内容。比较简单,图片来自于《算法导论》
但是!!但是其实有个坑
在做阿里笔试题的时候发现了。尼玛它的快速排序怎么感觉和我的不一样。百度了一下,偶,果然不一样。。下面这个排序的思路来自数据结构(严蔚敏),代码如下:
public void qsort(int[] A, int left, int right){
if(left < right) {
int key = A[left];
int low = left, high = right;
while (low < high) {
while (low < high && A[high] > key) {
high--;
}
A[low] = A[high];
while (low < high && A[low] < key) {
low++;
}
A[high] = A[low];
}
A[low] = key;
qsort(A, left, low - 1);
qsort(A, low + 1, right);
}
}
这个思路的意思是:先选定左边为基准值(你要选右边也可以,后面代码相应改一改),定义low,high,然后先从后向前找,如果找到了比基准值小的,则low下标处等于该值,否则high--,然后再从前往后找,如果找到了比基准值大的,则high下标处等于该值,否则low++,直到low和high相等。这道题令人惊奇的是它交换数据的方式:
每个值被覆盖之前。都被拿走了。首先是基准值,然后之后每个值都是先拿走,然后才可能被覆盖,最后再把基准值放到最后一个被拿走的值里。这样就实现了一波交换。牛逼啊。
如果对代码有疑惑可以看我的github源码,上面有所有方法的单元测试:https://github.com/qjkobe/IntroductionToAlgorithms
如果发现问题,请立刻告诉我。我可不想误人子弟