在做一道劍指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的話,這個時候和“填坑思路”的效果就是一樣的了。