快速排序

基本思路

说到快速排序,我们得先聊一聊分治法。分治法共三个步骤:

  • 分解 - 分解原问题为若干子问题,这些子问题相互独立并且是原问题的较小规模的实例。
  • 解决 - 递归「分解」子问题,直到子问题达到临界条件无法再进行更小的分解时,求解该子问题。
  • 合并 - 合并子问题的解,即得到原问题的解。

分治法是一种强有力的编程思想,分而治之,将复杂的问题分解为规模小的简单的子问题,再逐个解决这些简单的子问题,最后将子问题的结果合并起来,得到复杂问题的答案。

快速排序就是用到了分治法的思想,我们以数组arr[p...r]为例,来说明快排的思路:

  • 分解: 数组arr[p...r]分解为两个子数组arr[p...q-1]和arr[q+1...r],使得arr[q]大于arr[p...q-1]中的所有元素,并且arr[q]小于arr[q+1...r]中的所有元素。
  • 解决:递归地对两个子数组进行快速排序。
  • 合并:原址排序,不需要合并操作。

除了分治法的思想,快速排序的另一个关键点在于分解这一步,称之为切分。我们可以取arr[p]为切分元素,然后从数组的最左边开始向右遍历直到找到一个大于它的元素A,再从数组的最右边向左遍历直到找到一个小于它的元素B,此时分两种情况:

  1. 当元素A的索引小于元素B的索引,想象一下,此时上述的两次遍历的指针还没有相遇过。我们交换元素A和B,继续上述的两次遍历,直到遇到第二种情况。
  2. 当元素A的索引大于等于元素B的索引,此时两次遍历的指针已经相遇交错了,即保证了数组arr内的所有元素都被遍历了最少一次,我们交换切分元素arr[p]和元素B,就完成了分解操作,而分解操作中描述的arr[q]就是切分元素arr[p]新的位置。

php代码实现快速排序

将数组的第一个元素作为切分元素实现切分函数

function partition(&$arr, $lo, $hi) {
  $i = $lo;
  $j = $hi + 1;
  while (1) {
    // 从$lo+1 开始找一个比lo大的数{$i}
    while ($arr[$lo] > $arr[++$i]) {
      if ($i == $hi) break;
    }
    // 从最右边往左找一个比lo小的数{$j}
    while ($arr[$lo] < $arr[--$j]) {
      if ($j == $lo) break;
    }

    // 假使$i >= $j, 跳出循环
    if ($i >= $j) break;

    // 假使$i < $j, $i的值 与 $j的值交换, 继续2,3步骤
    $tmp = $arr[$j];
    $arr[$j] = $arr[$i];
    $arr[$i] = $tmp;
  }
  // 假使$i >= $j,  $j 的值与$lo的值交换, 返回$j
  $tmp = $arr[$j];
  $arr[$j] = $arr[$lo];
  $arr[$lo] = $tmp;
  return $j;
}

调用切分函数实现快速排序

function quick_sort(&$arr) {
  quick($arr, 0, count($arr) - 1);
}

function quick(&$arr, $lo, $hi) {
  if ($lo >= $hi) return;
  $j = partition($arr, $lo, $hi);
  quick($arr, $lo, $j - 1);
  quick($arr, $j + 1, $hi);
}

测试代码

$arr = [];
for ($i = 0; $i < 100; $i++) {
    $arr[$i] = rand(0, 10000);
}
print_r($arr);
quick_sort($arr);
print_r($arr);

性能

空间复杂度

虽然快速排序是原地排序,但是在递归调用的过程中也使用了栈空间,平均空间复杂度为: \(O(\lg n)\), 最坏情况下空间复杂度为\(O(n)\)

时间复杂度

切分函数进行n=r-p+1次比较和小于n次交换,可知每一次切分的时间复杂度是\(O(n)\)

快速排序的时间复杂度≈切分函数的时间复杂度*递归分解的次数。

在最坏的情况下,即数组已经有序,需要递归分解n次,此时的时间复杂度会达到\(O(n ^ 2)\)。在我们完全可以在代码层面上避免这种最坏的情况,在此提供两种思路:

  1. 在排序函数入口处,将数组的元素随机打乱。
  2. 在切分函数入口处,随机获取数组的一个元素A,把A与数组的第一个元素,即原切分元素交换位置,目的是让切分元素随机化。

在数组元素次序随机的情况下,递归的次数趋向于二分法,即分解\(\lg n\)次,可得快排的平均时间复杂度为\(O(N * \lg n)\)。我们总可以做到快速排序的随机化,可以忽略最坏的情况,故此快速排序是一个高效的算法。

posted @ 2021-06-25 20:05  TianJiankun  阅读(91)  评论(0编辑  收藏  举报