「Go语言面试题」算法:手写快速排序(QuickSort)
什么是快速排序
快速排序(QuickSort)是计算机科学中最经典、最高效的排序算法之一,由Tony Hoare于1959年发明。快速排序的核心思想是"分而治之",平均时间复杂度为O(n log n),在大多数情况下表现出色。
快速排序算法步骤
- 从数列中挑出一个元素,称为"基准"(pivot)
- 重新排序数列,所有比基准值小的元素摆放在基准前面
- 所有比基准值大的元素摆在基准后面(相同的数可以到任一边)
- 递归地对基准值左右两边的子序列进行排序
Go语言实现
以下是快速排序的Go语言实现,包含详细的注释说明:
package main
import (
"fmt"
"math/rand"
)
// QuickSort 快速排序函数
func QuickSort(arr []int) {
// 如果数组长度小于等于1,无需排序
if len(arr) <= 1 {
return
}
// 调用递归排序函数
quickSort(arr, 0, len(arr)-1)
}
// quickSort 递归排序函数
func quickSort(arr []int, low, high int) {
if low < high {
// 获取分区点
pi := partition(arr, low, high)
// 递归排序分区点左边的子数组
quickSort(arr, low, pi-1)
// 递归排序分区点右边的子数组
quickSort(arr, pi+1, high)
}
}
// partition 分区函数,选择最后一个元素作为基准
func partition(arr []int, low, high int) int {
// 选择基准值(这里选择最后一个元素)
pivot := arr[high]
// 较小元素的索引
i := low - 1
// 遍历数组,重新排列元素
for j := low; j < high; j++ {
// 如果当前元素小于或等于基准值
if arr[j] <= pivot {
// 增加较小元素的索引
i++
// 交换元素
arr[i], arr[j] = arr[j], arr[i]
}
}
// 将基准值放到正确位置
arr[i+1], arr[high] = arr[high], arr[i+1]
// 返回基准值的最终位置
return i + 1
}
// 优化版本:随机选择基准值,避免最坏情况
func partitionRandom(arr []int, low, high int) int {
// 随机选择基准值
randomIndex := rand.Intn(high-low+1) + low
arr[randomIndex], arr[high] = arr[high], arr[randomIndex]
// 使用标准分区逻辑
return partition(arr, low, high)
}
func main() {
// 测试用例
arr := []int{10, 7, 8, 9, 1, 5}
fmt.Println("原始数组:", arr)
QuickSort(arr)
fmt.Println("排序后数组:", arr)
// 测试随机数组
randomArr := make([]int, 10)
for i := range randomArr {
randomArr[i] = rand.Intn(100)
}
fmt.Println("随机数组:", randomArr)
QuickSort(randomArr)
fmt.Println("排序后随机数组:", randomArr)
}
算法分析
时间复杂度
- 最佳情况:O(n log n) - 每次分区都能将数组均匀分成两部分
- 平均情况:O(n log n)
- 最坏情况:O(n²) - 当数组已经有序或逆序,且选择固定位置的基准值时
空间复杂度
- 最佳情况:O(log n) - 递归调用栈的深度
- 最坏情况:O(n) - 不平衡的分区导致递归深度增加
优化策略
- 随机选择基准值:避免在已排序数组上出现最坏情况
- 三数取中法:选择第一个、中间和最后一个元素的中值作为基准
- 小数组使用插入排序:当子数组规模较小时,插入排序效率更高
- 尾递归优化:减少递归调用的栈深度
快速排序的特点
优点
- 高效:在平均情况下,它是基于比较的排序算法中最快的之一
- 原地排序:只需要很小的额外内存空间
- 缓存友好:具有良好的局部性特性,能有效利用CPU缓存
缺点
- 不稳定:相等元素的相对位置可能会改变
- 最坏情况性能差:虽然可以通过优化避免,但最坏情况下性能会下降到O(n²)
- 递归实现:对于极大数组,可能导致栈溢出
与其他排序算法的比较
- vs 归并排序:快速排序是原地排序,不需要额外空间,但最坏情况性能不如归并排序稳定
- vs 堆排序:快速排序的平均情况性能更好,但堆排序能保证最坏情况下也是O(n log n)
- vs 插入排序:对于小规模数据,插入排序可能更快,但大规模数据快速排序优势明显
总结
快速排序是一种高效、实用的排序算法,尤其适合处理大规模数据集。通过合理选择基准值和优化策略,可以避免最坏情况的发生,使其在大多数场景下表现出优异的性能。
理解快速排序不仅有助于应对技术面试,更能加深对分治算法思想和递归编程的理解,是每位程序员必备的基础算法知识。