快速排序算法是常用的排序算法之一,一次偶然的机会我发现快速排序算法存在一些问题,开始我以为只是我的这版教材有问题,后来才发现网上所有的快速排序算法都是这样的。
先来说说快速排序的思想吧
选取一个基准,把一个数组逻辑上分割成两个,使用递归把数组一直细分,解释的比较简单,如果还不理解这个算法就去先学习下快速排序算法,今天我主要讲的是它存在的问题
下面我写一个目前所用的从大到小的快速排序的代码(java版)
测试类
public class Test{
public static void main(String[] args){
Scanner read=new Scanner(System.in);
System.out.println(“请输入测试5用例:”);
int[] data=new int[5];
for(int i=0;i<5;i++)
data[i]=read.nextInt();
quickSort(data,0,4);
System.out.println(“排序结果:”);
for(int i=0;i<5;i++)
System.out.print(data[i]+" ");
System.out.println();
}
public static void quickSort(int[] R,int low,int high){
int base=R[low];
int i=low+1;
int j=high;
int temp=0;
while(i<j){
while((i<j)&&(R[j]<=base))
–j;
while((i<j)&&(R[i]>=base))
++i;
if(i<j){
temp=R[i];
R[i]=R[j];
R[j]=temp;
}
}
if(R[j]>R[low]){
temp=R[low];
R[low]=R[j];
R[j]=temp;
}
if(i-low>1)
quickSort(R,low,i-1);
if(high-j>1)
quickSort(R,j+1,high);
}
}
我们先试一组的数据
结果没问题我们再换一组数据测试
结果并没有像我们想象的那样,老师的答复是这是一个老算法应该不会存在错误的。我决定从算法的本质入手苦思冥想了一天终于有了答案
第一轮比较我们发现base右边的数都比base小所以一直移动j,一直到 i=j=low+1时就算比较完成,我们再用j指向的数100和base指向的120比较,发现不需要替换。
后面就到了分组递归环节了,理想状态下每一组递归都会有一位数和基数base替换然后把替换位置左边(low到i+1)的分成一个组,右边(j+1到high)的分成一个组,i和j指向的那个数和基数替换我们视为有序就不参与下面的分组,
算法原本的意图就是每次用基数比较,把比基数大的分成一组,比基数小的分成一组,基数的顺序就排好了就可以固定在这个位置不用参与下面的分组。
算法每一次比较i和j指向的那个数位置固定,当出现极端情况时就会出现致命错误
当右边所有的数都比基数小时,base不需要与i和j指向的数替换位置,应该固定的位置应该是基数本身所在的位置,而不是i和j指向的数100,但是算法却错误的还是把i和j指向的那个数视为有序剔除了,不参与下面的排序,但100虽然比基数120小,但不一定会比100右边的数大,所以这里就存在问题
问题到这就清楚了,快速排序中的每一组排序都需要固定一个数的位置,并把这个数左边分成一组,右边分成一组,这种思路是正确的,但是错就错在每次被固定的这个数到底应该是i和j指向的那个位置还是基数最终所在的位置,结果很显然,因为排序就是按照基数排队,基数在数组中的顺序肯定是可以确定的,所以我们最终分组的标准应该是以基数最终所在位置为分组标准,
也就是以下两种情况下图圈圈所在的位置
也就是说当一轮比较之后发现基数的位置不需要替换时,我们应该以基数为准,将它的左右分组
解决办法也就来了,按照一般的代码编写我们就忽略上图的第一种情况,当基数位置不需要替换时怎么保证排序结果的正确
改正的代码如下
在排序方法中交换基数位置语句前插入一个判断,当基数不需要替换时就把i和j指向基数所在的位置
if(i==low+1&&R[i]<R[low]){
i=low;
j=low;
}
public static void quickSort(int[] R,int low,int high){
int base=R[low];
int i=low+1;
int j=high;
int temp=0;
while(i<j){
while((i<j)&&(R[j]<=base))
–j;
while((i<j)&&(R[i]>=base))
++i;
if(i<j){
temp=R[i];
R[i]=R[j];
R[j]=temp;
}
}
if(i==low+1&&R[i]<R[low]){
i=low;
j=low;
}
if(R[j]>R[low]){
temp=R[low];
R[low]=R[j];
R[j]=temp;
}
if(i-low>1)
quickSort(R,low,i-1);
if(high-j>1)
quickSort(R,j+1,high);
}
我们再测试一下刚才的那组数据,结果正确
当然还有另外一种不是很好的解决办法,就是分组的时候把i和j指向的那个位置划分到某一个组中重新参与排序,不过这样会造成资源浪费,我们只希望它在基数不需要改变位置的时候把i和j指向的位置放到数组中重新排序,当基数位置每次都需要交换时也就是我们理想的情况时就不会出现这种错误。
代码如下
把quickSort(R,j+1,high);改成了quickSort(R,j,high);
public static void quickSort(int[] R,int low,int high){
int base=R[low];
int i=low+1;
int j=high;
int temp=0;
while(i<j){
while((i<j)&&(R[j]<=base))
–j;
while((i<j)&&(R[i]>=base))
++i;
if(i<j){
temp=R[i];
R[i]=R[j];
R[j]=temp;
}
}
if(R[j]>R[low]){
temp=R[low];
R[low]=R[j];
R[j]=temp;
}
if(i-low>1)
quickSort(R,low,i-1);
if(high-j>1)
quickSort(R,j,high);
}
测试结果正确
小编整理了一些java进阶学习资料和面试题,需要资料的请加JAVA高阶学习Q群:701136382 这是小编创建的java高阶学习交流群,加群一起交流学习深造。群里也有小编整理的2019年最新最全的java高阶学习资料!