什么是快速排序
其实快速排序是对冒泡排序的一种改进,它的基本思想就是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据都要小,再按这种方式对这两部分的数据分别进行快速排序,整个排序过程可以递归进行,示的整个数据变成有序序列。
快速排序的原理:
排序算法的思想非常简单,在待排序的数列中,我们首先要找一个数字作为基准数,为了方便,我们把大于基准数的元素移动到左边,把大于基准数的元素移动到右边。这时,左右两个分区的元素就相对有序了;接着把两个分区的元素分别按照上面两种方法继续对每个分区找出基准数,然后移动直到各个分区只有一个数为止。
这是典型的分治思想,即分治法。下面我们对一个实际例子进行算法描述来说明一下快速排序的排序步骤。
以47 29 71 99 78 19 24 47的待排序数列为例进行排序,为了方便区分两个47,我们对后面的47增加一个下划线,即待排序的数列为47 29 71 99 78 19 24 47
首先,我们需要在数列中选择一个基准数,我们一般会选择中间的一个数或者头尾数。这里我就选择第一个数47作为基准数,接着把比47小的数移动到左边,把47大的数移动到右边,对于相等的数字不做移动。所以实际上我们需要找到中间的某个位置k,这样k左边全部比k上的值小,k右边的值全部比k上的值大。
接下来开始移动元素,怎么移动呢?其实冒泡排序也涉及到元素的移动,但是冒泡排序的移动很累,比如把最后一个元素移动到第一个,就需要比较n-1次,同时交换n-1次,效率很低。其实,只需要把第一个元素和最后一个元素交换就好了。之前说快速排序是对冒泡排序的一种改进,就是这个原因。
快速排序的操作是这样的:首先从数列的右边开始往左边找,我们设这个下标为i,也就是进行i--操作,找到第一个比基准数小的值没让它与基准数交换;接着从左往右找,设这个下标为j,然后执行操作(j++),找到第一个比基准数的大的值,让它与基准数交换;然后继续寻找,知道i与j相遇时结束,最后基准数所在的位置即k的位置,也就是说k左边的值均比k上的值小,而k右边的值都比k上的值大。我们可以先看看示意图:
所以对于上面的数列47 29 71 99 78 19 24 47,进行第一趟排序第一个交换的排序情况如下,第1次的操作情况如图所示:
交换之后,基准数47移动到了下标为6的位置,对i继续扫描,如下图所示:
-----------------------------------------------------------------------------------------------------------
第二次发现可交换的值
此时交换后的数列变为24 29 47 99 78 19 71 47,接下来继续对i、j进行操作,如下图所示,继续进行i--及j++的比较操作。
------------------------------------------------------------------------------------------------------------
进行了i j的移动和比较,交换之后,我们最终得到的数列是24 29 19 47 78 99 71 47,接下来我们继续进行i--的操作,发现在i为4时比47大不用交换,,在i=3时i与j相遇,这时就不用继续移动和比较了,已经找到k了,并且k的值为3,我们可以已经可以确认k左边的值比47小,右边的值比47大(由于要保持相对位置不变,所以47同样在基准值47的右边)。
47这个值已经落到了它该在的位置,第一趟排序已经完成,接下来就是以k为基准,分为两部分,然后在左右两部分分别执行上述排序操作,最后数据会分为4部分,;接着对每个部分进行操作,知道每个部分都只有一个值为止。
接下来进行第二趟排序,现在左边部分为24 29 19,我们选择1一个数24位基准数,接着进行i--、j++操作,我们发现i最初的值为19,比24这个基准书要小,所以与基准数交换,得到数列为19 29 24;j为1时,我们的发现29比24大,所以进行交换,得到数列为19 24 29,此时i为2,j为1;继续i--发现i为1与j相遇,左边部分的数列的k为1,并且左右两部分分别只有一个元素,此时第二趟排序的左边部分已经排序完毕,同时左边部分的所有数都已经完成排序。
接着看右边的部分,待排序的数列为78 99 71 47,我们同样选择第一个数78为基准数,接下里进行i j移动与比较,发现47比78小,进行交换,得到的数列为47 99 71 78,从左往右发现99比基数47大,进行交换,德奥数列为47 78 71 99,继续从右往左看,发现71比基准数78小,进行交换,得到47 71 78 99;此时i在整体数组中的下标为6,j为5,继续j++则与i相遇,所以完成次轮排序
此时右边数列的k为6,一般是相遇的位置,也就是基准值所在的位置,这是数列又被分为两部分,左边是47 71,右边是99,则需要继续对左边部分的数据进行排序,虽然只有两个数据,但是我们还是要按照快速排序的思想进行操作下去,选择47为基准数,将i进行从右向左的移动和比较,发现i与j相等时没有产生移动,完成第2轮排序。
至此,所有的排序都已经完成,最终数列的结果是19 24 29 47 47 71 78 99.
快速排序的代码实现
其实快速排序有一个比较简单的思想,那就是递归。对于每一趟排序都是一样的思路,只不过需要进行排序的数组范围越来越小了,使用递归排序实现这种排序是最合适不过了。
public class QuickSort {
private int[] array;
public QuickSort(int[] array){
this.array = array;
}
public void sort(){
}
public void print(){
for(int i=0;i<array.length;i++){
System.out.println(array[i]);
}
}
public void quickSort(int[] src,int begin,int end){
if(begin <end){
int key = src[begin];
int i =begin;
int j = end;
while(i<j){
while(i<j && src[j] >key){
j--;
}
if(i <j){
src[i] = src[j];
i++;
}
while(i < j && src[i] <key){
i++;
}
if(i<j){
src[j] = src[i];
j--;
}
}
src[i] = key;
quickSort(src,begin,i-1);
quickSort(src,i+1,end);
}
}
}
快速排序算法的特点以及性能分析
快速排序在最坏的情况下时间复杂度和冒泡排序一样,是O(n2),实际上每次比较都要需要交换,但是这种情况并不多见。快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级别的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中消耗一定的空间,在一般情况下的空间复杂度为O(logn),在最差的情况下,若每次只完成了一个元素的排序,那么空间复杂度为O(n),所以我们一般认为快速排序的空间复杂度为O(logn)。
另外,快速排序是一个不太稳定的算法,在经过排序后,可能对相同的值的元素的相对位置造成改变。
快速排序基本上被认为是相同数量级的所有排序算法中,平均性能最好的。