一、什么是快速排序
快速排序也使用了递归,将数组按照一个基准值(这里去数组的第一个元素),进行循环操作,有两个指针,首先一个指针j从右往左循环,直到找到一个比基准值小的元素,然后停下;一个指针i从左往右循环,直到找到一个比基准值大的元素,然后停下,如果此时这两个指针i<j,就交换这这两个元素,交换完了之后,继续刚才的操作,如果右边的指针和左边的指针重合,将这个值与最开始的基准值交换,此时基准值就称之为归位,基准值的左边数据,都小于基准值,右边的数据都大于基准值,完成第一次排序;
但是此时左右两边的数据未必是有序的,因此要继续进行递归操作,直到都有序,整个数据就有序了。
关于远离可以参考以下链接,做的图很清晰:
快速排序——JAVA实现(图文并茂)
二、java实现快速排序算法
以下代码摘自https://blog.csdn.net/u014241071/article/details/81565148,小小的修改了一下。
import java.util.Arrays;
public class Test {
//arr 需要排序的数组
//low 开始时最左边的索引=0
//high 开始时最右边的索引=arr.length-1
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
//如果low>hight,不符合要求,直接返回
if(low>high){
return;
}
i=low;//左边哨兵的索引
j=high;//右边哨兵的索引
//temp就是基准位
temp = arr[low];//以最左边为基准位
//判断传入的低位索引是否小于高位索引,
//不满足就直接结束,不再进行后续的排序操作
//外部循环,i<j的情况下,一直循环,直到将基准位归位
while (i<j) {
//内部循环1,从右往左循环j,直到找到一个值比基准值大或者i>=j时退出循环
//思考1:因为选择的是左边的值作为基准值,因此一定要从右边开始往左循环,为什么?
//思考2:,为什么要加上条件i<j?
while (arr[j]>=temp&&i<j) {
j--;
}
//内部循环2,从左往右循环i,直到找到一个值比基准值小或者i>=j时退出循环
//思考一下,为什么要加上条件i<j?
//步骤和上面类似
while (arr[i]<=temp&&i<j) {
i++;
}
//能到这一步,说明i,j都已经停下来了
//i确定了一个比基准值大的值,j确定了一个比基准值小的值,或者i>=j时退出的循环
//接下来如果是因为确定了值而跳出了循环就,交换这两个值,如果i>=j时退出的循环,那么不用交换值,直接到将基准与i,j共同所在位置的元素交换这一步
//思考3:内层循环都有i<j的判断了,为什么这里还要加i<j的条件?
if (i<j) {
//z、y 都是临时参数,用于存放左右哨兵所在位置的数据
int z = arr[i];
int y = arr[j];
// 左右哨兵 交换数据(互相持有对方的数据)
arr[i] = y;
arr[j] = z;
}
}
//这时跳出了 “while (i<j) {}” 循环,第一次的归位已经完成
//说明 i=j 左右在同一位置
//最后将基准与i,j共同所在位置的元素交换
//如果i根本没动,直接是j移到基准值处,那么此时i,j,low三值在一起,根本不用交换,直接到递归那一步即可
if(low<i){
arr[low] = arr[i];//或 arr[low] = arr[j];
arr[i] = temp;//或 arr[j] = temp;//temp值最开始保存了基准值
}
//i=j
//这时左半数组<(i或j所在索引的数)<右半数组,但是左右数组还不一定处于有序状态
// 只要用相同的方法 分别处理 左右数组就可以了
//关于这里的递归的理解,后面有解释
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
//测试排序
int[] arr = {9,8,7,6,4,2,1,10,50};
System.out.println("排序前的数组"+ Arrays.toString(arr));
Test.quickSort(arr, 0, arr.length-1);
System.out.println("排序后的数组"+Arrays.toString(arr));
}
}
三、几点需要思考的点
1、左边的值作为基准值,因此一定要右边指针从右边开始往左循环,为什么?
结束排序循环的条件是,两个指针相遇,如果左边指针先向右边循环,如果找到了一个值大于基准值,停下来了,然后右边指针开始向左循环,和这个值相遇了,那么就要将这个值和基准值交换,但是这样肯定是不对的,因为这个值大于基准值,交换后,基准值左边的数就不是全部都小于基准值的,这样是不符合要求的。
因此得出结论如果左边的值作为基准值,一定要右边指针从右边开始往左循环,可自行模拟一下,可行的。
当然如果右边的值作为基准值,要左边指针从左边开始往右循环。
2 、while (temp<=arr[j]&&i<j)和 while (temp>=arr[i]&&i<j) 为什么要加上条件i<j?
代码中while (temp<=arr[j]&&i<j),而不是while (temp<=arr[j]),为什么要要加上条件i<j呢?
如果不加上条件i<j,缺陷是很明显的,例如,我们的j在小于i的时候找到一个值小于基准值,然后i向右循环找到一个值大于基准值,此时还有必要交换两个元素的值吗?再有,他们两个还会相遇吗?所以这是不允许的。
3、内层循环都有i<j的判断了,为什么在交换元素的时候还要加i<j的条件?
内层循环都结束之后,说明i找到了一个元素大于基准值,j找到了一个元素小于基准值,但是还有一种情况那么就是i>=j时退出的循环,既然是因为i=j导致的内层循环结束,那么不用交换值,直接到将基准与i,j共同所在位置的元素交换这一步即可
四、递归流程