在今年的畢業生就業經驗交流會上,聽到一位師兄分享自己的面試心得。其中有一個面試官的問題是(好像是阿里的),如何優化快排算法?
在這里整理一下我的看法和答案。
快速排序的基本思想:
快速排序使用分治的思想,通過一趟排序將待排序列分割成兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小。之后分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的。
快速排序的三個步驟:
(1)選擇基準:在待排序列中,按照某種方式挑出一個元素,作為 “基準”(pivot)
(2)分割操作:以該基準在序列中的實際位置,把序列分成兩個子序列。此時,在基準左邊的元素都比該基準小,在基準右邊的元素都比基準大
(3)遞歸地對兩個序列進行快速排序,直到序列為空或者只有一個元素。
優化的思想:
- 對尋找基準的方法進行優化 (標準的方式是 左邊的第一個作為基準,但是還有更好的方法)
- 對遞歸進行優化 (使用尾遞歸的思想)
- 使用并行或多線程處理子序列
三種選擇基準的方法:(3種)
方法1:固定位置
思想:取序列的第一個或最后一個元素作為基準
方法2:隨機選取基準
思想:取待排序列中任意一個元素作為基準
方法3:三數取中(median-of-three)
舉例:待排序序列為:8 1 4 9 6 3 5 2 7 0
左邊為:8,右邊為0,中間為6.
我們這里取三個數排序后,中間那個數作為樞軸,則樞軸為6
這三種方法里面,使用三數取中選擇樞軸優勢還是很明顯的,但是還是處理不了重復數組。
方法二:并行或多線程處理子序列
利用多線程的思想
方法三:尾遞歸的思想
這是我網上看到的很多人說快排使用尾遞歸的思想,可以進行優化。但是看見很久,我個人感覺這個并不是尾遞歸的思想,只是普通的遞歸。
尾遞歸的定義是:
尾遞歸是用來優化遞歸算法空間復雜度的,其原理是當遞歸調用出現在函數的最后一步時,編譯器就可以丟棄當前函數的調用幀,這樣,整個遞歸過程中,就僅僅存在一個調用幀,這樣就減小了內存的消耗。
但是下面的代碼卻是:
在進入第一次遞歸調用時,由于后續還要進行start=index+1;所以這個并不是尾遞歸的思想。
這是stackoverflow上面的對于這個問題的回答,他也認為快排沒有辦法使用尾遞歸進行優化:http://stackoverflow.com/questions/9247504/how-to-implement-tail-recursive-quick-sort-in-scala#comment11651078_9247504
網上流行的尾遞歸的快排代碼
int Partition(int *p,int len,int start,int last)
{
int flag=*(p+start);
int i=start;
int j=last;
while(i<j)
{
while(i<j && *(p+j)>flag) --j;
*(p+i)=*(p+j);
while(i<j && *(p+i)<=flag) ++i;
*(p+j)=*(p+i);
}
*(p+i)=flag;
return i;
}
void QuickSort(int *p,int len,int start,int last)
{
if(NULL=p) return;
int index;
while(start<last)
{
index=Partition(p,len,start,last);
QuickSort(p,len,start,index-1); //尾遞歸
//QuickSort(p,len,index+1,last);
start=index+1;
}
} ```
關于數據結合和算法的其他文章:
這又是一個flag http://www.lxweimin.com/p/1dc543da3897