排序演算法
【一】.冒泡排序
思路分析:
想象一個大水池裡有N多還未排好的序列的氫氣球,較大的先冒出來,然後依次是較小的往上冒。即,每次比較相鄰的兩個數,小的在前大的在後,否則進行位置互換。
代碼實現
/** * 交換方法 * @param array $arr 目標數組 * @param $a 索引a * @param $b 索引b * @param bool $flag 交換標誌 * @return bool */ function swap(array &$arr,$a,$b,$flag = false){ // 遍曆i後面的元素,只要該元素小於當前元素,就把較小的往前冒泡 if($arr[$a] > $arr[$b]){ $temp = $arr[$a]; $arr[$a] = $arr[$b]; $arr[$b] = $temp; $flag = true; } return $flag; } /** * 第一種寫法 * @param $arr 所要排序的數組 * @return mixed 返回的數組 */ function bubbleSort($arr) { $len = count($arr); if ($len <= 1) {return $arr;} //該層迴圈控制 需要冒泡的輪數 for ($i = 0; $i < $len-1; $i++) { //該層迴圈用來控制每輪 冒出一個數 需要比較的次數 for ($j = $i + 1; $j < $len; $j++) { // 或者 $this->swap($arr,$j,$j+1); $this->swap($arr,$i,$j); } } return $arr; } //第二種寫法 public function BubbleSort2($arr){ $len = count($arr); if ($len <= 1) {return $arr;} for ($i = 0;$i < $len-1;$i++){ //TODO 本趟排序開始前,交換標誌應為假 $flag = false; for ($j = 0;$j <= $len-2;$j++){ $flag = $this->swap($arr,$j,$j+1,$flag); } if(!$flag) return $arr; } return $arr; } //第三種寫法 function BubbleSort3(array &$arr){ $len = count($arr); if ($len <= 1) {return $arr;} for($i = 0;$i < $len-1;$i++){ //從後往前逐層上浮小的元素 $j >= 0 for($j = $len - 2;$j >= $i ;$j --){ $this->swap($arr,$j,$j+1); } } return $arr; } //第四種寫法 function bubbleSort4($arr) { $len = count($arr); if ($len <= 1) {return $arr;} for($i = 0;$i < $len-1;$i++) { for($j = 0;$j < $len-$i-1;$j++) { $this->swap($arr,$j,$j+1); } } return $arr; }
小結:
【二】.選擇排序
思路分析:
每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完
代碼實現
/* * @param 選擇排序法 * 每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完 * */ function selectSort($arr){ //雙重迴圈完成,外層控制輪數,內層控制比較次數 $len = count($arr); if ($len <= 1) {return $arr;} for ($i = 0; $i < $len-1; $i++) { $minIndex = $i; // 找出i後面最小的元素與當前元素交換 for ($j = $i + 1; $j < $len; $j++) { if ($arr[$minIndex] > $arr[$j]){ $minIndex = $j; } } if ($minIndex != $i) { $temp = $arr[$i]; $arr[$i] = $arr[$minIndex]; $arr[$minIndex] = $temp; } } return $arr; }
小結:
時間複雜度:O(n^2)
不穩定的排序方法(比如序列[5, 5, 3]第一次就將第一個[5]與[3]交換,導致第一個5挪動到第二個5後面)。
在一趟選擇,如果一個元素比當前元素小,而該小的元素又出現在一個和當前元素相等的元素後面,那麼交換後穩定性就被破壞了
最好情況是,已經有序,交換0次;最壞情況交換n-1次,逆序交換n/2次。交換次數比冒泡排序少多了,由於交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快
【三】.插入排序
思路分析:
代碼實現
/* * 插入排序法 * 每步將一個待排序的記錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止。 * */ function insertSort($arr){ $len = count($arr); if ($len <= 1) {return $arr;} //先預設$array[0],已經有序,是有序表 for($i = 1;$i < $len;$i++){ if ($arr[$i] < $arr[$i-1]){ $insertVal = $arr[$i]; //$insertVal是準備插入的數 $insertIndex = $i - 1; //有序表中準備比較的數的下標 while($insertIndex >= 0 && $insertVal < $arr[$insertIndex]){ $arr[$insertIndex + 1] = $arr[$insertIndex]; //將數組往後挪 $insertIndex--; //將下標往前挪,準備與前一個進行比較 } if($insertIndex + 1 !== $i){ $arr[$insertIndex + 1] = $insertVal; } } } return $arr; } function insertSort2($arr){ $len = count($arr); if ($len <= 1) {return $arr;} //先預設$array[0],已經有序,是有序表 for($i = 1;$i < $len;$i++){ if ($arr[$i] < $arr[$i-1]){ $insertVal = $arr[$i]; //$insertVal是準備插入的數 //$j 有序表中準備比較的數的下標 //$j-- 將下標往前挪,準備與前一個進行比較 for ($j = $i-1;$j >= 0 && $insertVal < $arr[$j];$j--){ $arr[$j+1]= $arr[$j];//將數組往後挪 } $arr[$j + 1] = $insertVal; } } return $arr; }
小結:
【四】.快速排序
思路分析:
代碼實現
/** * @param $arr 目標數組 * @param int $l 左起座標 * @param $r 右起座標 初始化傳入數組時,$r = count($arr)-1 * @return mixed */ public function quick_sort(&$arr, $l=0, $r) { $length = count($arr); //先判斷是否需要繼續進行 遞迴出口:數組長度為1,直接返回數組 if(!is_array($arr)||$length <= 1) {return $arr;} if ($l < $r) { $i = $l; $j = $r; $baseVal = $arr[$l]; while ($i < $j) { // 從右向左找第一個小於$baseVal的數 while($i < $j && $arr[$j] >= $baseVal) $j--; if($i < $j) $arr[$i++] = $arr[$j]; // 從左向右找第一個大於等於$baseVal的數 while($i < $j && $arr[$i] < $baseVal) $i++; if($i < $j) $arr[$j--] = $arr[$i]; } $arr[$i] = $baseVal; $this->quick_sort($arr, $l, $i - 1); // 遞迴調用 $this->quick_sort($arr, $i + 1, $r); return $arr; } } /* * 快速排序法 * */ public function quick_sort2($arr) { $length = count($arr); //先判斷是否需要繼續進行 遞迴出口:數組長度為1,直接返回數組 if(!is_array($arr)||$length <= 1) {return $arr;} //選擇第一個元素作為基準 $baseValue = $arr[0]; //遍曆除了尺規外的所有元素,按照大小關係放入兩個數組內 //初始化兩個數組 $leftArr = array(); //小於基準的 $rightArr = array(); //大於基準的 //使用for迴圈進行遍曆,把選定的基準當做比較的對象 for($i = 1; $i<$length; $i++) { if( $arr[$i] < $baseValue) { //放入左邊數組 $leftArr[] = $arr[$i]; } else { //放入右邊數組 $rightArr[] = $arr[$i]; } } //再分別對左邊和右邊的數組進行相同的排序處理方式遞迴調用這個函數 $leftArr = $this->quick_sort2($leftArr); $rightArr = $this->quick_sort2($rightArr); //合并 左邊 尺規 右邊, 注意:array($baseValue),關聯著重複資料 return array_merge($leftArr, array($baseValue), $rightArr); }
小結:
【五】.計數排序
思路分析
代碼實現
/** * 計數排序 * @param $arr * @return array */ function countingSort($arr) { $len = count( $arr ); if( $len <= 1 ) return $arr; // 找出待排序的數組中最大值和最小值 $min = min($arr); $max = max($arr); // 計算待排序的數組中每個元素的個數 $countArr = array(); for($i = $min; $i <= $max; $i++) { $countArr[$i] = 0; } foreach($arr as $v) { $countArr[$v] += 1; } $resArr = array(); foreach ($countArr as $k=>$c) { for($i = 0; $i < $c; $i++) { $resArr[] = $k; } } return $resArr; }
小結:
計數排序的核心在於將輸入的資料值轉化為鍵儲存在額外開闢的數組空間中。
作為一種線性時間複雜度的排序,計數排序要求輸入的資料必須是有確定範圍的整數。
計數排序不是比較排序,排序的速度快於任何比較排序演算法
最佳情況:T(n) = O(n+k)
最差情況:T(n) = O(n+k)
平均情況:T(n) = O(n+k)
限制條件很多 注意
【六】.桶排序
思路分析
代碼實現
/** * 木桶排序設計 * @param $arr 目標數組 * @param int $bucketCount 分配的木桶數目(整數) * @return array */ public function bucketSort($arr,$bucketCount = 10) { $len = count($arr); $max = max($arr)+1; if ($len <= 1) {return $arr;} //填充木桶 $arrFill = array_fill(0, $bucketCount, []); //開始標示木桶 for($i = 0; $i < $len ; $i++) { $key = intval($arr[$i]/($max/$bucketCount)); array_push($arrFill[$key] , $arr[$i]); //TODO 測試發現:如果此處調用,耗時翻倍 /*if(count($arrFill[$key])){ $arrFill[$key] = $this->insertSort($arrFill[$key]); }*/ } //對每個不是空的桶進行排序 foreach ($arrFill as $key=>$f){ if (count($f)){ $arrFill[$key] = $this->insertSort($f); } } //開始從木桶中拿出資料 for($i = 0; $i < count($arrFill); $i ++) { if($arrFill[$i]){ for($j = 0; $j <= count($arrFill[$i]); $j++) { //這一行主要用來控制輸出多個數字 if ($arrFill[$i][$j]){ $arrBucket[] = $arrFill[$i][$j]; } } }; } return $arrBucket; }
注:
總結:
當輸入的元素是n 個0到k之間的整數時,它的已耗用時間是 O(n + k)。計數排序不是比較排序,排序的速度快於任何比較排序演算法。由於用來計數的數組C的長度取決於待排序數組中資料的範圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於資料範圍很大的數組,需要大量時間和記憶體。
穩定的排序方法
桶排序是計數排序的升級版
最佳情況:T(n) = O(n+k)
最差情況:T(n) = O(n^2)
平均情況:T(n) = O(n+k)
附錄
【1】排序演算法總結
【2】自行分析
/** * @param $arr 目標數組 * @return array 返回的已排序數組 */ public function bOrCSort($arr) { $len = count($arr); $max = max($arr); if ($len <= 1) {return $arr;} //填充木桶 $arrFill = array_fill(0, $max, 0); for($i = 0; $i < $len ; $i++) { $arrFill[$arr[$i]] ++; } //開始從木桶中拿出資料 for($i = 0; $i <= $max; $i ++) { for($j = 1; $j <= $arrFill[$i]; $j++) { //這一行主要用來控制輸出多個數字 $arrRes[] = $i; } } return $arrRes; }
【3】用時測試
bucketsort 用時:1013.6640071869 mscountingSort 用時:5.6858062744141 msquick_sort 用時:66540.108919144 msselectSort 用時:15234.955072403 msbubbleSort 用時:162055.89604378 msinsertSort 用時:12029.093980789 ms內建sort 用時:3.0169486999512 ms
【4】參考文章
【5】提示