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" ;
思路参考:程序员小灰微信公众号