数据结构与算法之PHP排序算法(快速排序)

一、基本思想
快速排序又称划分交换排序,是对冒泡排序的一种改进,亦是分而治之思想在排序算法上的典型应用。
它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列的目的。
 
二、算法过程
1)从数列中挑出一个元素,称为“基准值”;
2)将待排序元素进行分区,比基准值小的元素放在基准值前面,比基准值大的元素放在基准值后面。分区结束后,该基准值就处于数组的中间位置;
3)对左右两个分区重复以上步骤直到所有元素都是有序的。
 
三、算法图解及PHP代码实现
1、替换版
上图只给出了第1趟快速排序的流程。在第1趟排序中,设置pivot=arr[i],即pivot=3。
1) 右 → 左 查找小于pivot的数:找到满足条件的数arr[j]=2,此时j=4;然后将a[j]赋值a[i],此时i=0;接着从左往右遍历。
2) 左 → 右 查找大于pivot的数:找到满足条件的数arr[i]=4,此时i=1;然后将a[i]赋值a[j],此时j=4;接着从右往左遍历。
3) 右 → 左 查找小于pivot的数:找到满足条件的数arr[j]=1,此时j=3;然后将a[j]赋值a[i],此时i=1;接着从左往右遍历。
4) 左 → 右 查找大于pivot的数:找到满足条件的数arr[i]=6,此时i=2;然后将a[i]赋值a[j],此时j=3;接着从右往左遍历。
5) 右 → 左 查找小于pivot的数:没有找到满足条件的数。当i>=j时,停止查找;然后将x赋值给a[i]。此趟遍历结束!
按照同样的方法,分别对3左右两边的子数列2 1和6 4 5进行递归遍历,最后得到有序数组。
PHP代码如下:
<?php
function quickSort(&$arr, $low, $high) {
    if ($low > $high) {
        return;
    }
    $index = partition($arr, $low, $high);
    quickSort($arr, $low, $index - 1);
    quickSort($arr, $index + 1, $high);
}
function partition(&$arr, $low, $high) {
    $len = count($arr);
    if ($len <= 1) {
        return $arr;
    }
    $pivot = $arr[$low];
    while ($low < $high) {
        while ($arr[$high] >= $pivot && $high > $low) {
            $high--;
        }
        $arr[$low] = $arr[$high];
        while ($arr[$low] <= $pivot && $high > $low) {
            $low++;
        }
        $arr[$high] = $arr[$low];
    }
    $arr[$high] = $pivot;
    return $high;
}

2、交换版

1)以第一个数组元素作为基准数,即pivot=arr[0];
2)设置两个变量i、j,排序开始的时候:i=0,j=n-1(待排序数组长度为n);
3)从j开始由后向前搜索(j--),找到第一个小于pivot的值arr[j],将arr[j]和arr[i]互换;
4)从i开始由前向后搜索(i++),找到第一个大于pivot的值arr[i],将arr[i]和arr[j]互换;
5)重复第3、4步,直到i=j,排序结束。
上图只给出了第1趟快速排序的流程。第一趟排序结束后,分别对3左右两边的子数列2 1和6 4 5进行递归遍历,最后得到有序数组。
PHP代码如下:
<?php
function quickSort(&$arr, $low, $high) {
    $i = $low;
    $j = $high;
    $pivot = $arr[$low];
 
    while ($j > $i) {
        while ($arr[$j] >= $pivot && $j > $i) {
            $j--;
        }
        if ($arr[$j] <= $pivot) {
            $temp = $arr[$j];
            $arr[$j] = $arr[$i];
            $arr[$i] = $temp;
        }
 
        while ($arr[$i] <= $pivot && $j > $i) {
            $i++;
        }
        if ($arr[$i] >= $pivot) {
            $temp = $arr[$i];
            $arr[$i] = $arr[$j];
            $arr[$j] = $temp;
        }
    }
    if ($i > $low) {
        quickSort($arr, $low, $i - 1);
    }
    if ($j < $high) {
        quickSort($arr, $j + 1, $high);
    }
}
// test
$arr = [85, 24, 63, 45, 17, 31, 96, 50];
quickSort($arr, 0, count($arr) - 1);
print_r($arr);

3、啊哈磊版(参见《啊哈!算法》第1章第3节:最常用的排序——快速排序)

看了啊哈磊的快排思路,快速排序流程为:把待排序数组的第一个数作为基准数,左右两端设置两个哨兵,先从右往左找一个小于基准数的数,再从左往右找一个大于基准数的数,然后交换它们。当左右两个哨兵相遇时,将该数和基准数交换。
下面是对85, 24, 63, 45, 17, 31, 96, 50的排序过程:
i=0  1   2   3   4   5   6   j=7
85, 24, 63, 45, 17, 31, 96, 50
基准数是85,
右→左,j=7,arr[7]=50,50小于85,
左→右,i=6,arr[6]=96,96大于85,
因为i小于j,所以96和50交换,变成
                        i=6 j=7
85, 24, 63, 45, 17, 31, 50, 96
右→左,j=6,与i相遇,所以,将50和基准数85交换,变成
                        i=j=6
50, 24, 63, 45, 17, 31, 85, 96
第一轮交换结束。

递归处理左边
i=0  1  2   3   4   j=5
50, 24, 63, 45, 17, 31
基准数是50,
右→左,j=5,arr[5]=31,31小于50,
左→右,i=2,arr[2]=63,63大于50,
31和63交换,变成
       i=2   3   4  j=5
50, 24, 31, 45, 17, 63
右→左,j=4,arr[4]=17,17小于85,
左→右,i=4时与j相遇,所以,将17和基准数50交换,变成
                i=j=4
17, 24, 31, 45, 50, 63
第二轮交换结束。
以此类推,直到不可拆分出新的子序列为止,排序完全结束。
 
啊哈磊版快排的核心思想是:快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。
PHP代码如下:
<?php
function qSort($left, $right) {
    global $arr;
    if ($left > $right) {
        return;
    }
    $pivot = $arr[$left];
    $i = $left;
    $j = $right;
    while ($i != $j) {
        while ($arr[$j] >= $pivot && $i < $j) {
            $j--;
        }
        while ($arr[$i] <= $pivot && $i < $j) {
            $i++;
        }
        // 交换i 和 j 两个位置上的数
        if ($i < $j) {
            $t = $arr[$i];
            $arr[$i] = $arr[$j];
            $arr[$j] = $t;
        }
    }
    // 基准数归位
    $arr[$left] = $arr[$i];
    $arr[$i] = $pivot;
 
    qSort($left, $i - 1);  // 递归处理左边
    qSort($i + 1, $right); // 递归处理右边
}
// test
$arr = [85, 24, 63, 45, 17, 31, 96, 50];
qSort(0, count($arr) - 1);
print_r($arr);

 

四、效率分析

1、时间复杂度:O(nlogn)
最好的情况:当分区选取的基准元素为待排序元素中的"中值",时间复杂度为O(nlogn);
最坏的情况:当分区选取的基准元素为待排序元素中的最大或最小值,时间复杂度为O(n²)。
2、空间复杂度:O(log2n),是不稳定排序。
posted @ 2018-03-09 14:44  鹿呦呦  阅读(438)  评论(1编辑  收藏  举报