題記:
- 常見的排序演算法有:氣泡排序法,快速排序法,選擇排序法,插入排序法,此處作為自己最近面試準備進行的學習筆記,同時也希望能幫到你。
- 需求:將一個有多個數字的陣列進行從小到大的排序.
排序演算法
【一】.氣泡排序
- 思路分析:
想象一個大水池裡有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;
}
- 小結:
- 時間複雜度:O(n^2)
- 補充:可使用PHP內建函式
sort()
或rsort()
.
- 上述函式對索引陣列按照鍵值進行排序,為 array 中的單元賦予新的鍵名,這將刪除原有的鍵名而不僅是重新排序。如果成功則返回 TRUE,否則返回 FALSE
【二】.選擇排序
- 思路分析:
每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完
/*
* @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值較小時,選擇排序比氣泡排序快
【三】.插入排序
- 思路分析:
- 每步將一個待排序的紀錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止。(從而得到一個新的、個數加一的有序資料)
- 描述:
- ⒈ 從第一個元素開始,該元素可以認為已經被排序
- ⒉ 取出下一個元素,在已經排序的元素序列中從後向前掃描
- ⒊ 如果該元素(已排序)大於新元素,將該元素移到下一位置
- ⒋ 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
- ⒌ 將新元素插入到下一位置中
- ⒍ 重複步驟 2~5
- 程式碼實現
- 此處提供兩種寫法,主要是迴圈的寫法稍有不同,可作參考.
/*
* 插入排序法
* 每步將一個待排序的記錄,按其關鍵碼值的大小插入前面已經排序的檔案中適當位置上,直到全部插入完為止。
* */
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;
}
- 小結:
- 時間複雜度:O(n^2)
- 空間複雜度:O(1) (用於記錄需要插入的資料)
-
穩定的排序方法
- 演算法適用於少量資料的排序
- 如果比較操作的代價比交換操作大的話,可以採用二分查詢法來減少比較操作的數目。該演算法可以認為是插入排序的一個變種,稱為二分查詢排序。
【四】.快速排序
- 思路分析:
- 通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,
- 然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列
- 程式碼實現
- 注:網上多數為
quick_sort2()
這類的寫法,感覺並非原演算法的描述,建議可做參考.
- 或許程式碼
quick_sort()
有所欠缺,並未發現能有較快的排序效果,尷尬了.
/**
* @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);
}
- 小結:
- 既不浪費空間又可以快一點的排序演算法
- 最差時間複雜度O(N^2),平均時間複雜度為O(NlogN)
- 推薦文章-坐在馬桶上看演算法:快速排序(注:其中的演算法邏輯並非標準邏輯,建議閱讀底部的評論,可做參考)
【五】.計數排序
- 思路分析
- 計數排序使用一個額外的陣列C,其中第i個元素是待排序陣列A中值等於i的元素的個數。然後根據陣列C來將A中的元素排到正確的位置。它只能對整數進行排序
- 演算法描述:
- 找出待排序的陣列中最大和最小的元素;
- 統計陣列中每個值為i的元素出現的次數,存入陣列C的第i項;
- 對所有的計數累加(從C中的第一個元素開始,每一項和前一項相加);
- 反向填充目標陣列:將每個元素i放在新陣列的第C(i)項,每放一個元素就將C(i)減去1
/**
* 計數排序
* @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;
}
-
注:
- 上述程式碼是我根據對木桶排序的定義進行的設計,因為網上多數的PHP程式碼感覺不合規範,其中的
insertSort()
為借用的文中所寫的插入排序
- 通過測試發現,此方法耗時比
countingSort()
要長好多,此處僅做參考不做推薦。
-
總結:
- 當輸入的元素是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】用時測試
- 為了簡單比較幾種演算法的用時大小,本人隨機生成了數量為10000,數值在300以內的測試陣列,文中介紹的演算法用時如下:
bucketsort 用時:1013.6640071869 ms
countingSort 用時:5.6858062744141 ms
quick_sort 用時:66540.108919144 ms
selectSort 用時:15234.955072403 ms
bubbleSort 用時:162055.89604378 ms
insertSort 用時:12029.093980789 ms
內建sort 用時:3.0169486999512 ms
- 所以,簡單需求的陣列排序處理還是建議使用內建的
sort()
函式.
【4】參考文章
【5】提示
- 本文主要為了學習而生
- 累了,就寫這些吧
- 如果大家看出錯誤和意見,希望能給予提醒,共同進步,Thanks ...