1. 程式人生 > >Java實現快排+小坑+partition的兩種思路

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

思想 圖片 實現 也會 key 答案 mmm 不存在 指向

在做一道劍指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思路1——填坑思路

想下我們的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開始噢。

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