|
8 | 8 |
|
9 | 9 | 依据串行(list)的大小(n),一般而言,好的表现是O(nlogn),且坏的行为是O(n2)。对于一个排序理想的表现是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(nlogn)。 |
10 | 10 |
|
| 11 | +所有基于比较的排序的时间复杂度至少是 O(nlogn)。 |
| 12 | + |
11 | 13 | ## 常见排序算法 |
12 | 14 |
|
13 | 15 | #### 稳定排序: |
|
25 | 27 | * 选择排序(Selection Sort)— O(n²) |
26 | 28 | * 希尔排序(Shell Sort)— O(nlogn) |
27 | 29 | * 堆排序(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) |
29 | 129 |
|
30 | 130 | ### 参考资料 |
31 | 131 |
|
32 | 132 | 1. [各种基本排序算法的总结](http://blog.sina.com.cn/s/blog_4080505a0101iewt.html) |
33 | 133 | 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