1. 程式人生 > 其它 >PHP資料結構(二十四) ——堆排序

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

相關閱讀:

PHP資料結構(二十三) ——快速排序

PHP資料結構(二十二) ——快速排序

PHP資料結構(二十一) ——希爾排序

PHP資料結構(二十) ——其他插入排序

PHP資料結構(十九) ——B+樹

PHP資料結構(十八) ——直接插入排序

PHP資料結構(十七) ——內部排序綜述

PHP資料結構(十六) ——B樹

PHP資料結構(十五) ——雜湊表​

PHP資料結構(十四) ——鍵樹(雙鏈樹)

PHP資料結構(十三) ——動態查詢表(二叉排序樹)

PHP資料結構(十二) ——靜態查詢表​

PHP資料結構(十一) ——圖的連通性問題與最小生成樹演算法(2)

PHP資料結構(十一) ——圖的連通性問題與最小生成樹演算法(1)

PHP資料結構(十) ——有向無環圖與拓撲演算法

PHP資料結構(九) ——圖的定義、儲存與兩種方式遍歷

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(實踐2)

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(實踐1)

PHP資料結構(八) ——赫夫曼樹實現字串編解碼(理論)

PHP資料結構(七) ——串與實現KMP演算法

PHP資料結構(六) ——樹與二叉樹之概念及儲存結構

PHP資料結構(六) ——陣列的相乘、廣義表

PHP資料結構(五) ——陣列的壓縮與轉置

PHP資料結構(四) ——佇列

PHP資料結構(三)——運用棧實現括號匹配

PHP資料結構(二)——鏈式結構線性表

PHP資料結構(一)——順序結構線性表