1. 前言
排序算法是數(shù)據(jù)結(jié)構(gòu)中最基礎(chǔ)的算法,快速排序則是面試中最常見的排序算法。無(wú)論是校招面試還是社招面試,快速排序算法的出現(xiàn)頻率遠(yuǎn)高于其他算法,而且經(jīng)常會(huì)要求候選人白板手寫實(shí)現(xiàn)算法??焖倥判蛩惴ǖ暮诵氖欠种翁幚?,重點(diǎn)是分析時(shí)間復(fù)雜度。
2. 快速排序算法
面試官提問(wèn):快速排序算法是怎么實(shí)現(xiàn)的?能手寫實(shí)現(xiàn)一個(gè)快排算法嗎?
題目解析:
為了實(shí)現(xiàn)bug free(基本沒(méi)有邏輯缺陷)的白板編程,候選人可以將解決這個(gè)題目的過(guò)程分為兩個(gè)步驟:
(1)分析快速排序算法的步驟,并且編碼實(shí)現(xiàn);
(2)完成編碼后,使用一個(gè)小規(guī)模的數(shù)據(jù)作為測(cè)試樣例,模擬算法流程驗(yàn)證代碼邏輯是否符合預(yù)期。
2.1 快速排序步驟
快速排序算法的核心是分治算法,所謂分治(Divide and Conquer)就是將一個(gè)復(fù)雜的問(wèn)題分成兩個(gè)或者多個(gè)相同或者相似的子問(wèn)題,再把這些子問(wèn)題分成多個(gè)相同或者相似的子問(wèn)題,直到子問(wèn)題能夠被簡(jiǎn)單求解,把子問(wèn)題的解合起來(lái)就是原始問(wèn)題的解。
快排的分治思維在于將大的數(shù)組拆分為兩個(gè)需要排序的左右子數(shù)組,再對(duì)子數(shù)組套用相同算法,直到子樹組只有一個(gè)數(shù)字,一個(gè)數(shù)字的數(shù)組本身就是有序的,構(gòu)成了原子問(wèn)題的解??炫诺暮诵牟襟E拆分為:
(1)選擇基準(zhǔn):對(duì)于數(shù)組 A,選擇一個(gè)數(shù)字作為基準(zhǔn)值(pivot),習(xí)慣選擇數(shù)組第一個(gè)元素作為 pivot;
(2)構(gòu)造分區(qū):定義 left 和 right 左右指針,將小于基準(zhǔn)的元素移動(dòng)到左邊,將大于基準(zhǔn)的元素移動(dòng)到右邊;
(3)遞歸求解:對(duì)于基準(zhǔn)左邊的數(shù)組 A1、基準(zhǔn)右邊的數(shù)組 A2 重復(fù)第一步和第二步,直到所有的子樹組已經(jīng)滿足排序性質(zhì)(子數(shù)組為空或者子樹組只有一個(gè)元素)。
快速排序的算法實(shí)現(xiàn)代碼以及解析,示例:
public void quicksort(int[] A,int left, int right) {
//1. 遞歸終止條件,如果不構(gòu)成數(shù)組結(jié)束算法
if(left > right)
return;
int i, j, t, pivot;
//2. 選擇第一個(gè)元素作為pivot
pivot = A[left];
i = left;
j = right;
while(i != j) {
//3.1 這里要注意順序,必須先從右邊開始找到第一個(gè)比pivot小的數(shù)
while(A[j] >= pivot && i < j)
j--;
//3.2 然后從左邊找到第一個(gè)比pivot大的數(shù)字
while(A[i] <= pivot && i < j)
i++;
//3.3 交換兩個(gè)數(shù)在數(shù)組中的位置
if(i < j) {
t = A[i];
A[i] = A[j];
A[j] = t;
}
}
//4. 最終將基準(zhǔn)數(shù)歸位
A[left] = A[i];
A[i] = pivot;
//遞歸處理左邊的分?jǐn)?shù)組
quicksort(A,left, i-1);
//遞歸處理右邊的分?jǐn)?shù)組
quicksort(A,i+1, right);
}
2.2 小型數(shù)組模擬
下面我們使用一個(gè)小規(guī)模的數(shù)組模擬快速排序的過(guò)程,原始數(shù)組A的順序是22、11、44、10、33。
(1)首先給定初始值:pivot = nums[left] = 22,left=0,right=4。
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
數(shù)組值 | 22 | 11 | 44 | 10 | 33 |
指針 | left | right |
(2)從右到左找到 index=3 的位置,nums[3] 比 pivot 小;從左到右找到 index=2 的位置,nums[2] 比 pivot 大。
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
數(shù)組值 | 22 | 11 | 44 | 10 | 33 |
指針 | left | i | j | right |
(3)交換坐標(biāo) i 和 j 所在的數(shù)組值。
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
數(shù)組值 | 22 | 11 | 10 | 44 | 33 |
指針 | left | i,j | right |
(3)坐標(biāo) i 和 j 碰頭,本次查找結(jié)束,交換 pivot 的值和 nums[i] 的值。
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
數(shù)組值 | 10 | 11 | 22 | 44 | 33 |
指針 | left | i,j | right |
(4)本次查詢完全結(jié)束,對(duì)坐標(biāo)[0,1]的子數(shù)組和坐標(biāo)[3,4]的子樹組進(jìn)行相同的遞歸操作,結(jié)束之后,數(shù)組A得到結(jié)果10、11、22、33、44。
2.3 快排考察點(diǎn)
關(guān)于快排,候選人需要注意幾個(gè)關(guān)鍵的考察點(diǎn):
(1)快排的平均時(shí)間復(fù)雜度為O(N*logN)
,最壞狀態(tài)下時(shí)間復(fù)雜度為O(N^2)
,一般不會(huì)涉及具體的數(shù)學(xué)證明;
(2)快排不是穩(wěn)定排序算法,例如原始數(shù)組存在兩個(gè)相同的數(shù)值,排序可能會(huì)交換兩個(gè)值的順序;
(3)快速排序的核心思路是分治算法,實(shí)現(xiàn)方式是遞歸。
3. 小結(jié)
本章節(jié)介紹了面試最常見的排序算法算法:快排的算法思路以及使用 Java 實(shí)現(xiàn)了一個(gè)快排的算法模板。候選人可以使用小規(guī)模的數(shù)組測(cè)試現(xiàn)場(chǎng)編寫的算法是否符合預(yù)期,特別需要注意快排的時(shí)間復(fù)雜度。