1. 程式人生 > >快速排序算法真的正確嗎?-試試120,100,105,103,118 從大到小排列

快速排序算法真的正確嗎?-試試120,100,105,103,118 從大到小排列

問題 code 標準 交流 ext 時也 一位數 判斷 插入

快速排序算法是常用的排序算法之一,一次偶然的機會我發現快速排序算法存在一些問題,開始我以為只是我的這版教材有問題,後來才發現網上所有的快速排序算法都是這樣的。

先來說說快速排序的思想吧

選取一個基準,把一個數組邏輯上分割成兩個,使用遞歸把數組一直細分,解釋的比較簡單,如果還不理解這個算法就去先學習下快速排序算法,今天我主要講的是它存在的問題

下面我寫一個目前所用的從大到小的快速排序的代碼(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高階學習資料!

快速排序算法真的正確嗎?-試試120,100,105,103,118 從大到小排列