1. 程式人生 > >PHP 實現二叉堆的操作類 以及 堆排序

PHP 實現二叉堆的操作類 以及 堆排序

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" ;

思路參考:程式設計師小灰微信公眾號