氣泡排序、插⼊排序、選擇排序這三種排序演算法,它們的時間複雜度都是O(n2),⽐較⾼,適合⼩規模資料的排序。今天,我講兩種時間複雜度為O(nlogn)的排序演算法,歸併排序和快速排序。這兩種排序演算法適合⼤規模的資料排序,⽐氣泡排序、插⼊排序、選擇排序這三種排序演算法要更常⽤。
歸併排序
歸併排序的原理
歸併排序的核⼼思想還是蠻簡單的。如果要排序⼀個陣列,我們先把陣列從中間分成前後兩部分,然後對前後兩部分分別排序,再將排好序的兩部分合並在⼀起,這樣整個陣列就都有序了。
歸併排序使⽤的就是分治思想。分治,顧名思義,就是分⽽治之,將⼀個⼤問題分解成⼩的⼦問題來解決。⼩的⼦問題解決了,⼤問題也就解決了。
分治演算法⼀般都是⽤遞迴來實現的。分治是⼀種解決問題的處理思想,遞迴是⼀種程式設計技巧,這兩者並不衝突。
歸併排序演算法實現
class mergeSort{
//需要排序的陣列
public $arr = [6,3,-1,9,39,43,23,11];
function __construction()
{
$this->mergeSort(0,count($arr)-1);
}
publuc function mergeSort($min,$max){
if($min<$max){
$middle = ($min+$max)/2;
$this->mergeSort($this->arr,0,$middle-1);
$this->mergeSort($this->arr,$middle+1,$max);
$this->merge($min,$middle,$max);
}
}
//$left為左陣列起始下標,$right為右陣列結束下標
public function merge($left,$middle,$right){
$tempArr = $this->arr;
$rightStart = $middle+1;
$temp = $left;
while($left<=$middle&&$rightStart<=$right){
if($arr[$left]<$arr[$rightStart]){
$tempArr[$temp++]=$this->arr[$left++];
}else{
$tempArr[$temp++]=$this->arr[$rightStart++];
}
}
while($rightStart<=$right){
$tempArr[$temp++]=$this->arr[$rightStart++];
}
$this->arr = $tempArr;
}
}
$sort = new mergeSort();
$arr = $sort->arr;
歸併排序的效能分析
歸併排序穩不穩定關鍵要看merge()函式,也就是兩個有序⼦陣列合併成⼀個有序陣列的那部分程式碼。
在合併的過程中,如果A[p…q]和A[q+1…r]之間有值相同的元素,那我們可以像虛擬碼中那樣,先把A[p…q]中的元素放⼊tmp 陣列。這樣就保證了值相同的元素,在合併前後的先後順序不變。所以,歸併排序是⼀個穩定的排序演算法。
其時間複雜度是⾮常穩定的,不管是最好情況、最壞情況,還是平均情況,時間複雜度都是O(nlogn)。
歸併排序的合併函式,在合併兩個有序陣列為⼀個有序陣列時,需要藉助額外的儲存空間。空間複雜度為O(n)。
快速排序
快速排序的原理
快排利⽤的也是分治思想。乍看起來,它有點像歸併排序,但是思路其實完全不⼀樣。
快排的思想是這樣的:如果要排序陣列中下標從p到r之間的⼀組資料,我們選擇p到r之間的任意⼀個資料作為pivot(分割槽點)。
我們遍歷p到r之間的資料,將⼩於pivot的放到左邊,將⼤於pivot的放到右邊,將pivot放到中間。經過這⼀步驟之後,陣列p到r之間的資料就被分成了三個部分,前⾯p到q-1之間都是⼩於pivot的,中間是pivot,後⾯的q+1到r之間是⼤於pivot的。
根據分治、遞迴的處理思想,我們可以⽤遞迴排序下標從p到q-1之間的資料和下標從q+1到r之間的資料,直到區間縮⼩為1,就說明所有的資料都有序了。
歸併排序中有⼀個merge()合併函式,我們這⾥有⼀個partition()分割槽函式。partition()分割槽函式實際上我們前⾯已經講過了,就是隨機選擇⼀個元素作為pivot(⼀般情況下,可以選擇p到r區間的最後⼀個元素),然後對A[p…r]分割槽,函式返回pivot的下標。
如果我們不考慮空間消耗的話,partition()分割槽函式可以寫得⾮常簡單。我們申請兩個臨時陣列X和Y,遍歷A[p…r],將⼩於pivot的元素都拷⻉到臨時陣列X,將⼤於pivot的元素都拷⻉到臨時陣列Y,最後再將陣列X和陣列Y中資料順序拷⻉到A[p…r]。
快速排序演算法實現
class quickSort{
public $arr = [4,3,8,65,43,-1,21,55];
__construction(){
$this->quickSort($this->arr,0,count($arr)-1);
}
function quickSort($arr,$begin,$end){
if($begin<$end){
//以第一個數為基準
$first = $arr[$begin];
$i = $begin;
$j = $end;
while($i<$j){
while($i<$j&&$arr[$j]<$first){
$j--;
$arr[$i]=$arr[$j];
}
while($i<$j&&$arr[$i]>=$first){
$i++;
$arr[$j]=$arr[$i];
}
$arr[$i] = $first;
}
$this->quickSort($arr,$begin,$i-1);
$this->quickSort($arr,$i+1,$end);
}else{
return $arr;
}
}
}
$quickSort = new quickSort();
$arr = $quickSort->arr;
快速排序的效能分析
快排是⼀種原地、不穩定的排序演算法。快排也是⽤遞迴來實現的。對於遞迴程式碼的時間複雜度。如果每次分割槽操作,都能正好把陣列分成⼤⼩接近相等的兩個⼩區間,那快排的時間複雜度遞推求解公式跟歸併是相同的。所以,快排的時間複雜度也是 O(nlogn)。
歸併排序與快速排序比較
本作品採用《CC 協議》,轉載必須註明作者和本文連結