1.快速排序的引入
快速排序是从冒泡牌序演变而来的算法,但是要比冒泡排序高效很多,所以叫做快速排序。
快速排序 |
---|
排序之所以快速,是因为此算法采用分治法。同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。 |
不同的是,冒泡排序在每一轮只将一个元素冒泡到数组的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列的一端,比它小的元素移动到数组的另一端,从而将数组拆分为两部分。 |
2.快速排序的排序思想
2.1 快速排序的核心思想:分治法
每一次数组分为两部分,到底有什么好处?
给定假设8个元素的数组,一般情况下冒泡排序需要比较8轮(实际上比较7轮数组已经为有序数组),每一轮将一个元素移动到数组的一端。
快速排序的流程是:
如图在分治法的思想下,原数组在每一轮被拆分为了两部分,每一部分的下一轮又分别被拆分名为了两部分,直到不可再分为止。
这样一共需要多少轮呢?平均情况下需要logn轮,因此快速排序算法的时间复杂度为O(nlogn)。
2.2 快速排序的核心:基准元素的选择与移动
分治法极大的缩减了排序过程中比较的次数,但基准元素的选择与移动都是快速排序的核心问题。
2.2.1 基准元素的选择
基准元素,英文pivot,用于在分治过程中以此为中心,把其他元素移动到基准元素的左右两边。
那么基准元素该如何选择呢?
最简单的方式是选择数组的第一个元素:
这种选择的方式大部分情况下是没有问题的,但当给定的数组是一个逆序的数组,想要把这个数组转换为正序的数组,就会发生下面的情况。
观察上图我们可以看出整个数组并没有被分成一半一半,每一轮仅仅是确定了基准元素的位置。这种情况下,数组的第一个元素不是最小值就是最大值,无法发挥出分治法的优势。在这种极端的情况下,快速排序要经过N轮排序,时间复杂度变为了O(N^2)。
但通常上我们可以尽量避免此种情况的发生,采取的方式是我们可以不选择数组的第一个元素,而是随机选择一个元素作为基准元素。
这样即使数组处于完全逆序的情况下,也可以有效地将数组分成两部分。
当然即使是随机选择基准元素,也有很小的概率选到最小值或者最大值作为基准元素,会影响到分治的影响。
所以快速排序的平均时间复杂度为O(nlogn),最坏情况下(选择最小值或者最大值作为基准元素)时间复杂度为O(n^2)。
2.2.2 元素的移动(挖坑法)
快速排序的算法思想 |
---|
快速排序的算法思想是分治法,比大小,再进行分区。1.首先从数组中取出第一个数作为基准数 2.将这个数大于或者等于的数全放在它的右边,小于它的数放在它的左边。 3.再对左右区间重复操作第二步,直到各区间只有一个数 |
这里元素的移动采用的是挖坑法,这里需要将挖坑法的具体实现思路进行说明。
挖坑填数的实现思路 |
---|
1.将基准数挖出形成第一个坑 |
2.由后往前找比它小的数,找到后挖出此数,填到前一个坑中 |
3.由前往后找比它大于或等于的数,找到后,也挖出此数,填到前一个坑中 |
4.在重复进行执行第二步与第三步的操作 |
3.快速排序的代码实现
对于数组{5,3,9,1,6,7,2,4,0,8}进行快速排排序
挖坑填数第一轮:
如此进行重复挖坑填数的第2与第3步操作,即可完成最终的顺序
public class QuickSortUtils {
//快速排序
public static void quickSort(int []arr,int start,int end){
/*挖坑填数:得到基准数所在索引位置,以这个位置分成左右两个区。
然后对左右两区进行递归调用。
*/
if(start<end){
//获取分成左右两区基准数所在的索引位置
int index= getIndex(arr,start,end);
//对左区进行递归
quickSort(arr,start,index-1);
//对右区进行递归
quickSort(arr,index+1,end);
}
}
/*
挖坑填数:1.将基准数挖出来形成第一个坑
2.由后往前找到比它小的数,找到后挖出此数填到前一个坑
3.由前往后找到比它大于等于的数,找到后也挖出此数填到前一个坑
4.再重新执行2.3操作步骤
*/
private static int getIndex(int[] arr, int start, int end) {
int i=start;
int j=end;
//定义基准数
int x=arr[i];
//重复2,3步骤
while(i<j){
//2.由后往前找比它小的数,找到后挖出此数填到前一个坑中
while(i<j&&arr[j]>=x){
j--;
}
//找到后挖出此数,填到前一个坑中
if(i<j){
arr[i]=arr[j];
i++; //找到后让i顺便递增下
}
//3.由前往后找到比它大或等于的数,找到后也挖出此数,填到前一个坑中
while(i<j&&arr[i]<x){
i++;
}
//找到后也挖出此数填到前一个坑中
if(i<j){
arr[j]=arr[i];
j--;//顺便让j再减一下
}
}
//把基准数填到最后一个坑中
arr[i]=x;
return i;//返回基准数所在的索引位置
}
}
import java.util.Arrays;
public class MyTest {
public static void main(String[] args) {
int [] arr={
5,3,9,1,6,7,2,4,0,8};
QuickSortUtils.quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
}
运行结果: