关键词
- 快速排序
- 递归
- 分而治之
- 合并排序
- 归纳证明
数学
归纳证明
归纳证明是一种证明算法行之有效的方式,它分两步:基线 条件和归纳条件。是不是有点似曾相识的感觉?例如,假设我要证明我能爬到梯子的最上面。 递归条件是这样的:如果我站在一个横档上,就能将脚放到下一个横档上。换言之,如果我站 在第二个横档上,就能爬到第三个横档。这就是归纳条件。而基线条件是这样的,即我已经站 在第一个横档上。因此,通过每次爬一个横档,我就能爬到梯子最顶端。
- 归纳条件
- 基线条件
时间复杂度
快速排序的速度高度依赖于基准值。最糟的情况时间复杂度为O(n²), 最好的情况是O(n * log n)
最糟糕的情况
如果数组是有序的,而且永远选择第一个元素作为基准值,那么这个调用栈就变得最长了,调用栈的高度更好等于数组的长度,因为基准值的一边总是空的。这个就是最坏的情况,时间复杂度为O(n * n) = O(n²)。
计算公式: 每一次调用栈需要遍历所有元素一次,时间复杂度记录为 O(n),调用栈的高度刚好等于数组长度O(n), 所以,时间复杂度为O(n * n) = O(n²)
最佳的情况(也是平均情况)
基准值选择假设刚好每次都是中间的数,那么调用栈就变得非常短,因为每一次都把数组切成两半,所以不需要那么多递归,很快就达到了基准条件。调用栈的时间复杂度为O(log n),整个算法时间复杂度为O(n) * O(log n) = O(n * log n)
注: 虽然快速排序的时间复杂度跟基准值有很大的关系,实际上,快速查找的速度确实更快,因为相对于遇上最糟情况,它遇上平均情况的可能性要大得多。所以,快速排序总的来说还是最快的排序算法之一。
练习
func QuiklyOrder(arr []int) (res []int) {
if len(arr) < 2 {
return arr;
}
left := []int{};
right := []int{};
pivot := arr[0];
for index, _ := range arr {
if index != 0 {
if arr[index] <= pivot {
left = append(left, arr[index])
} else {
right = append(right, arr[index])
}
}
}
newLeft := QuiklyOrder(left)
newRight := QuiklyOrder(right)
result := append(append(newLeft, pivot), newRight...)
return result
}
func main() {
arr := []int{2, 1, 4, 5, 4, 7, 8, 3};
results := QuiklyOrder(arr)
fmt.Println("原数组", arr)
fmt.Println("排序后", results)
}
// 输出结果
$ go run main.go
原数组 [2 1 4 5 4 7 8 3]
排序后 [1 2 3 4 4 5 7 8]
小结
- 实现快速排序时,请随机地选择用作基准值的元素
- 归纳证明这个数据知识很重要,涉及到公式的推导
- 大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在?