PHP 實現二叉堆的操作類 以及 堆排序
阿新 • • 發佈:2018-12-04
tips: 此處預設最小堆
應用:優先佇列
<?php /** * 堆的程式碼實現 * 二叉堆本質上是一種完全二叉樹 * 二叉堆雖然是一顆完全二叉樹,但它的儲存方式並不是鏈式儲存,而是順序儲存。換句話說,二叉堆的所有節點都儲存在陣列當中。 */ class HeapOperator { /** * 上浮調整 * @param array 待調整的堆 * @param up_key 堆中上調的下標(預設最後一位) */ public function upAdjust($array,$up_key = -1) { $count = count($array); if ($up_key == -1) { $childIndex = $count-1; # 插入節點值 } else { $childIndex = $up_key; } $parentIndex = ($childIndex-1)/2; #當前父節點值 // temp儲存插入的葉子節點值,用於最後的賦值 $temp = $array[$childIndex]; while ($childIndex > 0 && $temp < $array[$parentIndex]) { //無需真正交換,單向賦值即可 $array[$childIndex] = $array[$parentIndex]; $childIndex = $parentIndex; $parentIndex = ($parentIndex-1) / 2; } $array[$childIndex] = $temp; return $array; } /** * 下沉調整() * @param array 待調整的堆 * 把最後一位補到刪除節點位置,然後比較 * @param parentIndex 要下沉的父節點 * @param parentIndex 堆的有效大小 */ public function downAdjust($array, $parentIndex, $length) { // temp儲存父節點值,用於最後的賦值 $temp = $array[$parentIndex]; $childIndex = 2 * $parentIndex +1; while($childIndex < $length) { // 如果有右孩子,且右孩子小於左孩子的值,則定位到右孩子 if($childIndex + 1 < $length && $array[$childIndex + 1] < $array[$childIndex]) { $childIndex++; # 當前孩子節點為右節點 } # 否則為左節點 // 如果父節點小於任何一個孩子的值,直接跳出 if ($temp <= $array[$childIndex]) { break; } //無需真正交換,單向賦值即可 (父節點與子節點交換) $array[$parentIndex] = $array[$childIndex]; # 父=子 換值 $parentIndex = $childIndex; $childIndex = 2* $childIndex + 1; # 換下標 } # 此時的 parentIndex 是最終參與交換的子節點 $array[$parentIndex] = $temp; return $array; } /** * 構建二叉堆 * 構建二叉堆,也就是把一個無序的完全二叉樹調整為二叉堆,本質上就是讓所有非葉子節點依次下沉。 * @param arr 待調整的堆 */ public function buildHeap($arr) { // 從最後一個非葉子節點開始,依次下沉調整 $count = count($arr); for($i = floor($count / 2);$i >= 0; $i--){ $arr = self::downAdjust($arr,$i, $count); } return $arr; } /** * 插入節點 * 思路:先插入最後位置節點,然後上浮 * @param arr 待插入的堆(已有序的完全二叉堆) * @param value 插入的節點值 */ public function insertNode($arr,$value) { $count = count($arr); # $value 的下標為$count $arr[$count] = $value; $array = self::upAdjust($arr); return $array; } /** * 刪除節點 * 思路:刪除該節點,最後一個節點補位,然後依次下沉 * @param arr 待調整的堆(已有序的完全二叉堆) * @param del_key 刪除的節點下標值 */ public function deleteNode($arr,$del_key) { # 刪除節點 $count = count($arr); if ($del_key || $del_key == 0) { unset($arr[$del_key]); } if ($del_key > floor($count/2) ) { return $arr; } # 補位 $arr[$del_key] = $arr[$count-1]; unset($arr[$count-1]); # 按鍵值排序 ksort($arr); $length = count($arr); # 下沉 $array = self::downAdjust($arr, $del_key, $length); return $array; } /** * 堆排序 * 思路:1. 把無序陣列構建成二叉堆。 * 2. 迴圈刪除堆頂元素,移到集合尾部,調節堆產生新的堆頂。 * 空間複雜度:O(1) 為開闢新的新的空間 * 時間複雜度:O(nlogn)「 不穩定排序 」 * @param arr 待調整的堆(已有序的完全二叉堆,此處有序是指符合二叉堆特性,並非陣列有序,如果是無序陣列,可以先呼叫上方 buildHeap 構建二叉堆,這裡不做贅述) */ public function heapSort($arr) { $count = count($arr); for ($i = $count-1; $i > 0 ; $i--) { # 取出第一個元素與最後一個元素交換位置 $first = $arr[0]; $arr[0] = $arr[$i]; $arr[$i] = $first; # 之後最後一個元素(原第一個元素)不參與下沉操作 # 將現在的二叉堆,首元素進行下沉操作 $arr = self::downAdjust($arr, 0, $i); // print_r(implode(",", $arr)); // echo "\n"; } return $arr; } }
呼叫:
<?php require_once("erchadui.php"); $tes = new HeapOperator; echo "上浮節點"; $array_o = [1,3,2,6,5,7,8,9,10,0]; $res_o = $tes->upAdjust($array_o); // var_dump($res_o); print_r(implode(",", $res_o)); echo "\n" ; echo "下沉節點"; $array_r = [1,11,2,6,5,7,8,9,10,9]; $res_r = $tes->downAdjust($array_r, 1, count($array_r)); print_r(implode(",", $res_r)); echo "\n" ; echo "構建二叉樹 \n"; $array_t = [7,1,3,10,5,2,8,9,6]; $res_t = $tes->buildHeap($array_t); // var_dump($res_t); print_r(implode(",", $res_t)); echo "\n" ; echo "插入節點 \n"; $array_t = [1,5,2,6,7,3,8,9,10]; $res_t = $tes->insertNode($array_t,0); print_r(implode(",", $res_t)); echo "\n" ; echo "刪除節點"; $array_f = [0,1,2,6,5,3,8,9,10,7]; $res_f = $tes->deleteNode($array_f,0); print_r(implode(",", $res_f)); echo "\n" ; echo "堆排序"; $array_f = [0,1,2,6,5,3,8,9,10,7]; $res_f = $tes->heapSort($array_f,0); print_r(implode(",", $res_f)); # 此時是由大到小排序,如果想從小到大可以用最大堆 echo "\n" ;
思路參考:程式設計師小灰微信公眾號