1. 程式人生 > >[閉目洞察算法系列之一]快速排序

[閉目洞察算法系列之一]快速排序

這是第一篇關於演算法的部落格, 我本人對演算法沒什麼深刻見解, 此處只是對別人部落格的再整理, 用自己理解的方式進行表述一遍, 一方面加深印象, 另一方面做知識沉澱,供他日食用。 廢話到此為止, 下面是正題

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

快速排序思想核心分為兩部分:

        一是分治, 即選擇一個值X作為標杆, 然後對每個子陣列進行大致排序, 使得該標杆X左邊的值都小於或等於X,, 右邊的值都大於或等於X

       注意, 左邊的值只需小於X即可, 至於這些值彼此之間不一定是有序的。

例如(2, 3, 6, 489, 77, 34)就是正確分治後的結果

        二是遞迴, 遞迴的目的是對分治的陣列再此進行分治, 需要分別對標杆X左右兩邊的集合分別進行遞迴,從而使得每一個子的集合均滿足左邊小於右邊, 直至該集合只有一個 

        (分治函式一般命名為partition, 其中的標杆X一般命名為pivot(軸心))

上述兩個階段中, 難點在於分治階段, 所以我們接下來著重分析一下分治過程, 舉個實際的例子說明問題,借鑑其他部落格, 我們可以把該過程想象成一個挖坑填坑的過程。

1. 首先我們準備一組資料 array[ ], 選擇第一個元素11作為pivot值(標杆),本輪分治想要達到的效果是11位於中間某個位置, 左邊的值都小於11, 右邊的都大於11

0

1

2

 3


5

6

7

8

11

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


5

6

7

8

10

22

32

4

2

66

10

55

69

b. 由前向後, 期初low = 0, 現在low = 1,我們現在要找比pivot_value大的值填到新坑array[6]中,22大於11, 滿足填坑條件,填進去,形成新坑array[1], high--, high = 5

0

1

2

 3


5

6

7

8

10

22

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


5

6

7

8

10

2

32

4

2

66

22

55

69


d. 現在是由前向後, low = 2, 32 大於11, 再此填坑, 形成新坑array[2], high-- 後等於3

0

1

2

 3


5

6

7

8

10

2

32

4

32

66

22

55

69


e. 現在由後向前,high = 3, 4小與11, 滿足條件, 挖出來, 填到array[2]中, 形成新坑, low++後等於3

0

1

2

 3


5

6

7

8

10

2

4

4

32

66

22

55

69


f. 此時, 由前向後,low = 3, 注意,此時low和high值相同,那麼,也就是說所有的元素已經全部遍歷了,迴圈結束,此時位置3就是最終pivot的位置,將pivot_value填進去即可

0

1

2

 3


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);
	}
}