本人已参与「新人创作礼」活动,一起开启掘金创作之路。
分而治之
概念
分而治之(divide and conquer,D&C)—— 一种著名的递归式问题解决方法。 D&C的工作原理:
- 找出简单的基线条件;
- 确定如何缩小问题的规模,使其符合基线条件。 例如,对于数组类问题的基线条件,则常常是数组为空,或数组内仅有一个元素。 分解方式则通常是将其分为两个尽可能等大小的数组。
练习
4.1 - 4.3 见算法图解之Swift实践【第三章 递归】 4.4 还记得第1章介绍的二分查找吗?它也是一种分而治之算法。你能找出二分查找算法的基线条件和递归条件吗?
基线条件:数组范围缩小到只有一个元素 递归条件:将数组分为两部分,根据对比目标值和区间低位和高位间的关系,丢弃其中不符合条件的一半,并对另一半继续进行二分法查找。
快速排序
实现
快速排序用到了分治的思想。
func quickSort(_ arr: [Int]) -> [Int] {
if arr.count < 2 { // 数组元素个数为1或者数组为空,无需排序,直接返回原数组
return arr
}
let pivot = arr[0] // 选取数组的第一个元素作为基准点
var leftArr: [Int] = []
var rightArr: [Int] = []
for i in 1..<arr.count { // 从第二个元素开始遍历
if arr[i] < pivot { // 得到所有比基准值小的数组
leftArr.append(arr[i])
} else { // 得到所有比基准值大的数组
rightArr.append(arr[i])
}
}
// 将子数组进行排序后,再与基准值一同组成有序数组
return quickSort(leftArr) + [pivot] + quickSort(rightArr) }
快速排序是一种常用的排序算法,比选择排序快得多。
扩展与应用
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1: 输入:arr = [3,2,1], k = 2 输出:[1,2] 或者 [2,1]
示例 2: 输入:arr = [0,1,2,1], k = 1 输出:[0]
限制: 0 <= k <= arr.length <= 10000 0 <= arr[i] <= 10000
这题的结题思路可以很简单,求最小的k个数组成的数组,首先我们可以考虑将数组进行排序,再截取数组的前k个元素即可得到结果。
其中排序可以用到本章学习的快速排序。
func getLeastNumbers(_ arr: [Int], _ k: Int) -> [Int] {
if arr.count == 0 || k == 0 {
return []
}
let sortedArr = qsort(arr)
return Array(sortedArr[0...k-1])
}
// 快排
func qsort(_ arr: [Int]) -> [Int] {
if arr.count < 2 {
return arr
}
let pivot = arr[0]
var leftArr: [Int] = []
var rightArr: [Int] = []
for i in 1..<arr.count {
if arr[i] < pivot {
leftArr.append(arr[i])
} else {
rightArr.append(arr[i])
}
}
return qsort(leftArr) + [pivot] + qsort(rightArr)
}
大O表示法的平均情况和最糟情况
最糟情况指的是算法运行时可能遇到的最坏的情况。
平均情况指的是算法遇到时的最佳情况。(最佳情况也是平均情况)
- 快速排序的平均情况为O(n log n),最糟情况为O(n^2)。这取决于基准值选取的好坏。
- 当每次基准值选取的均为最差情况时,则需要对包含n个元素的数组进行n次基准值的选取。当每次基准这选取的均为最佳情况时,则仅需要log n次选取。(二分)
- 而当每一次选取基准值后,数组的每个元素均需要和基准值进行对比,因此,每次选取基准值之后需要进行n次比较。
- 因此,快速排序的最糟情况为O(n * n)=O(n^2),平均情况为O(n log n)。
练习
使用大O表示法时,下面各种操作都需要多长时间?
4.5 打印数组中每个元素的值。
O(n)
4.6 将数组中每个元素的值都乘以2。
O(n)
4.7 只将数组中第一个元素的值乘以2。
O(1)
4.8 根据数组包含的元素创建一个乘法表,即如果数组为[2, 3, 7, 8, 10],首先将每个元素 都乘以2,再将每个元素都乘以3,然后将每个元素都乘以7,以此类推。
O(n^2)
小结
- D&C将问题逐步分解。使用D&C处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
- 实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为O(n log n)。
- 大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
- 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(log n)的速度比O(n)快得多。