为什么说它实用性最强呢?因为快速排序的平均性能非常好,虽然它的最坏运行时间为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]已排序。(这里没有合并过程,和合并排序有很大不同。关于合并排序)
快速排序代码如下:
/**
* 快速排序
* @param A 目标数组
* @param p 左标记位
* @param r 右标记位
*/
public 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 切割位
*/
public 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在一个包含了八个元素数组上的操作。
所以说,其实数组被分成了四个部分:
以上就是快速排序的内容。比较简单,图片来自于《算法导论》
但是!!但是其实有个坑
在做阿里笔试题的时候发现了。尼玛它的快速排序怎么感觉和我的不一样。百度了一下,偶,果然不一样。。下面这个排序的思路来自数据结构(严蔚敏),代码如下:
/**
* 快速排序 来自数据结构(严蔚敏)
* @param A 目标数组
* @param left 左标记位
* @param right 右标记位
*/
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相等。
在不申请额外空间的情况下,竟然直接赋值,我一开始看到这个的时候是吃惊的。真相其实是用一个key(第一个基准值)来存一个数据,这样之后的数组里就一直会有一个空的位置用来赋值,最后等主要工作完成,再将key赋值给最终空的位置,就这样实现了一波交换。(相当的巧妙啊!!,_(:з」∠)_我是服的)
上面有所有方法的单元测试:https://github.com/qjkobe/IntroductionToAlgorithms
原文:http://blog.csdn.net/qj30212/article/details/52474837
我略微修改了一下代码和描述