[閉目洞察算法系列之一]快速排序
這是第一篇關於演算法的部落格, 我本人對演算法沒什麼深刻見解, 此處只是對別人部落格的再整理, 用自己理解的方式進行表述一遍, 一方面加深印象, 另一方面做知識沉澱,供他日食用。 廢話到此為止, 下面是正題
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
快速排序思想核心分為兩部分:
一是分治, 即選擇一個值X作為標杆, 然後對每個子陣列進行大致排序, 使得該標杆X左邊的值都小於或等於X,, 右邊的值都大於或等於X
注意, 左邊的值只需小於X即可, 至於這些值彼此之間不一定是有序的。
二是遞迴, 遞迴的目的是對分治的陣列再此進行分治, 需要分別對標杆X左右兩邊的集合分別進行遞迴,從而使得每一個子的集合均滿足左邊小於右邊, 直至該集合只有一個
(分治函式一般命名為partition, 其中的標杆X一般命名為pivot(軸心))
上述兩個階段中, 難點在於分治階段, 所以我們接下來著重分析一下分治過程, 舉個實際的例子說明問題,借鑑其他部落格, 我們可以把該過程想象成一個挖坑填坑的過程。
1. 首先我們準備一組資料 array[ ], 選擇第一個元素11作為pivot值(標杆),本輪分治想要達到的效果是11位於中間某個位置, 左邊的值都小於11, 右邊的都大於11
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
|
22
|
32
|
4
|
2
|
66
|
10
|
55
|
69
|
2. 我們需要幾個變數來輔助, int left = start(此處為0), int right = end(此處為8), int pivot = start, int pivot_value = array[pivot], 所謂挖坑是指將該位置的值儲存起來, 然後將新找到滿足條件的值(v1)填到該位置, 從而在v1原來的位置上形成了一個新坑, 等待新值填入,如此迴圈下去,直至結束
那麼, 首先我們把11存放到pivot_value中了, 此時在array[0]處形成了一個坑。想要完成分治過程,我們肯定是要遍歷比較陣列中的每一個元素的, 具體順序是, 先由後向前, 再由前向後與pivot_value比較,然後再次交替重複該過程,向中間逼近,直至left 和right抵達相同位置(pos),此處就是pivot_value最終填充的位置, 而該pos就是要返回的位置,供遞迴使用
下面我們結合例項走一遍,
a. 由後向前: 期初hight = 8, pivot_value = 11,我們現在要找一個比11 小的值填到這個array[0]位置,11 比 69小, hight--, 11 比55小, hight--, 10比11小,滿足填坑條件, 把10填到array[0]處,並且low++, low =1, 從而在array[6]處產生一個新坑(紅色為待填的坑, 藍色為填完的坑)
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
22
|
32
|
4
|
2
|
66
|
|
55
|
69
|
b. 由前向後, 期初low = 0, 現在low = 1,我們現在要找比pivot_value大的值填到新坑array[6]中,22大於11, 滿足填坑條件,填進去,形成新坑array[1], high--, high = 5
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
|
32
|
4
|
2
|
66
|
22
|
55
|
69
|
c. 現在又回到由後向前的環節,同樣的, high = 5, 然後與pivot_value比較, 66 大於11, high--, 2小於11, 滿足填坑條件,填到array[1]中,形成新坑array[4], low ++後等於2
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
2
|
32
|
4
|
|
66
|
22
|
55
|
69
|
d. 現在是由前向後, low = 2, 32 大於11, 再此填坑, 形成新坑array[2], high-- 後等於3
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
2
|
|
4
|
32
|
66
|
22
|
55
|
69
|
e. 現在由後向前,high = 3, 4小與11, 滿足條件, 挖出來, 填到array[2]中, 形成新坑, low++後等於3
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
2
|
4
|
|
32
|
66
|
22
|
55
|
69
|
f. 此時, 由前向後,low = 3, 注意,此時low和high值相同,那麼,也就是說所有的元素已經全部遍歷了,迴圈結束,此時位置3就是最終pivot的位置,將pivot_value填進去即可
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
10
|
2
|
4
|
11
|
32
|
66
|
22
|
55
|
69
|
至此,本輪分治結束,返回位置3
我們可以看到分治後的結果, 所有11左邊的數字都小於11, 右邊的都大於11
對挖坑填數進行總結
1.low = left; high = right; 將基準數挖出形成第一個坑array[low]。
2.high--由後向前找比它小的數,找到後挖出此數填前一個坑array[low]中。
3.high++由前向後找比它大的數,找到後也挖出此數填到前一個坑array[high]中。
4.再重複執行2,3二步,直到low==high,將基準數填入array[low]中。
實現程式碼如下:
分治程式碼:
int partition(int *array, int start, int end)
{
if (array == nullptr || start < 0 || end < start)
{
return -1;
}
int pivot = start;
int pivot_value = array[pivot];
int low = start;
int high = end;
while(low < high)
{
while(low < high && array[high] >= pivot_value)
{
high--;
}
if (low < high)
{
array[low++] = array[high];
}
while (low < high && array[low] <= pivot_value)
{
low++;
}
if (low < high)
{
array[high--] = array[low];
}
}
array[low] = pivot_value;
return low;
}
遞迴程式碼:
void quickSort(int *array, int start, int end)
{
if (start < end)
{
int pivot = partition(array, start, end);
quickSort(array, start, pivot -1);
quickSort(array, pivot + 1, end);
}
}