算法思路分析:
快速排序的思想就是,選一個數作爲基數(這裏我選的是第一個數),大於這個基數的放到右邊,小於這個基數的放到左邊,等於這個基數的數可以放到左邊或右邊,看自己習慣,這裏我是放到了左邊,一趟結束後,將基數放到中間分隔的位置,第二趟將數組從基數的位置分成兩半,分割後的兩個的數組繼續重複以上步驟,選基數,將小數放在基數左邊,將大數放到基數的右邊,在分割數組,直到數組不能再分爲止,排序結束。
例如從小到大排序:
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
/**
第一種寫法
**/
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);
}
/**
第二種寫法
**/
<?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) getPosition($data, $low, $pos - 1); //如果當前位置沒有到達最左側,往左分區
if ($pos + 1 < $high) getPosition($data, $pos + 1, $high); //如果當前位置沒有到達最右側,往右分區
}
$data = array(3,2,6,8);
quickSort($data, 0, count($data) - 1);
var_dump($data);
二、非遞歸實現
用非遞歸的算法,主要是利用到棧的思想,遞歸實現的過程,用數組來保存right兩個指針。用pivot = partition(); 實現分區,每次分爲左右兩個部分(類似於遞歸實現中的左右遞歸),然後返回移動後的$left “指針”,用於判斷是否需要繼續分區。
<?php
/**
* 快排的非遞歸實現
* @param $data
* @return bool
*/
$data = [];
function quickSort($num)
{
if(empty($num)) return false;
global $data;
$data = $num;
$left = 0;
$right = count($num) - 1;
$pointer = [];
array_push($pointer, $left, $right);
while (! empty($pointer)){
//先彈出left, right
$right = array_pop($pointer); //取數組最後一個元素
$left = array_pop($pointer);
$pivot = partition($left, $right);
//先壓入$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)
{
global $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]));