排序算法是算法学习的第一步,想当初我学的第一个算法就是选择排序,不过当时很长一段时间我都不清楚我到底用的是选择还是冒泡还是插入。只记得两个for一个if排序就完成了。
再后来更系统地接触算法,才发现那才是排序算法队伍中小小而基本的一员。
买的《算法导论》一直没有认真地看一看,下来要找实习找工作,为了做准备,也是为了复习一下算法,便扒出来好好学一学,并做一些记录,免得我金鱼般的记忆使我看了和没看一样。
快速排序
快速排序用到了分治思想,同样的还有归并排序。乍看起来快速排序和归并排序非常相似,都是将问题变小,先排序子串,最后合并。不同的是快速排序在划分子问题的时候经过多一步处理,将划分的两组数据划分为一大一小,这样在最后合并的时候就不必像归并排序那样再进行比较。但也正因为如此,划分的不定性使得快速排序的时间复杂度并不稳定。
快速排序的期望复杂度是O(nlogn),但最坏情况下可能就会变成O(n^2),最坏情况就是每次将一组数据划分为两组的时候,分界线都选在了边界上,使得划分了和没划分一样,最后就变成了普通的选择排序了。
快速排序分为三步分治过程,划分,解决,合并。
分解是将输入数组A[l..r]划分成两个子数组的过程。选择一个p,使得a被划分成三部分,分别是a[l..p-1],a[p]和a[p+1..r]。并且使得a[l..p-1]中的元素都小于等于(或大于等于)a[p],同时a[p]小于等于(或大于等于)a[p+1..r]中的所有元素。
解决是调用递归程序,解决分解中划分生成的两个子序列。
合并是递归到最深层,已经不能再划分成更小的子序列了,便开始合并。因为在分解的时候已经比较过大小,每一个父序列分解而来的两个子序列不仅是有序的,而且合并成一个序列之后还是有序的。因为快排可以在输入数组上进行操作,所以合并这一步不需要编写代码。
《算法导论》上称这样的排序为原址排序,即在原数组上操作就可以完成排序,不需要临时数组。
书上的代码非常简洁巧妙,我就不把书上的伪代码照抄上来了,这里给出Java的实现代码以供参考:
//快速排序
public static void QuickSort(int[] a, int left, int right) {
if (left < right) {
int p = partition(a, left, right);
QuickSort(a, left, p - 1);
QuickSort(a, p + 1, right);
}
}
//快速排序数组划分
private static int