算法思路分析:
快速排序的思想就是,选一个数作为基数(这里我选的是第一个数),大于这个基数的放到右边,小于这个基数的放到左边,等于这个基数的数可以放到左边或右边,看自己习惯,这里我是放到了左边,一趟结束后,将基数放到中间分隔的位置,第二趟将数组从基数的位置分成两半,分割后的两个的数组继续重复以上步骤,选基数,将小数放在基数左边,将大数放到基数的右边,在分割数组,直到数组不能再分为止,排序结束。
例如从小到大排序:
1>第一趟,第一个数为基数temp,设置两个指针left = 0,right = n.length,
①从right开始与基数temp比较,如果n[right]>基数temp,则right指针向前移一位,继续与基数temp比较,直到不满足n[right]>基数temp
②将n[right]赋给n[left]
③从left开始与基数temp比较,如果n[left]<=基数temp,则left指针向后移一位,继续与基数temp比较,直到不满足n[left]<=基数temp
④将n[left]赋给n[rigth]
⑤重复①-④步,直到left==right结束,将基数temp赋给n[left]
2> 第二趟,将数组从中间分隔,每个数组再进行第1步的操作,然后再将分隔后的数组进行分隔再快排,
3>递归重复分隔快排,直到数组不能再分,也就是只剩下一个元素的时候,结束递归,排序完成
下面用图,演示第一趟执行流程:
复杂度分析:
1>时间复杂度:
最坏情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序)
这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n。
最好情况下是O(nlog2n),推导过程如下:(递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n) )
空间复杂度:快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据:
最优的情况下空间复杂度为:O(log2n);每一次都平分数组的情况
最差的情况下空间复杂度为:O( n );退化为冒泡排序的情况
所以平均空间复杂度为O(log2n)。
一、递归实现
<?php
/**
第一种写法
**/
<?php
function getPosition(&$data, $low, $high) {
$target = $data[$low];
//因为交换完后,并没有全部比较完,继续扫描,直到$low = $high 为止
while ($low < $high) {
//从右向左找小于target的数
while ($low < $high && $data[$high] > $target) $high--;
//找到后交换
$data[$low] = $data[$high];
//从左向右找大于target的数
while ($low < $high && $data[$low] < $target) $low++;
//找到后交换
$data[$high] = $data[$low];
}
$data[$high] = $target;
return $high;
}
function quickSort(&$data, $low, $high) {
$pos = getPosition($data, $low, $high); //获取下一次分区位置。
if ($low < $pos - 1) quickSort($data, $low, $pos - 1); //如果当前位置没有到达最左侧,往左分区
if ($pos + 1 < $high) quickSort($data, $pos + 1, $high); //如果当前位置没有到达最右侧,往右分区
}
/**
第二种写法
**/
function quickSort(&$rotateArray, $left, $right)
{
$temp_left = $left;
$temp_right = $right;
$mid = $rotateArray[intval(($left + $right) / 2)];
while ($temp_left < $temp_right){
//左右指针没有重合
while ($rotateArray[$temp_left] < $mid) ++$temp_left; //和中间值进行比较,小的指针右移
while ($rotateArray[$temp_right] > $mid) --$temp_right;//和中间值进行比较,大的指针左移
if($temp_left <= $temp_right){
//左边有大于中间值的,右边有小于中间值的
$temp = $rotateArray[$temp_left];
$rotateArray[$temp_left] = $rotateArray[$temp_right];
$rotateArray[$temp_right] = $temp;
--$temp_right;
++$temp_left;
}
}
if($temp_left == $temp_right) $temp_left++;
if($left < $temp_right) quickSort($rotateArray, $left, $temp_left - 1);
if($temp_left < $right) quickSort($rotateArray, $temp_right + 1, $right);
}
$data = array(3,2,6,8);
quickSort($data, 0, count($data) - 1);
var_dump($data);
二、非递归实现
用非递归的算法,主要是利用到栈的思想,递归实现的过程,用数组来保存 l e f t 和 left 和 left和right两个指针。用pivot = partition(); 实现分区,每次分为左右两个部分(类似于递归实现中的左右递归),然后返回移动后的$left “指针”,用于判断是否需要继续分区。
<?php
/**
* 快排的非递归实现
* @param $data
* @return bool
*/
function quickSort($data)
{
if(empty($data)) return false;
$left = 0;
$right = count($data) - 1;
$pointer = [];
array_push($pointer, $left, $right);
while (! empty($pointer)){
//先弹出left, right
$right = array_pop($pointer); //取数组最后一个元素
$left = array_pop($pointer);
$pivot = partition($left, $right, $data);
//先压入$left, 在压入$right
if($left < $pivot - 1) array_push($pointer, $left, $pivot - 1);
if($pivot + 1 < $right) array_push($pointer, $pivot + 1, $right);
}
return $data;
}
/**
* 对数组data中下标从left到right的元素,选取基准元素pivot,
* 根据与基准比较的大小,将各个元素排到基准元素的两端。
* 返回值为最后基准元素的位置
*/
function partition($left, $right, $data)
{
$pivot = $data[$left]; //任选一个元素作为枢轴,这里选择第一个元素
while ($left < $right){
while ($left < $right && $data[$right] >= $pivot) $right--;
$data[$left] = $data[$right];
while ($left < $right && $data[$left] <= $pivot) $left++;
$data[$right] = $data[$left];
}
//当$left = $right
$data[$left] = $pivot; //把中间数添加到$low结束的位置
return $left;
}
var_dump(quickSort([2, 5, 6, 12, 1]));