PHP資料結構(二十四) ——堆排序
PHP資料結構(二十四)——堆排序
(原創內容,轉載請註明來源,謝謝)
一、定義
堆排序也屬於一種選擇排序,效率較高且空間佔用相對較少。
堆的定義:n個元素的序列(k1,k2…kn),當且僅當滿足以下1或者2的其中一種關係時,稱為堆。
1)大頂堆:ki<=k2i且,ki<=k2i+1,其中i=1,2…n/2
2)小頂堆:ki>=k2i且,ki>=k2i+1,其中i=1,2…n/2
可將堆對應的一維陣列看成一個完全二叉樹,且滿足非終端節點對應的值不大於(或不小於)其左右子節點的值。堆頂元素(即完全二叉樹的根)必定是這個序列的最小值。(有些地方將滿足此條件的完全二叉樹稱為二叉堆)
堆排序定義:輸出堆頂元素後,用剩餘的n-1個元素重組成一個堆,得到次小值。如此反覆直至獲取整個陣列。
堆排序相比於樹形排序,節約很多空間,只需要一個記錄大小的輔助空間,每個待排序的記錄僅佔用一個空間。
二、堆的操作:
1、插入
堆的插入總是在最後一個位置,因此,插入之前的堆總是滿足二叉堆的要求。
由於是用一維陣列表示,即插入在一維陣列的最後一個位置。然後根據節點下標,奇數是左節點,偶數是右節點,通過公式【父節點編號=(左節點編號-1)/2,或父節點編號=右節點編號/2-1】,找到其父節點的下標。
並且比較其父節點的值,如果不符合排列順序,則互動。父節點繼續往上,直至比到根節點。
2、刪除
堆的刪除總是刪除第一個節點,即陣列的第一個元素。再將陣列最後一個元素放到第一個元素。接著再根據下標找到左右子節點,並且進行位置的調整。
三、堆的圖與儲存如下圖所示(圖片來自網路)
四、演算法
1)將獲取到的一組陣列,逐個節點插入到空的一維陣列(二叉堆)中,如果有必要則進行位置的調整。插入完成後,獲得一個二叉堆,並且第一個元素即為最小值。
2)把第一個元素賦值給新的陣列(結果陣列,採用push方式賦值)後,刪除第一個元素(根據定義同時將最後一個元素調整到第一個元素,其實也可以理解為把最後一個元素的值賦給第一個元素,再刪除最後一個元素),再將新的根節點逐級往下進行位置的調整,獲取到一個新的二叉堆。
3)重複步驟2,直至二叉堆為空。則結果陣列即為排序好的陣列。
五、程式碼主要流程:
1)根據輸入的陣列,採用逐個插入的方式,生成二叉堆(一維陣列)。
2)將二叉堆的第一個元素取走,再將最後一個元素的值賦給第一個元素,再刪除最後一個元素。
3)更新二叉堆,從根節點開始和左右子節點比較,如果有小的值則互換,互換後繼續與之後的左右位元組的進行比較。如果到某一層不需要互換了,則可以退出迴圈,不用繼續往後檢視互換問題。另外要注意不要超出陣列下標。
4)重複2、3步驟,直到二叉堆為空,則已經獲取整個陣列。
六、原始碼如下
//堆排序
publicfunction heapSelectSort(array $arr = array()){
$arr= $this->_checkNeedSort($arr);
if(!$arr){
return$arr;
}
//長度只有1直接返回
if(1== count($arr)){
return$arr;
}
//構造二叉堆
$heapArr= $this->_getHeapArray($arr);
//定義一個結果集
$arrRes= array();
//迴圈刪除二叉堆
while(0< count($heapArr)){
//堆頂入結果集
array_push($arrRes,$heapArr[0]);
//堆最後一個元素賦值給第一個元素,並刪除最後一個元素
//注:$heapArr[0]= array_pop($heapArr);將返回異常結果
$heapArr[0]= $heapArr[count($heapArr)-1];
array_pop($heapArr);
//unset($heapArr[count($heapArr)-1]);//array_pop也可以用這個代替
//重新調整堆頂
$heapArr= $this->_adjustHeapArray($heapArr);
}
return$arrRes;
}
//根據一維陣列構造二叉堆
privatefunction _getHeapArray(array $arr = array()){
if(empty($arr)){
return$arr;
}
$heapArr= array();
$arrLength= count($arr);
for($i=0;$i<$arrLength;$i++){
$heapArr[$i]= $arr[$i];
//當前下標
$curIndex= $i;
//判斷是否有必要繼續迴圈(當插入的節點不需要互換,則後面的父節點也不用繼續查詢)
$needLoop= true;
while(0< $curIndex && $needLoop){
$needLoop= false;//預設不需要迴圈
//如果是偶數,說明是右節點,父節點是i/2-1,即與父節點比較大小,不對則互換
if(0== $curIndex%2){
if($heapArr[$curIndex]< $heapArr[$curIndex/2-1]){
$tmp= $heapArr[$curIndex];
$heapArr[$curIndex]= $heapArr[$curIndex/2-1];
$heapArr[$curIndex/2-1]= $tmp;
$curIndex= $curIndex / 2 - 1;//尋找下個父節點
$needLoop= true;//置為需要迴圈
}
}else{
//如果是奇數,說明是左節點,父節點是(i-1)/2,即與父節點比較大小,不對則互換
if($heapArr[$curIndex]< $heapArr[($curIndex-1)/2]){
$tmp= $heapArr[$curIndex];
$heapArr[$curIndex]= $heapArr[($curIndex-1)/2];
$heapArr[($curIndex-1)/2]= $tmp;
$curIndex= ($curIndex - 1) / 2;
$needLoop= true;//置為需要迴圈
}
}
}
}
return$heapArr;
}
//更新二叉堆
privatefunction _adjustHeapArray(array $heapArr = array()){
if(empty($heapArr)){
return$heapArr;
}
$curIndex= 0;//當前下標是0,開始互換
$heapNum= count($heapArr);//二叉堆的最大值
while(1){
//左子節點下標
$leftIndex= 2 * $curIndex + 1;
//左節點最大下標heapNum-1,大於此值,說明該節點沒有子節點,退出迴圈
if($leftIndex>= $heapNum){
break;
}
//左子節點值
$leftVal= $heapArr[$leftIndex];
//右節點下標
$rightIndex= $leftIndex + 1;
//如果存在右節點值則取值,否則設定為inf
$rightVal= $rightIndex < $heapNum ? $heapArr[$rightIndex] : INF;
//比較父節點與兩個子節點的大小,視情況進行互換
//先比較兩個子節點,確定最小值
$childMin= $leftVal < $rightVal ? $leftVal : $rightVal;
//當前值
$curVal= $heapArr[$curIndex];
//如果父節點小於最小值,則沒必要後面的比較,直接退出迴圈
if($curVal<= $childMin){
break;
}
//沒有退出迴圈,說明需要進行節點互換,根據值找下標
$indexMin= $childMin == $leftVal ? $leftIndex : $rightIndex;
//互換
$heapArr[$curIndex]= $childMin;
$heapArr[$indexMin]= $curVal;
//將當前節點變為其子節點
$curIndex= $indexMin;
}
return$heapArr;
}
——written by linhxx 2017.07.20
相關閱讀: