「Go语言面试题」算法:手写快速排序(QuickSort)


什么是快速排序

快速排序(QuickSort)是计算机科学中最经典、最高效的排序算法之一,由Tony Hoare于1959年发明。快速排序的核心思想是"分而治之",平均时间复杂度为O(n log n),在大多数情况下表现出色。

快速排序算法步骤

  1. 从数列中挑出一个元素,称为"基准"(pivot)
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面
  3. 所有比基准值大的元素摆在基准后面(相同的数可以到任一边)
  4. 递归地对基准值左右两边的子序列进行排序

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) - 不平衡的分区导致递归深度增加

优化策略

  1. 随机选择基准值:避免在已排序数组上出现最坏情况
  2. 三数取中法:选择第一个、中间和最后一个元素的中值作为基准
  3. 小数组使用插入排序:当子数组规模较小时,插入排序效率更高
  4. 尾递归优化:减少递归调用的栈深度

快速排序的特点

优点

  1. 高效:在平均情况下,它是基于比较的排序算法中最快的之一
  2. 原地排序:只需要很小的额外内存空间
  3. 缓存友好:具有良好的局部性特性,能有效利用CPU缓存

缺点

  1. 不稳定:相等元素的相对位置可能会改变
  2. 最坏情况性能差:虽然可以通过优化避免,但最坏情况下性能会下降到O(n²)
  3. 递归实现:对于极大数组,可能导致栈溢出

与其他排序算法的比较

  • vs 归并排序:快速排序是原地排序,不需要额外空间,但最坏情况性能不如归并排序稳定
  • vs 堆排序:快速排序的平均情况性能更好,但堆排序能保证最坏情况下也是O(n log n)
  • vs 插入排序:对于小规模数据,插入排序可能更快,但大规模数据快速排序优势明显

总结

快速排序是一种高效、实用的排序算法,尤其适合处理大规模数据集。通过合理选择基准值和优化策略,可以避免最坏情况的发生,使其在大多数场景下表现出优异的性能。

理解快速排序不仅有助于应对技术面试,更能加深对分治算法思想和递归编程的理解,是每位程序员必备的基础算法知识。

wx

关注公众号

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo