Python中的快速排序

2025 年 4 月 17 日 | 6 分钟阅读

排序是软件工程中的一项主要活动,并且有许多算法可以用来对元素列表进行排序。最有效的排序算法之一是快速排序,它基于分治策略。快速排序的平均内存复杂度为 O(n log n),在实践中被广泛使用。

在本文中,我们将介绍 Python 中的快速排序算法,并探讨其实现、性能和优化技术。

快速排序的工作原理

快速排序算法通过将数组划分为两个子数组来工作:一个包含小于所选枢轴元素的元素,另一个包含大于或等于枢轴元素的元素。选择枢轴元素是为了使其在划分完成后在已排序数组中的最终位置。

然后,算法通过对每个子数组应用相同的过程来递归地对子数组进行排序。当子数组的元素少于两个时,递归停止。

代码

quicksort 函数接受一个数组 arr 作为输入,并返回一个已排序的数组。基本情况是当 arr 的长度小于 2 时,此时 arr 已排序。

枢轴元素被选为 arr 的第一个元素。然后,通过将小于枢轴的元素分离到 left,将大于或等于枢轴的元素分离到 right 来生成子数组。

通过对 left 和 right 递归调用 quicksort 来获得已排序的子数组。将已排序的数组与枢轴元素连接起来以获得最终的已排序数组。

选择枢轴元素

枢轴元素的选取对快速排序算法的性能有重要影响。如果枢轴元素选择不当,算法可能会表现出糟糕的性能,有时甚至会出现时间复杂度为 O(n^2) 的最坏情况。

选择枢轴元素的一种常见方法是选择数组的第一个、中间和最后一个元素的中间值。这种方法在实践中通常效果良好,并且可以防止最坏情况的发生。

划分数组

快速排序中的划分步骤对算法的性能至关重要。在上面的实现中,我们使用列表推导式来划分数组,这对于大型数组来说可能效率不高。

一种简单的划分方法是使用两个索引从数组的两端遍历数组,并交换放在错误子数组中的元素。这种方法对于大型数组可能更有效,并且在实践中经常使用。

快速排序中的递归

快速排序算法的递归特性可能导致非常大的数组出现堆栈溢出错误。为了避免这种情况,可以使用算法的尾递归实现,它通过重用递归调用的堆栈帧来优化递归。然而,Python 默认情况下不优化尾递归,因此这种方法在 Python 中可能不会带来显著的性能提升。

算法实现

输入

A:一个包含 n 个元素的数组

lo:要排序的子数组的第一个元素的索引

hi:要排序的子数组的最后一个元素的索引

快速排序算法

  1. 如果 lo 小于 hi,则执行以下操作
    • 调用 partition(A, lo, hi) 并将枢轴元素的索引存储在 p 中。
    • 递归调用 quicksort(A, lo, p-1)。
    • 递归调用 quicksort(A, p+1, hi)。

划分算法

  1. 设枢轴为子数组 A[lo..hi] 的最后一个元素。
  2. 设 i 为子数组第一个元素的索引。
  3. 对于从 lo 到 hi-1 的每个 j,执行以下操作
    1. 如果 A[j] <= 枢轴,则执行以下操作
      1. 增加 i。
      2. 将 A[i] 与 A[j] 交换。
  4. 将 A[i+1] 与 A[hi] 交换。
  5. 返回 i+1。

程序实现

输出

Sorted array: [1, 5, 7, 8, 9, 10]

快速排序的时间和空间复杂度

时间复杂度

快速排序的平均时间复杂度为 **O(n log n)**,其中 n 是数组中的元素数量。但是,最坏情况时间复杂度为 **O(n^2)**,这发生在枢轴元素是数组中最小或最大的元素,并且子数组没有均匀划分时。

空间复杂度

由于算法的递归特性,快速排序算法的平均空间复杂度为 **O(log n)**。然而,最坏情况空间复杂度为 **O(n)**,这发生在递归调用不平衡并导致长链式堆栈帧时。

将快速排序与其他排序算法进行比较

快速排序是当前最有效的排序算法之一,平均时间复杂度为 O(n log n),内存区域利用率高。然而,它可能出现时间复杂度为 O(n^2) 的最坏情况,并且对于小型数组或高度有序的数据来说,它可能不是最佳选择。其他流行的排序算法包括归并排序、堆排序和插入排序。归并排序的最坏情况时间复杂度为 O(n log n),但需要额外的内存来合并子数组。

堆排序的最坏情况时间复杂度为 O(n log n),并且没有额外的内存要求,但它可能不如快速排序那样节省内存。插入排序的时间复杂度为 O(n^2),但对于小型数组和部分排序的数据非常有效。排序算法的选择取决于具体用例、数据的大小和结构以及可用资源。

快速排序优化技术

有几种技术可以用来优化快速排序算法并提高其性能。其中一些技术包括:

随机化枢轴选择:与其选择第一个元素作为枢轴,不如从数组中选择一个随机元素。这可以降低最坏情况发生的概率,并提高算法的平均性能。

尾递归:使用快速排序的尾递归实现来优化递归并避免堆栈溢出错误。

混合算法:使用一种混合算法,将快速排序与另一种排序算法结合起来处理小型数组或高度有序的数据。例如,对于小于 10 个元素的子数组使用插入排序。

结论

快速排序是一种高效的排序算法,平均可以在 O(n log n) 时间内对大型数组进行排序。然而,它可能出现时间复杂度为 O(n^2) 的最坏情况,并且对于小型数组或高度有序的数据来说,它可能不是最佳选择。


下一个主题Python 中的堆排序