排序之快速排序|Go主題月

快速排序簡述

歸併排序同樣,快速排序也採用分治策略,但不須要使用額外的存儲空間。不過,算法的效率會有所降低。算法

  1. 快速排序算法首先選出一個基準值。爲簡單起見,咱們選取切片數組(下面簡稱列表)中的第一個元素。
  2. 基準值的做用是幫助切分列表
  3. 在最終的有序列表中,基準值的位置被稱做分割點,算法在分割點切分列表爲兩部分,而後對這兩部分應用快速排序。

快速排序過程

假設須要對列表[5, 3, 10, 1]應用快速排序算法進行升序排序,過程以下圖:數組

  1. 選取基準值:元素 5 將做爲第一個基準值。

快速排序基準值.png 2. 計算分割點(爲基準值找到正確的位置)而進行分區操做: 它會找到分割點,同時將其餘元素放到正確的一邊——分割點的左邊的數據都小於基準值;分割點右邊的數據都大於基準值。markdown

分區操做首先找到兩個座標——leftmark 和 rightmark——它們分別位於列表剩餘元素的開頭和末尾,以下圖所示。
分區的目的是根據待排序元素與基準值的相對大小將它們放到正確的一邊,同時逐漸逼近分割點。
下圖展現了爲基準值元素 5 尋找正確位置的過程。函數

快速排序圖解.png

首先加大 leftmark,直到遇到一個大於基準值的元素。而後減少 rightmark,直到遇到一個小於基準值的元素。這樣一來,就找到兩個與最終的分割點錯序的元素。
本例中,這兩個元素就是 10 和 1。互換這兩個元素的位置,而後重複上述過程。post

當 rightmark 小於 leftmark 時,過程終止。此時,rightmark 的位置就是分割點。將基準值與當前位於分割點的元素互換,便可使基準值位於正確位置,如上圖所示。
分割點左邊的全部元素都小於基準值,右邊的全部元素都大於基準值。所以,能夠在分割點處將列表一分爲二,並針對左右兩部分遞歸調用快速排序函數。ui

Golang實現快速排序算法代碼

1. package main
2. 
3. import "fmt"
4. 
5. func quickSort(numList []int) {
6. 	quickSortHelper(numList, 0, len(numList)-1)
7. }
8. 
9. func quickSortHelper(numList []int, first, last int) {
10. 	if first < last {
11. 		// 計算切割點
12. 		splitPoint := partition(numList, first, last)
13. 		// 對切割點左邊的數據進行快速排序
14. 		// 即對 比切割點都小的數據進行快速排序
15. 		quickSortHelper(numList, first, splitPoint-1)
16. 		// 對切割點右邊的數據進行快速排序
17. 		// 即對 比切割點都大的數據進行快速排序
18. 		quickSortHelper(numList, splitPoint+1, last)
19. 	}
20. }
21. 
22. func partition(numList []int, first, last int) int {
23. 	// 選擇第一個數做爲基準點
24. 	// 把列表中的比基準點小的數據放在基準點的左邊
25. 	// 把列表中的比基準點大的數據放在基準點的右邊
26. 	// 而且返回切割點所在的位置,以便再次進行切割
27. 	pivotValue := numList[first]
28. 	leftMark := first + 1
29. 	rightMark := last
30. 	done := false
31. 	// 尋找切割點
32. 	for !(done) {
33. 		// 當左浮標從左向右移動的過程當中
34. 		// 且左右浮標沒有交叉的時候
35. 		// 若是左浮標的值比基準值小,則繼續往右移動
36. 		// 若是左浮標的值比基準值大,中止往右移動
37. 		// 意味着下一個值應該處於基準值的右邊
38. 		for leftMark <= rightMark &&
39. 			numList[leftMark] <= pivotValue {
40. 			leftMark += 1
41. 		}
42. 
43. 		// 當右浮標從右向左移動的過程當中
44. 		// 且左右浮標沒有交叉的時候
45. 		// 若是右浮標的值比基準值大,則繼續往左移動
46. 		// 若是右浮標的值比基準值小,中止向左移動
47. 		// 意味着這個值應該處於基準值的左邊
48. 		for rightMark >= leftMark &&
49. 			numList[rightMark] >= pivotValue {
50. 			rightMark -= 1
51. 		}
52. 
53. 		// 若是左右浮標交叉,說明已找到切割點的位置
54. 		// 中止尋找切割掉
55. 		if rightMark < leftMark {
56. 			done = true
57. 		} else {
58. 			// 左右浮標沒有交叉可是左右浮標都停下來了
59. 			// 即左右浮標的值應該處於基準值的另外一邊
60. 			// 那麼須要互相交換左右浮標值
61. 			temp := numList[leftMark]
62. 			numList[leftMark] = numList[rightMark]
63. 			numList[rightMark] = temp
64. 		}
65. 	}
66. 
67. 	// 把切割點上的數據移動到基準值所在的位置
68. 	numList[first] = numList[rightMark]
69. 	// 把基準值移動到正確的位置上
70. 	numList[rightMark] = pivotValue
71. 	// 返回分割點
72. 	return rightMark
73. }
74. 
75. 
76. func main() {
77. 	numList := []int{10, 1, 5, 3, 99}
78. 	quickSort(numList)
79. 	fmt.Println(numList)
80. }
複製代碼

在上面代碼中,快速排序函數 quickSort 調用了遞歸函數 quickSortHelper。spa

quickSortHelper 首先判斷是否處於基本狀況。code

  1. 若是列表的長度小於或等於 1,說明它已是有序列表;
  2. 若是長度大於 1,則進行分區操做並遞歸地排序。

時間複雜度

  1. 對於長度爲 n 的列表,若是分區操做老是發生在列表的中部,就會切分 logn 次。
  2. 爲了找到分割點,n 個元素都要與基準值比較。因此,時間複雜度是O(nlogn)。

另外,快速排序算法不須要像歸併排序算法那樣使用額外的存儲空間orm

相關文章
相關標籤/搜索