快速排序以及PHP实现

475 阅读3分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

算法原理

快排的思想是这样的:如果要排序数组中下标从p到r之间的一组数据,我们选择p到r之间的任意一个数据作为pivot(分区点)。然后遍历p到r之间的数据,将小于pivot的放到左边,将大于pivot的放到右边,将povit放到中间。经过这一步之后,数组p到r之间的数据就分成了3部分,前面p到q-1之间都是小于povit的,中间是povit,后面的q+1到r之间是大于povit的。根据分治、递归的处理思想,我们可以用递归排序下标从p到q-1之间的数据和下标从q+1到r之间的数据,直到区间缩小为1,就说明所有的数据都有序了。

递推公式:quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1, r)
终止条件:p >= r

性能分析

  1. 算法稳定性:
    因为分区过程中涉及交换操作,如果数组中有两个8,其中一个是pivot,经过分区处理后,后面的8就有可能放到了另一个8的前面,先后顺序就颠倒了,所以快速排序是不稳定的排序算法。比如数组[1,2,3,9,8,11,8],取后面的8作为pivot,那么分区后就会将后面的8与9进行交换。

  2. 时间复杂度:最好、最坏、平均情况
    快排也是用递归实现的,所以时间复杂度也可以用递推公式表示。
    如果每次分区操作都能正好把数组分成大小接近相等的两个小区间,那快排的时间复杂度递推求解公式跟归并的相同。

    T(1) = C; n=1 时,只需要常量级的执行时间,所以表示为 C
    T(n) = 2*T(n/2) + n; n>1

    所以,快排的时间复杂度也是O(nlogn)

    如果数组中的元素原来已经有序了,比如1,3,5,6,8,若每次选择最后一个元素作为pivot,那每次分区得到的两个区间都是不均等的,需要进行大约n次的分区,才能完成整个快排过程,而每次分区我们平均要扫描大约n/2个元素,这种情况下,快排的时间复杂度就是O(n^2)
    前面两种情况,一个是分区及其均衡,一个是分区极不均衡,它们分别对应了快排的最好情况时间复杂度和最坏情况时间复杂度。那快排的平均时间复杂度是多少呢?T(n)大部分情况下是O(nlogn),只有在极端情况下才是退化到O(n^2),而且我们也有很多方法将这个概率降低。

  3. 空间复杂度:快排是一种原地排序算法,空间复杂度是O(1)

原地交换的实现图解

php实现

   /**
     * 快排
     * @param array $array
     * @return array
     */
    public function quickSort(array $array)
    {
        $count = count($array);
        $this->quickSortRecursive($array, 0, $count - 1);
        return $array;
    }

    private function quickSortRecursive(array &$array, int $left, int $right)
    {
        if ($left >= $right) {
            return;
        }
        $q = $this->partition($array, $left, $right);
        $this->quickSortRecursive($array, $left, $q - 1);
        $this->quickSortRecursive($array, $q + 1, $right);
    }

    private function partition(array &$array, int $left, int $right)
    {
        $pivot = $array[$right];
        $index = $left;

        for ($j = $left; $j < $right; ++$j) {
            if ($array[$j] < $pivot) {
                $tmp = $array[$j];
                $array[$j] = $array[$index];
                $array[$index] = $tmp;
                $index ++;
            }
        }
        $tmp = $array[$index];
        $array[$index] = $array[$right];
        $array[$right] = $tmp;

        return $index;
    }

归并排序与快速排序的区别

归并和快排用的都是分治思想,递推公式和递归代码也非常相似,那它们的区别在哪里呢?

  1. 归并排序,是先递归调用,再进行合并,合并的时候进行数据的交换。所以它是自下而上的排序方式。何为自下而上?就是先解决子问题,再解决父问题。
  2. 快速排序,是先分区,在递归调用,分区的时候进行数据的交换。所以它是自上而下的排序方式。何为自上而下?就是先解决父问题,再解决子问题。