快排几乎是每次面试必考的问题,实际中应用也很广泛,但是我发现自己看完之后,当时觉得理解了,但是过段时间就忘了。。所以下面就对它进行一次深刻细致的分析,能够随时想起来,至少把它的思路印在脑子里。
- 虽然快排的最坏情况时间复杂度为Ө(n^2);但期望时间复杂度为Ө(nlgn),且隐含的常数因子非常小;快排的另一大优势在于它可以原址排序,这意味着空间复杂度是Ө(1)。
- 快排同样采用了分治思想,即把原数组分成两个子数组后,再在子数组中分别递归调用本身。
但是快排的特色在于分组的方式:每次分组时将产生一个界标,界标左侧是小于等于自己的元素(没有排序,只是混乱的罗列;但也不必排序,只需要他们小于等于界标就好);同时,界标右侧全是大于自己的元素。界标确定后,将不再参与下一次的排序,因为从某种意义上,它在整个原数组中的位置已经是对的了。递归调用分别在界标左右两侧的子数组中进行。本质上,快排就是不断产生界标的过程。
非平凡的,快排每次启动时,把目标(子)数组的最后一个元素当做界标,把它置换到数组合适的位置上。之所以选最后一个,可能是出于操作上的方便,事实上,选取数组中任何一个元素都是可以的,没有太大区别,只是细节上操作不太方便而已。
我想强调的是,当一个数组给定时,只要确定排序方式(从大到小或反之),那么末尾元素在数组中的位置就是确定的(这好像是一句废话),这是因为:数组中所有大于它的元素的个数已经确定了,同时小于等于它的元素个数也是确定的。我们根据这些元素的个数就可以确定末尾元素的位置。每次分组时,快排算法只关心在界标元素左右的元素的个数,并不关心它们是否排好序。我个人认为,这才是快排的核心思想。 伪代码
Quicksort(A, p, r) if(p < r) q = Partition(A, p, r) Quicksort(A, p, q-1) Quicksort(A, q+1, r) Partition(A, p, r) temp = A[r] i = p-1 for j = p to r-1 if A[j] <= temp i = i+1 exchange A[i] with A[j] exchange A[i+1] with A[r] return i+1
可以看出,快排的关键在于分组函数Partition(),它的任务不仅是找到末尾元素pivot在数组中的正确位置将其返回,同时还要在返回之前将处于该位置的元素和pivot对调。第一个任务显然要更费力一些:因为数组中到底有多少个元素小于等于pivot,并不是一眼就能看出来的,所以必须要经过一次遍历才能知道。遍历的目标是前n-1个元素。
在遍历过程中,Partition设置了两个指标:j,代表当前正在遍历的元素的下标;而i,代表小于等于pivot的子数组的末尾元素的下标(某种程度上就是这个子数组的长度)。
只要目标数组中有小于等于pivot的元素,i的值就会不断增长;当然i增长的同时,A[i+1]还会和被选中元素A[j](小于等于pivot)置换。到最后是pivot本身的一次置换。
暂时就这么多吧,我觉得已经很清楚了。
PS:目前已入职宜信近两个月了,这期间一直在恶补java web开发的相关知识,包括各种前后端框架,根本没有时间写博客。。但随着我逐渐熟悉业务,博客更新会慢慢恢复的。
宜信现在换了一个CTO,裁了三分之二的人,并在大规模补充新鲜血液,欢迎各位有志于互联网金融的小伙伴加入,在这里绝对有你发挥的舞台哦~
<–2017.8.4更新 –>
贴一段java代码实现:
public class QuickSort {
public static void main(String[] args) {
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
Logger logger = Logger.getLogger("W");
logger.setLevel(Level.ALL);
logger.addHandler(handler);
logger.log(Level.INFO, "hehe");
}
public static void qsort(int[] arr, int start, int end) {
if (start < end) {
int mid = sort(arr, start, end);
qsort(arr, start, mid - 1);
qsort(arr, mid + 1, end);
}
}
public static int sort(int[] arr, int start, int end) {
int i = start - 1;
for (int j = start; j < end; j++) {
if (arr[j] < arr[end]) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, end);
return i + 1;
}
public static void swap(int[] arr, int start, int end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
}