转自: https://blog.csdn.net/haelang/article/details/44496387
快速排序用到了分治思想,同样的还有归并排序。乍看起来快速排序和归并排序非常相似,都是将问题变小,先排序子串,最后合并。不同的是快速排序在划分子问题的时候经过多一步处理,将划分的两组数据划分为一大一小,这样在最后合并的时候就不必像归并排序那样再进行比较。但也正因为如此,划分的不定性使得快速排序的时间复杂度并不稳定。
快速排序的期望复杂度是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]中的所有元素。
解决是调用递归程序,解决分解中划分生成的两个子序列。
合并是递归到最深层,已经不能再划分成更小的子序列了,便开始合并。因为在分解的时候已经比较过大小,每一个父序列分解而来的两个子序列不仅是有序的,而且合并成一个序列之后还是有序的。因为快排可以在输入数组上进行操作,所以合并这一步不需要编写代码。
代码实现:
public static void classicQuickSort(int[] arr,int left,int right) {
if(left < right) {
int p = partition(arr,left,right);
classicQuickSort(arr,left,p-1);
classicQuickSort(arr,p+1,right);
}
}
public static int partition(int[] arr, int left, int right) {
//主元
int x = arr[right];
//比主元小的最右元素
int p = left - 1;
for(int i = left ; i < right;i++) {
if(arr[i] <= x) {
p++;
swap(arr,p,i);
}
}
swap(arr, right, p + 1);
return p+1;
}
private static void swap(int[] arr, int p, int i) {
int temp = arr[p];
arr[p] = arr[i];
arr[i] = temp;
}
QuickSort(int[] a,int left,int right)函数没什么好说的,设置递归边界,接下来递归处理左序列,再处理右序列。
下来的partition(int[] a, int left, int right)就比较有意思了。
int x = a[right];这行代码选中一个主元,这里我们每次选择的都是当前序列中最右边那个。int p = left - 1;这行代码保存了一个变量p,用来记录比主元小的所有元素中,在序列中存放的位置最靠右的那个。接下来是个循环,从当前序列的第一个循环到倒数第二个(right-1)元素,来进行和主元比较。因为最后一个已经是主元了,所以就没有必要循环到right了。循环里面先是一个比较if (a[i] <= x)。这里写的是小于等于,更改这个就可以改变序列式由小到大还是由大到小排列。这里则是由小到大排列。如果进入了if语句,则说明a[i](当前元素)比主元小,还记得之前的变量p吗,保存着比主元小的元素最右边的位置,这里先p++,接着把a[i]和a[p]交换,就是说把a[p]右边的元素和当前元素换位置。a[p]右边的元素是什么呢?可能就是当前元素,也可能是比主元大的元素。这样,就完成了比主元小的元素的处理。
可是如果a[i]>x呢,则不进入if执行这两行代码,也就是不动那个比主元大的元素。
这样直到循环结束,整个序列就变成了三部分,从a[left…p]是比主元小的元素,a[p+1…right-1]是比主元大的元素,a[right]则是主元。而我们划分的目的是将主元放在这两个序列的中间,则再执行一行语句swap(a, p+1, right);,将主元和比它大序列的第一个元素互换位置,就大功告成了。
书上的图解非常的清晰:(标号是根据上面代码所标,和书上不太一样,但意思是一样的)
这张图描述了一次划分。浅蓝色部分是不大于主元的部分,深蓝色部分是大于主元的部分。没有颜色的是还未处理的元素,最后的元素则是主元。
快速排序的主要内容差不多就这些了,书上接下来证明了快速排序的正确性,以及计算了其时间复杂度。之后讨论了划分的不平衡性所导致的性能退化。对一个排好序的序列使用上述快排,时间复杂度为O(n^2),而插入排序则仅为O(n)。因此便有了随机化快速排序的出现。
快速排序的随机化版本
上面版本的快排在选取主元的时候,每次都选取最右边的元素。当序列为有序时,会发现划分出来的两个子序列一个里面没有元素,而另一个则只比原来少一个元素。为了避免这种情况,引入一个随机化量来破坏这种有序状态。
在随机化的快排里面,选取a[left…right]中的随机一个元素作为主元,然后再进行划分,就可以得到一个平衡的划分。
实现起来其实只需要对上面的代码做小小的修改就可以了。
public static void randomQuickSort(int[] arr,int left,int right) {
if(left < right) {
int p = randomPartition(arr, left, right);
randomQuickSort(arr,left,p-1);
randomQuickSort(arr, p+1, right);
}
}
public static int randomPartition(int[] arr, int left, int right) {
//主元下标
int x= new Random().nextInt(right - left + 1) + left;
swap(arr, x, right);
return ClassicQuickSort.partition(arr, left, right);
}
public static void swap(int[] arr, int left, int right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
作者:haelang
来源:CSDN
原文:https://blog.csdn.net/haelang/article/details/44496387
版权声明:本文为博主原创文章,转载请附上博文链接!