Skip to content

Commit ed6ed55

Browse files
committed
QuickSort
1 parent fc5fb06 commit ed6ed55

File tree

1 file changed

+103
-2
lines changed

1 file changed

+103
-2
lines changed

source/basic/algo/Sorting.md

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
依据串行(list)的大小(n),一般而言,好的表现是O(nlogn),且坏的行为是O(n2)。对于一个排序理想的表现是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(nlogn)。
1010

11+
所有基于比较的排序的时间复杂度至少是 O(nlogn)。
12+
1113
## 常见排序算法
1214

1315
#### 稳定排序:
@@ -25,10 +27,109 @@
2527
* 选择排序(Selection Sort)— O(n²)
2628
* 希尔排序(Shell Sort)— O(nlogn)
2729
* 堆排序(Heapsort)— O(nlogn)
28-
* 快速排序(Quicksort)— O(nlogn) 期望时间, O(n2) 最坏情况; 对于大的、乱数串行一般相信是最快的已知排序
30+
* 快速排序(Quicksort)— O(nlogn) 期望时间, O(n²) 最坏情况; 对于大的、乱数串行一般相信是最快的已知排序
31+
32+
#### 快排
33+
34+
快排是经典的 divide & conquer 问题,如下用于描述快排的思想、伪代码、代码、复杂度计算以及快排的变形。
35+
36+
##### 快排的思想
37+
38+
如下的三步用于描述快排的流程:
39+
40+
- 在数组中随机取一个值作为标兵
41+
- 对标兵左、右的区间进行划分(将比标兵大的数放在标兵的右面,比标兵小的数放在标兵的左面,如果倒序就反过来)
42+
- 重复如上两个过程,直到选取了所有的标兵并划分(此时每个标兵决定的区间中只有一个值,故有序)
43+
44+
##### 伪代码
45+
46+
如下是快排的主体伪代码
47+
48+
```
49+
QUCIKSORT(A, p, r)
50+
if p < r
51+
q = PARTITION(A, p, r)
52+
QUICKSORT(A, p, q-1)
53+
QUICKSORT(A, q+1, r)
54+
```
55+
56+
如下是用于选取标兵以及划分的伪代码
57+
58+
```
59+
PARTITION(A, p, r)
60+
x = A[r]
61+
i = p - 1
62+
for j = p to r - 1
63+
if A[j] <= x
64+
i++
65+
swap A[i] with A[j]
66+
swap A[i+1] with A[j]
67+
return i+1
68+
```
69+
70+
##### 代码
71+
72+
```Swift
73+
func quickSort(inout targetArray: [Int], begin: Int, end: Int) {
74+
if begin < end {
75+
let pivot = partition(&targetArray, begin: begin, end: end)
76+
quickSort(&targetArray, begin: begin, end: pivot - 1)
77+
quickSort(&targetArray, begin: pivot + 1, end: end)
78+
}
79+
}
80+
81+
func partition(inout targetArray: [Int], begin: Int, end: Int) -> Int {
82+
let value = targetArray[end]
83+
var i = begin - 1
84+
for j in begin ..< end {
85+
if targetArray[j] <= value {
86+
i += 1;
87+
swapTwoValue(&targetArray[i], b: &targetArray[j])
88+
}
89+
}
90+
swapTwoValue(&targetArray[i+1], b: &targetArray[end])
91+
return i+1
92+
}
93+
94+
func swapTwoValue(inout a: Int, inout b: Int) {
95+
let c = a
96+
a = b
97+
b = c
98+
}
99+
100+
var testArray :[Int] = [123,3333,223,231,3121,245,1123]
101+
102+
quickSort(&testArray, begin: 0, end: testArray.count-1)
103+
```
104+
105+
##### 复杂度分析
106+
107+
在最好的情况下,每次 partition 都会把数组一分为二,所以时间复杂度 T(n) = 2T(n/2) + O(n)
108+
109+
解为 T(n) = O(nlog(n))
110+
111+
在最坏的情况下,数组刚好和想要的结果顺序相同,每次 partition 到的都是当前无序区中最小(或最大)的记录,因此只得到一个比上一次划分少一个记录的子序列。T(n) = O(n) + T(n-1)
112+
113+
解为 T(n) = O(n²)
114+
115+
在平均的情况下,快排的时间复杂度是 O(nlog(n))
116+
117+
##### 变形
118+
119+
可以利用快排的 PARTITION 思想求数组中第K大元素这样的问题,步骤如下:
120+
121+
- 在数组中随机取一个值作为标兵,左右分化后其顺序为X
122+
- 如果 X == Kth 说明这就是第 K 大的数
123+
- 如果 X > Kth 说明第 K 大的数在标兵左边,继续在左边寻找第 Kth 大的数
124+
- 如果 X < Kth 说明第 K 大的数在标兵右边,继续在右边需找第 Kth - X 大的数
125+
126+
这个问题的时间复杂度是 O(n)
127+
128+
T(n) = n + n/2 + n/4 + ... = O(n)
29129

30130
### 参考资料
31131

32132
1. [各种基本排序算法的总结](http://blog.sina.com.cn/s/blog_4080505a0101iewt.html)
33133
2. [常用排序算法小结](http://blog.csdn.net/whuslei/article/details/6442755)
34-
3. [八大排序算法总结](http://blog.csdn.net/yexinghai/article/details/4649923)
134+
3. [八大排序算法总结](http://blog.csdn.net/yexinghai/article/details/4649923)
135+
4. [QuickSort](https://en.wikipedia.org/wiki/Quicksort)

0 commit comments

Comments
 (0)