什麼是快速排序數組
快速排序是運用分治的方法,經過一趟遍歷將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再用一樣的方法對這兩部分數據分別進行快速排序。 測試
它的流程是這樣ui
經常使用實現spa
先來看第一種實現方法,也是比較經常使用的方法,採用臨時數組來存儲分別存儲左邊元數和左邊元素。咱們選第一個元數爲基準值。code
function quickSortByTempArr(&$arr) { $length = count($arr); if ($length <= 1) return; $left = $right = []; for ($i = 1; $i < $length; $i++) { if ($arr[$i] < $arr[0]) { $left[] = $arr[$i]; } else { $right[] = $arr[$i]; } } quickSortByTempArr($left); quickSortByTempArr($right); $arr = array_merge($left, [$arr[0]], $right); }
該方法符合分治的思想,缺點就是申請了臨時數組分別存儲左右元素,空間複雜度爲O(n)blog
再來看方法二,採用元素交換的方式,選取中間元素(或選第一個元素)爲基準值,每次比較把更小的元素交換到左邊,更大的元素交換到右邊。排序
function quickSort(&$arr, $start, $end) { if ($start >= $end) return; $smaller = $start; $bigger = $end; $middle = $start + ($end - $start) / 2; while ($smaller <= $bigger) { // 若是更小,就呆在左邊 if ($arr[$smaller] < $arr[$middle]) { if ($smaller > $middle) { list($arr[$smaller], $arr[$middle]) = [$arr[$middle], $arr[$smaller]]; $middle = $smaller; } $smaller++; continue; } // 重複元素 if ($arr[$smaller] == $arr[$middle]) { $middle = $smaller; $smaller++; continue; } // 更大,則跟最右邊的未分類數據交換 // 右邊全是排好序時, 直接跟middle 交換 if ($bigger <= $middle) { list($arr[$smaller], $arr[$middle]) = [$arr[$middle], $arr[$smaller]]; $middle = $smaller; } else { list($arr[$smaller], $arr[$bigger]) = [$arr[$bigger], $arr[$smaller]]; } $bigger--; } quickSort($arr, $start, $middle - 1); quickSort($arr, $bigger + 1, $end); }
能夠看出,該方法並無申請新的臨時數組,空間複雜度爲 O(1),可是該方法效率不如方法一,測試結果執行時間恆定爲方法一的接近2倍,顯然不夠好。仔細查看執行流程,發現有時會把右側大於基準值的元素也交換到左側,而後再交換回去,冗餘操做。遞歸
方法三,仍是採用元素交換的方式,但每次交換都只把左則大於基準值的元素與右側小於基準值的元素進行交換。it
function quickSortOptimize(&$arr, $start, $end) { if ($start >= $end) return; $smaller = $start; $bigger = $end; $temp = $arr[$smaller]; while ($smaller < $bigger) { // 從右則找一個小於基準值的元素,賦給smaller,而後把該值空出來 while ($smaller < $bigger && $arr[$bigger] >= $temp) $bigger--; $arr[$smaller] = $arr[$bigger]; // 而後從左則找一個大於基準值的元素,賦給空出來的 $bigger while ($smaller < $bigger && $arr[$smaller] < $temp) $smaller++; $arr[$bigger] = $arr[$smaller]; } $arr[$smaller] = $temp; quickSortOptimize($arr, $start, $smaller - 1); quickSortOptimize($arr, $bigger + 1, $end); }
以上三種方法,都是遞歸的方式實現的,那有沒有非遞歸的方式呢?固然也是有的,咱們把每次待排序的數組分段,丟到一個臨時數組裏面,依次把它們取出來排序就ok了,在方法三的基礎上,稍做改動。io
function quickSortStack(&$arr, $start, $end) { $stack[] = [$start, $end]; while (!empty($stack)) { list($start, $end) = array_pop($stack); $smaller = $start; $bigger = $end; if ($start >= $end) continue; $temp = $arr[$smaller]; while ($smaller < $bigger) { // 從右則找一個小於基準值的元素,賦給smaller,而後把該值空出來 while ($smaller < $bigger && $arr[$bigger] >= $temp) $bigger--; $arr[$smaller] = $arr[$bigger]; // 而後從左則找一個大於基準值的元素,賦給空出來的 $bigger while ($smaller < $bigger && $arr[$smaller] < $temp) $smaller++; $arr[$bigger] = $arr[$smaller]; } $arr[$smaller] = $temp; if ($start < $smaller - 1) array_push($stack, [$start, $smaller - 1]); if ($end > $bigger + 1) array_push($stack, [$bigger + 1, $end]); } }
藉助了一個臨時數組,來存儲邊界值。時間複雜度和方法三徹底一致。
接下來咱們分別測試一下這幾個方法效率,100萬個元素爲例
$arr = range(1, 1000000); $sortArr = $arr; shuffle($arr); $tempArr1 = $tempArr2 = $tempArr3 = $arr; echo '---------方法一-----------', PHP_EOL; $startTime = microtime(true); quickSortByTempArr($arr); $endTime = microtime(true); echo 'sort -- ', $sortArr == $arr, PHP_EOL; echo 'total time', $endTime - $startTime, PHP_EOL; echo '---------方法二-----------', PHP_EOL; $startTime = microtime(true); quickSort($tempArr1, 0, count($tempArr1) - 1); $endTime = microtime(true); echo 'sort -- ', $sortArr == $tempArr1, PHP_EOL; echo 'total time', $endTime - $startTime, PHP_EOL; echo '---------方法三-----------', PHP_EOL; $startTime = microtime(true); quickSortOptimize($tempArr2, 0, count($tempArr2) - 1); $endTime = microtime(true); echo 'sort -- ', $sortArr == $tempArr2, PHP_EOL; echo 'total time', $endTime - $startTime, PHP_EOL; echo '---------方法四-----------', PHP_EOL; $startTime = microtime(true); quickSortStack($tempArr3, 0, count($tempArr3) - 1); $endTime = microtime(true); echo 'sort -- ', $sortArr == $tempArr3, PHP_EOL; echo 'total time', $endTime - $startTime, PHP_EOL;