Java實現快排+小坑+partition的兩種思路


在做一道劍指Offer的題的時候,有道題涉及到快排的思路,一開始就很快根據以前的思路寫出了代碼,但似乎有些細節不太對勁,自己拿數據試了下果然。然后折騰了下並記錄下一些小坑,還有總結下划分方法partition的兩種思路。

 

partition思路1——交換思路

以待排序數組的第一個元素為基准值key,然后兩個指針i和j,先從后面開始找(這個是個坑后面會總結)第一個比基准key小的數字,停下來,然后再從前面開始找第一個比基准key大的數字,停下來。

然后交換這兩個指針的元素。

 

當兩個指針相碰的時候,也就是i>=j了,或者說就i==j了(因為按照代碼應該不存在i>j的情況),就再把a[begin]的值和a[i]或者a[j]的值交換就好了。

大概代碼如下:

int key = a[begin];
        int i = begin, j = end;//坑三i的值
        
        while(i < j) {
            //坑2順序
            //while(i < j && a[i] <= key)i++;//從左邊開始找到第一個比key大的數字然后停下來
            while(i < j && a[j] >= key)j--;//從右邊開始找到第一個比key小的數字然后停下來
            while(i < j && a[i] <= key)i++;//從左邊開始找到第一個比key大的數字然后停下來
            
            swap(a, i, j);
        }
        
        swap(a, begin, i);//最后是i還是j的位置和begin交換都行,因為最后是i==j

 

 

 

partition思路2——填坑思路

想下我們的swap一般是怎么實現的: 

int temp = a[i];
a[i] = a[j];
a[j] = temp;

一開始把i位置的值存在一個地方,這相當於i位置有個坑了,因為其他數字可以覆蓋i位置了。

填坑思路的寫法就是根據這個思想。

 

一樣是以待排序數組的第一個元素為基准,我們將a[begin]的值存在key的位置,然后begin就有個坑了吧。

然后兩個指針,i從begin開始,j從end開始,一開始從后面也就是j從后往前遍歷,找到第一個比key小的數字,然后把這個數字覆蓋在i的位置上,因為i是從begin開始,而begin位置有個坑是可以填的。

好了j的東西既然寫在了i的位置,意味着j的位置也是個坑了,這個時候i就開始往前遍歷,找到第一個比key大的值,然后填在j的坑處,一直以此類推。

 

最后,i和j指針相碰后,只要把key的值填到最后i或者說是j的位置就好了。

 

看個大概代碼:

int key = a[begin];//begin的位置被存了起來,這個時候可以利用begin的位置存其他值了
        
        int i = begin, j = end;
        
        while(i < j) {
            while(i < j && a[j] >= key) j--;
            //一開始進來i就是begin,本來begin的值已經在key那里相當於begin或者說是i這里有個坑所以可以覆蓋
            a[i] = a[j];
            
            while(i < j && a[i] <= key) i++;
            //a[j]的值剛剛已經放在之前的i位置了,相當於j的位置有坑可以覆蓋
            a[j] = a[i];
        }
        
        a[i] = key;//最后填i或j都行了,因為最后一次肯定是i==j了

 

 

 

關於寫快排代碼過程中遇到的小坑

坑1:

這個其實也不是坑拉,就遞歸流程中對結束條件的理解。一開始我就想着begin==end就結束,如果是begin>end就出錯了。

但其實遞歸過程中是肯定會出現begin>end的情況的,因為partition+1和partition-1這一步。

 

所以正確的遞歸終結條件應該直接是begin >= end。    (提醒一下歸並排序中的終結條件是begin == end)

 

 

坑2:

坑2就是到底是先從后面往前找還是先從前面往后面找。

如果是填坑的思路就不會陷入這個坑中,因為你要先填a[i]或者說是a[begin]的坑,那么肯定是先從后往前遍歷。

 

但如果是交換的思路就emmm我就踩遼。

因為如果你是先從前往后找,那么出去第一個循環的條件一定是找到第一個比key大的數字了。(不可能是因為i >=j,因為外層循環確保了i < j才進來);

但這個時候第二個循環可能因為i>=j而出去,那么這個時候,如果指針相碰了,i和j一起指向一個比key大的值,然后和a[begin]交換的話,就會出現一個比key大的值出現在key的左邊,就錯遼。

 

所以應該要先從后往前遍歷這樣就會找到第一個比key小的值就出去循環,第二個循環即使i=j了出去也不怕,因為這個時候和begin交換是沒有問題的。

 

補充:這個while(i < j && a[i] >= key)中的i<j也是少不了的,不然的話有越界的危險

 

 

坑3:

坑3是i是從哪里開始的,這個在填坑思路中也不會出錯,因為第一個坑是在begin那里嘛所以肯定i是從begin開始……

然后我用交換思路的時候又采坑了,就想着反正是比較begin后面的數字嘛,就直接i從begin+1開始。

 

這樣會有什么問題呢?

考慮只有三個數字:11,32,41;這其實已經是有序的了,那么i如果從begin+1開始,那么i會直接原地跳出循環,因為32是第一個比key11大的數字嘛;

然后j從右邊開始遍歷,也會停在32的位置,因為雖然42,32都比key大講道理應該繼續往下遍歷的,但我們條件中還有i < j這一項,所以就停下來了。

然后出去循環,和begin交換——就變成32,11,41的錯誤答案了。

 

所以 i 要從begin開始噢。

 

 

補充小坑:

“交換思路”中,其實還有個小問題,就是條件那里應該只能夠a[i]<=key,而不能是<。

因為如果是小於,那么第一次i就會原地停下,然后和a[j]交換。然后最后key歸位我們是和begin和i交換的方式,但這個時候begin已經不是key了,就會出錯。

實際上,如果改寫成了a[i]<key的話,這個時候和“填坑思路”的效果就是一樣的了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM