快速排序详解
快速排序详解
摘要:快速排序是一种使用到了递归的重要排序方法,其具备比较低的平均时间复杂度,但是它的最坏情况时间复杂度并不是特别优秀。在某些情况下存在使用快速排序的必要,因此其作为主要的八大排序还是有很大的学习价值的,在此我们详细分析整理快速排序。
1.快速排序过程详解
快速排序首先需要我们定义一个基准数,对于这个基准数,我们通常让待排序数组中的第一个元素作为基准数,实际上谁做基准数都行,但是让第一个元素作为基准数最保险,代码写起来也最简单,因此我们定义第一个数字为基准数,如图所示,为初始的待排序数组,我们将数组中的首元素7定义为了基准数:
之后我们设定两个游标,分别指向数组中的首元素和末尾元素,如图所示:
定义好两个游标之后,我们让两个游标开始进行各自的移动以及操作,我们先让游标j往前移动,直到它指向了一个值比基准数小的元素方才停止;游标j停止移动后,我们让游标i开始往后移动,直到其指向了一个值比基准数大的元素方才停止,在两个游标游移的过程中,要时刻检测二者是否发生了相遇,若发生了相遇,二者则应该立即停止游移,并退出移动循环,我们在图中展示这个过程:
如上图所示,两个游标均指向了正确的位置,且经检测二者没有相遇,因此此时还不能退出游移循环。在双方都指向了符合条件的位置并停止游移之后,在循环内将发生另一个行为,那就是二者指向的数字交换,如图所示:
在交换之后我们继续让游标进行游移,直到他们在此指向相应的位置,如图所示:
之后我们再次进行交换:
并让两个游标继续移动,我们发现,再让两个游标继续移动的话,二者就会发生相遇了,两个游标的指向情况可能为下图所示:
在此想必某些人会产生一个疑问:为何二者的相遇位置是在这里,为何是j多向前进了一步而i没有,我们是否可以让相遇位置在此时指向19的位置呢,是否可以让i向后移动一次,而j游标不变呢?答案是可以的,在这里我们的最终游标相遇位置是和具体代码书写相关的,但是也不是可以随意改动的,我们需要注意谁先移动谁后移动,是和我们的基准数设置位置相关的。这里为了便于理解我们假象一个例子:如果存在一种情况,两个指针均指向了自己应该指向的指针,并退出了自己的游移内循环,且此时二者应该发生了交换,在交换后,i指针指向的是比较小的那个数字,j指针指向的是比较大的那个数字,此时再进行一次外循环,那么,如果j先行,那j就会指向那个比较小的数字,那个比较小的数字一定是小于基准数的,因此如果j先行,我们想让基准数和当前游标互换位置之后,基准数之前的数组仍然是一个有序数组,那么基准数就应该是数组首元素,而如果我们让i先行,那么基准数就应该是数组尾元素。因此谁先行,是和基准数的位置设定相关的。
在二者相遇之后,我们会让基准数和二者指向位置的元素互换,因而变成下面的样子:
这个过程实际上是在进行一个分类,它会保证所有比基准数小的元素统统在当前基准数的前边,而所有比基准数大的元素统统在基准数后边,这里实际上就是为基准数确定了正确的位置,不管前边的数字怎么排,基准数确实已经抵达了正确位置,它不用再变动了,因为它前面的数字确确实实已经比它都小,前边的数字怎么排序,也不会再影响到它,而同理,其后边的元素们也不会影响到它,因此当前的基准数已经被排好序了,我们不用再管他了。
在此之后我们便发现了这个排序中规律了,我们先排序好一个数字之后,它便将整个数组一分为二的划分成了一前一后两个无序数组,我们再对前边的数组进行同样的排序即可,这里就涉及到递归了,在此为每个数组重复这个过程,直到我们检测到数组中还有一个值的时候停止,此时说明这个数组已经有序。关于这里的理解,我认为可以使用一个以小见大的例子作为理解,如下图,我们对一个无序数组2,1进行排序:
我们让数值进行互换:
互换之后我们在此移动游标检测相遇:
由于基准数和游标指向了同一数字,因此交换是一次无意义交换,这时这个数组分为了两个数组,其中一个是基准数前边的比基准数小的无序数组,另一个是比基准数大的无序数组,我们分别对这两个数组进行研究分析,在研究分析中我们发现比基准数小的无序数组仅在逻辑上存在,实际上不存在,而比基准数大的那个数组在物理上存在但是它只有一个元素了,仅包含一个元素的数组我们认为是有序的,因此它也已经有序了。
在这里的递归过程中,我们是在递归中为每一个元素确定其准确的位置,而不顾其前后的无序数组,当递归规模小到一定程度的时候,如仅有三个节点的时候:
我们会发现我们根本无需研究前后的数组了,因为不用看也知道它们各自是有序的不用再排序了,所以此时我们要为它们设定一个终止条件,那就是检测到一个数组中仅有一个节点的时候,我们便终止这个检测过程。
在递归中也是如此,当我们的递归研究对象的规模缩小到3或者2的时候,在定位好基准数之后,它就已经是一个有序数组了,根据数学原理我们是知道的。因此这时我们终止递归就可以了,不用再继续向下排序了。
因此对于快速排序的过程,我们就可以理解为:先确定一个数字的准确位置,再递归的分析它分出来的两个数组,直到递归规模缩小为1停止,最终整个数组会趋于有序。
2.快速排序代码分析
import java.lang.reflect.Array;
import java.util.Arrays;
public class FastSort{
public static void main(String[] args){
int[] arr = {4,1,2,3,5,14,23,12,7,14};
quickSort(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr, int left, int right) {
if(left >= right) {//终止条件,如果左边界下标等于有边界下标,或者大于右边界下标的时候,说明当前数组的长度已经是1乃至该数组物理上不存在了,因此就直接返回,终止递归,这里是递归出口。
return ;
}
int i = left,j = right,base = arr[i];//根据传入的左右边界声明出两个游标,同时指定好基准数,我们命名为base
while(i!=j) {//只要左右游标不相等,说明二者没有相遇,循环就会继续执行
while(i<j && arr[j] >= base) {//j游标先行,只要j游标指向的数值大于基准数,j游标就开始游移,需要注意的是在j游标游移的过程中也会时刻注意到i和j游标是否相遇了,只要相遇就停止
j--;//j游标是往前走
}
while(i<j && arr[i] <= base) {//i游标后走,只要是i游标指向的数值小于基准数,i游标就开始游移,需要注意的是i游标在游移的过程中也会时刻注意到i和j游标是否相遇了,只要相遇就停止
i++;
}
int temp = arr[j];//在退出上边的外循环之后,说明二者均指向了需要被交换的元素,也就是说i指向了一个大于基准数的元素,j指向了一个小于基准数的元素,因此二者发生交换。如果此时两数不是因为这种指向情况而导致的交换,是由于相遇导致的交换,那么这个交换将没有意义
arr[j] = arr[i];
arr[i] = temp;
}
arr[left] = arr[i];//与基准数交换,此时我们已经找到了基准数的准确位置,我们将基准数与当前位置上的元素进行一次交换
arr[i] = base;
quickSort(arr, left, i-1);//递归的处理当前两个游标位置的右边无序数组
quickSort(arr, i+1,right);//递归的处理当前两个游标位置的左边无序数组
}
}