常見排序算法及PHP 實(shí)現(xiàn),你學(xué)會(huì)了嗎?
10大排序算法對(duì)比
冒泡排序
算法描述
冒泡排序是一種交換排序,它的基本思想是:對(duì)待排序記錄從后往前(逆序)進(jìn)行多遍掃描,當(dāng)發(fā)現(xiàn)相鄰兩條記錄的次序與排序要求的規(guī)則不符時(shí),就將這兩個(gè)記錄進(jìn)行交換。這樣,值較小的記錄將逐漸從后面向前移動(dòng),就像氣泡在水中向上浮一樣。
實(shí)現(xiàn)步驟
假設(shè)需要排序的記錄有n 個(gè),其值保存在數(shù)組A 中,使用冒泡排序法,需對(duì)數(shù)組A進(jìn)行n-1 次掃描,完成排序操作。具體過程如下:
1. 將A[n-1] 與A[n] 進(jìn)行比較,若A[n] < A[n-1] ,則交換兩元系的位置。
2. 修改數(shù)組下標(biāo),使需要比較的兩個(gè)元素為A[n-1] 和A[n-2] ,重復(fù)步驟(1),對(duì)這兩個(gè)元素進(jìn)行比較。重復(fù)這個(gè)過程,直到對(duì)A[1] 和A[0] 進(jìn)行比較完為止。完成第1 遍掃描。
3. 經(jīng)過第1 遍掃描后,最小的元素已經(jīng)像氣泡一樣“浮”到最上面,即位于元素A[0] 中了。接下來重復(fù)前面的步驟,進(jìn)行第2 遍掃描,只是掃描結(jié)束位置到A[2] 與A[1] 進(jìn)行比較完為止(因?yàn)锳[0]中已經(jīng)是最小的數(shù)據(jù),不用再進(jìn)行比較)。
4. 通過n-1 遍掃描,前n-1 個(gè)數(shù)都已經(jīng)排序完成,最后一個(gè)元素A[n] 肯定就是最大的數(shù)了。至此,完成排序操作。
圖片
代碼實(shí)現(xiàn)
/**
* 冒泡排序
* @param array $arr
*/
function bubbleSort(array &$arr) : void{
$length = count($arr);
// 外層循環(huán),從數(shù)組首部開始,每完成一次循環(huán),可確定$arr[$i] 位置的元素
for ($i = 0; $i < $length; $i++){
// 內(nèi)層循環(huán),$j 從后往前循環(huán)
for ($j = $length - 1; $j > $i; $j--) {
// 若前面的值大于后面的值,則互換位置
if ($arr[$j] < $arr[$j - 1]) {
// 互換數(shù)組兩個(gè)位置的值
[$arr[$j], $arr[$j - 1]] = [$arr[$j - 1], $arr[$j]];
}
}
}
}
希爾排序
算法描述
希爾排序可以說是插入排序的一種變種。無論是插入排序還是冒泡排序,如果數(shù)組的最大值剛好是在第一位,要將它挪到正確的位置就需要 n - 1 次移動(dòng)。
實(shí)現(xiàn)步驟
希爾排序的思想是采用插入排序的方法,先讓數(shù)組中任意間隔為 h 的元素有序,剛開始 h 的大小可以是 h = n / 2,接著讓 h = n / 4,讓 h 一直縮小,當(dāng) h = 1 時(shí),也就是此時(shí)數(shù)組中任意間隔為1的元素有序,此時(shí)的數(shù)組就是有序的了。
代碼實(shí)現(xiàn)
function shell_sort(array $arr){
// 將$arr按升序排列
$len = count($arr);
$f = 3;// 定義因子
$h = 1;// 最小為1
while ($h < $len/$f){
$h = $f*$h + 1; // 1, 4, 13, 40, 121, 364, 1093, ...
}
while ($h >= 1){ // 將數(shù)組變?yōu)閔有序
for ($i = $h; $i < $len; $i++){ // 將a[i]插入到a[i-h], a[i-2*h], a[i-3*h]... 之中 (算法的關(guān)鍵
for ($j = $i; $j >= $h; $j -= $h){
if ($arr[$j] < $arr[$j-$h]){
$temp = $arr[$j];
$arr[$j] = $arr[$j-$h];
$arr[$j-$h] = $temp;
}
//print_r($arr);echo '<br/>'; // 打開這行注釋,可以看到每一步被替換的情形
}
}
$h = intval($h/$f);
}
return $arr;
}
選擇排序
算法描述
選擇排序是通過n-i 次關(guān)鍵字間的比較,從n-i+1 個(gè)記錄中選出關(guān)鍵字最小的記錄,并和第i ( 1 <= i <= n ) 個(gè)記錄交換。
實(shí)現(xiàn)步驟
1. 維護(hù)數(shù)組中最小的前n 個(gè)元素的已排序序列。
2. 每次從剩余未排序的元素中選取最小的元素,將其放在已排序序列的后面,作為序列的第n+1 個(gè)記元素。
3. 以空序列作為排序工作的開始,直到未排序的序列里只剩一個(gè)元素時(shí)(它必然為最大),只需直接將其放在已排序的記錄之后,整個(gè)排序就完成了。
圖片
代碼實(shí)現(xiàn)
/**
* 選擇排序
* @param array $arr
*/
function selectionSort(array &$arr) : void{
$length = count($arr);
// 外層循環(huán),從數(shù)組首部開始,每完成一次循環(huán),可確定一個(gè)元素的位置
for ($i = 0; $i < $length - 1; $i++) {
// 選定的最小值的索引
$minIdx = $i;
// 從$i + 1 位開始循環(huán),判斷當(dāng)前選定的元素是否是當(dāng)次循環(huán)的最小值
for ($j = $i + 1; $j < $length; $j++) {
// 若出現(xiàn)比選定的值還小的值,則替換最小值的索引
if ($arr[$minIdx] > $arr[$j]) {
$minIdx = $j;
}
}
// 互換數(shù)組兩個(gè)位置的值
[$arr[$i], $arr[$minIdx]] = [$arr[$minIdx], $arr[$i]];
}
}
/**
* 選擇排序- 方法2
* @param array $arr
*/
function selectionSort2(array &$arr) : void{
$length = count($arr);
// 外層循環(huán),從數(shù)組首部開始,每完成一次循環(huán),依次確定數(shù)組元素的位置
for ($i = 0; $i < $length; $i++) {
// 從$i + 1 位開始循環(huán),依次判定$arr[$i] 與$arr[$j] 的大小
for ($j = $i + 1; $j < $length; $j++) {
// 若$arr[$i] 比$arr[$j] 大,則互換兩個(gè)元素的位置
if ($arr[$i] > $arr[$j]) {
// 互換數(shù)組兩個(gè)位置的值
[$arr[$j], $arr[$i]] = [$arr[$i], $arr[$j]];
}
}
}
}
插入排序
算法描述
插入排序是通過構(gòu)建有序序列,從未排序數(shù)據(jù)中選擇一個(gè)元素,在已排序序列中從后向前掃描,找到相應(yīng)位置并插入。插入排序在從后向前掃描過程中,需要把已排序元素逐個(gè)向后移動(dòng),為最新元素提供插入空間。
實(shí)現(xiàn)步驟
1. 對(duì)于第1 個(gè)元素,因?yàn)闆]有比較,將其作為已經(jīng)有序的序列。
2. 從數(shù)組中獲取下一個(gè)元素,在已經(jīng)排序的元素序列中從后向前掃描,并進(jìn)行判斷。
3. 若排序序列的元素大于新元素,則將該元素向后移動(dòng)一位。
4. 重復(fù)步驟(3),直到在已排序的元素中找到小于或者等于新元素的元素,將新元素插入到該元素的后面。
5. 重復(fù)步驟(2) ~ (4),直到完成排序。
圖片
代碼實(shí)現(xiàn)
/**
* 插入排序
* @param array $arr
*/
function insertionSort(array &$arr) : void{
$length = count($arr);
// 從數(shù)組首部開始排序,每完成一次循環(huán),可確定一個(gè)元素的位置
for ($i = 0; $i < $length - 1; $i++) {
// 內(nèi)層循環(huán)從$i + 1 個(gè)元素開始,一位一位向前比較
// 若前面的值比自己大,則替換,直到前面的值比自己小了,停止循環(huán)
for ($j = $i + 1; $j > 0; $j--) {
if ($arr[$j] >= $arr[$j - 1]) {
break;
}
[[$arr[$j], $arr[$j - 1]]] = [[$arr[$j - 1], $arr[$j]]];
}
}
}
/**
* 插入排序- 方法2
* @param array $arr
*/
function insertionSort2(array &$arr) : void{
$length = count($arr);
// 從數(shù)組首部開始排序,每完成一次循環(huán),可確定一個(gè)元素的位置
for ($i = 0; $i < $length - 1; $i++) {
// 從第二個(gè)元素開始,選擇固定位置的值作為基準(zhǔn)值
$currentVal = $arr[$i + 1];
// 初始鍵位于選定值的前一個(gè)位置
$preIdx = $i;
// 拿基準(zhǔn)值一步一步向前比較,直到基準(zhǔn)值比前面的值小,則兩值互換位置
while ($preIdx >= 0 && $currentVal < $arr[$preIdx]) {
$arr[$preIdx + 1] = $arr[$preIdx];
$arr[$preIdx] = $currentVal;
$preIdx--;
}
}
}
快速排序
算法描述
快速排序是通過一趟排序?qū)⒋庞涗浄指畛瑟?dú)立的兩部分,其中一部分記錄的關(guān)鍵字均比另一部分記錄的關(guān)鍵字小,則可分別對(duì)這兩部分記錄繼續(xù)進(jìn)行排序,以達(dá)到整個(gè)序列有序的目的。
實(shí)現(xiàn)步驟
快速排序使用分治策略來把待排序數(shù)據(jù)序列分為兩個(gè)子序列,具體步驟如下:
1. 從數(shù)列中挑出一個(gè)元素,以該元素為“基準(zhǔn)”。
2. 掃描一遍數(shù)列,將所有比“基準(zhǔn)”小的元素排在基準(zhǔn)前面,所有比“基準(zhǔn)”大的元素排在基準(zhǔn)后面。
3. 通過遞歸,將各子序列劃分為更小的序列,直到把小于基準(zhǔn)值元素的子數(shù)列和大于基誰值元素的子數(shù)列排序。
圖片
代碼實(shí)現(xiàn)
/**
* 快速排序
* @param $arr
*/
function quickSort(& $arr) : void{
$length = count($arr);
// 若數(shù)組為空,則不需要運(yùn)行
if ($length <= 1) {
return;
}
$middle = $arr[0]; // 選定一個(gè)中間值
$left = []; // 接收小于中間值
$right = [];// 接收大于中間值
// 循環(huán)比較
for ($i = 1; $i < $length; $i++) {
if ($middle < $arr[$i]) {
// 大于中間值
$right[] = $arr[$i];
} else {
// 小于或等于中間值
$left[] = $arr[$i];
}
}
// 遞歸排序劃分好的左右兩邊
quickSort($left);
quickSort($right);
$arr = array_merge($left, [$middle], $right);
}
堆排序
算法描述
堆排序(Heapsort)是指利用堆這種數(shù)據(jù)結(jié)構(gòu)所設(shè)計(jì)的一種排序算法。堆積是一個(gè)近似完全二叉樹的結(jié)構(gòu),并同時(shí)滿足堆積的性質(zhì):即子結(jié)點(diǎn)的鍵值或索引總是小于(或者大于)它的父節(jié)點(diǎn)。
實(shí)現(xiàn)步驟
1、將初始待排序關(guān)鍵字序列(R1,R2….Rn)構(gòu)建成最大堆,此堆為初始的無序區(qū)。
2、將堆頂元素R[1]與最后一個(gè)元素R[n]交換,此時(shí)得到新的無序區(qū)(R1,R2,……Rn-1)和新的有序區(qū)(Rn),且滿足R[1,2…n-1]<=R[n]。
3、由于交換后新的堆頂R[1]可能違反堆的性質(zhì),因此需要對(duì)當(dāng)前無序區(qū)(R1,R2,……Rn-1)調(diào)整為新堆,然后再次將R[1]與無序區(qū)最后一個(gè)元素交換,得到新的無序區(qū)(R1,R2….Rn-2)和新的有序區(qū)(Rn-1,Rn)。不斷重復(fù)此過程直到有序區(qū)的元素個(gè)數(shù)為n-1,則整個(gè)排序過程完成。
圖片
代碼實(shí)現(xiàn)
//因?yàn)槭菙?shù)組,下標(biāo)從0開始,所以,下標(biāo)為n根結(jié)點(diǎn)的左子結(jié)點(diǎn)為2n+1,右子結(jié)點(diǎn)為2n+2;
//初始化值,建立初始堆
$arr=array(49,38,65,97,76,13,27,50);
$arrSize=count($arr);
//將第一次排序抽出來,因?yàn)樽詈笠淮闻判虿恍枰俳粨Q值了。
buildHeap($arr,$arrSize);
for($i=$arrSize-1;$i>0;$i--){
swap($arr,$i,0);
$arrSize--;
buildHeap($arr,$arrSize);
}
//用數(shù)組建立最小堆
function buildHeap(&$arr,$arrSize){
//計(jì)算出最開始的下標(biāo)$index,如圖,為數(shù)字"97"所在位置,比較每一個(gè)子樹的父結(jié)點(diǎn)和子結(jié)點(diǎn),將最小值存入父結(jié)點(diǎn)中
//從$index處對(duì)一個(gè)樹進(jìn)行循環(huán)比較,形成最小堆
for($index=intval($arrSize/2)-1; $index>=0; $index--){
//如果有左節(jié)點(diǎn),將其下標(biāo)存進(jìn)最小值$min
if($index*2+1<$arrSize){
$min=$index*2+1;
//如果有右子結(jié)點(diǎn),比較左右結(jié)點(diǎn)的大小,如果右子結(jié)點(diǎn)更小,將其結(jié)點(diǎn)的下標(biāo)記錄進(jìn)最小值$min
if($index*2+2<$arrSize){
if($arr[$index*2+2]<$arr[$min]){
$min=$index*2+2;
}
}
//將子結(jié)點(diǎn)中較小的和父結(jié)點(diǎn)比較,若子結(jié)點(diǎn)較小,與父結(jié)點(diǎn)交換位置,同時(shí)更新較小
if($arr[$min]<$arr[$index]){
swap($arr,$min,$index);
}
}
}
}
//此函數(shù)用來交換下數(shù)組$arr中下標(biāo)為$one和$another的數(shù)據(jù)
function swap(&$arr,$one,$another){
$tmp=$arr[$one];
$arr[$one]=$arr[$another];
$arr[$another]=$tmp;
}
歸并排序
算法描述
歸并是一種典型的序列操作,其工作是把兩個(gè)或更多有序序列合并為一個(gè)有序序列。基于歸并的思想也可以實(shí)現(xiàn)排序,稱為歸并排序。
實(shí)現(xiàn)步驟
1. 初始時(shí),把待排序序列中的n 個(gè)元素看成n 個(gè)有序子序列(因?yàn)橹挥? 個(gè)元素的序列總是排好序的),每個(gè)子序列的長(zhǎng)度均為1。
2. 把序列組里的有序子序列兩兩歸并,每完成一論歸并,序列組里的序列個(gè)數(shù)減半,每個(gè)子序列的長(zhǎng)度加倍。
3. 對(duì)加長(zhǎng)的有序子序列重復(fù)上面的操作,最終得到一個(gè)長(zhǎng)度為n 的有序序列。這種歸并方法也稱為簡(jiǎn)單的二路歸并排序,其中每次操作都是把兩個(gè)有序序列合并為一個(gè)有序序列。也可考慮三路歸并或更多路的歸并。
圖片
代碼實(shí)現(xiàn)
/**
* 歸并排序
* @param array $arr
* @return array
*/
function mergeSort(array $arr){
// 計(jì)算數(shù)組長(zhǎng)度,若長(zhǎng)度不大于1,則不需要排序
$length = count($arr);
if ($length <= 1) {
return $arr;
}
// 獲取數(shù)組中間位置的索引
$midIdx = floor($length / 2);
// 把數(shù)組從中間拆分成左右兩部分
$left = mergeSort(array_slice($arr, 0, $midIdx));
$right = mergeSort(array_slice($arr, $midIdx));
// 合并兩部分,同時(shí)進(jìn)行排序
return merge($left, $right);
}
/**
* 合并數(shù)組,同時(shí)進(jìn)行排序
* @param array $left
* @param array $right
* @return array
*/
function merge(array $left, array $right){
// 分別計(jì)算左右兩數(shù)組的長(zhǎng)度
$lLength = count($left);
$rLength = count($right);
// 左右兩數(shù)組的索引
$l = $r = 0;
$lists = [];
// 只有左右兩數(shù)組都未遍歷完成時(shí),才有必要繼續(xù)遍歷
// 當(dāng)其中一個(gè)數(shù)組的元素遍歷完成,說明另一個(gè)數(shù)組中未遍歷過的值比遍歷過的值都大
while ($l < $lLength && $r < $rLength) {
// 比較$left[$l] 和$right[$r],取其中較小的值加入到$lists 數(shù)組中
if ($left[$l] < $right[$r]) {
$lists[] = $left[$l];
$l++;
} else {
$lists[] = $right[$r];
$r++;
}
}
// 合并$lists 和$left、$right 中剩余的元素
return array_merge($lists, array_slice($left, $l), array_slice($right,$r));
}
/**
* 合并數(shù)組,同時(shí)進(jìn)行排序- 方法2
* @param array $left
* @param array $right
* @return array
*/
function merge2(array $left, array $right){
// 分別計(jì)算左右兩數(shù)組的長(zhǎng)度
$lLength = count($left);
$rLength = count($right);
// 左右兩數(shù)組的索引
$l = $r = 0;
$lists = [];
// 只有左右兩數(shù)組都未遍歷完成時(shí),才有必要繼續(xù)遍歷
// 當(dāng)其中一個(gè)數(shù)組的元素遍歷完成,說明另一個(gè)數(shù)組中未遍歷過的值比遍歷過的值都大
while ($l < $lLength && $r < $rLength) {
// 比較$left[$l] 和$right[$r],取其中較小的值加入到$lists 數(shù)組中
if ($left[$l] < $right[$r]) {
$lists[] = $left[$l];
// PHP 中unset 掉數(shù)組中的元素后,其他元素的鍵名不變
unset($left[$l]);
$l++;
} else {
$lists[] = $right[$r];
unset($right[$r]);
$r++;
}
}
// 合并$lists 和$left、$right 中剩余的元素
return array_merge($lists, $left, $right);
}
/**
* 合并數(shù)組,同時(shí)進(jìn)行排序- 方法3
* @param array $left
* @param array $right
* @return array
*/
function merge3(array $left, array $right){
// 分別計(jì)算左右兩數(shù)組的長(zhǎng)度
$lLength = count($left);
$rLength = count($right);
$lists = [];
// 只有左右兩數(shù)組都未遍歷完成時(shí),才有必要繼續(xù)遍歷
// 當(dāng)其中一個(gè)數(shù)組的元素遍歷完成,說明另一個(gè)數(shù)組中未遍歷過的值比遍歷過的值都大
while ($lLength > 0 && $rLength > 0) {
// 比較$left[$l] 和$right[$r],取其中較小的值加入到$lists 數(shù)組中
if ($left[0] < $right[0]) {
$lists[] = $left[0];
// PHP 中unset 掉數(shù)組中的元素后,其他元素的鍵名不變
unset($left[0]);
// 重建數(shù)組索引,始終讓比較的值在第一位
$left = array_values($left);
$lLength--;
} else {
$lists[] = $right[0];
unset($right[0]);
$right = array_values($right);
$rLength--;
}
}
// 合并$lists 和$left、$right 中剩余的元素
return array_merge($lists, $left, $right);
}