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" ;
思路參考:程序員小灰微信公衆號