数据结构与算法之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),是不稳定排序。
浪漫家园,没事就来逛逛