堆排序有點小復雜,分成三塊:
第一塊,什么是堆,什么是最大堆
第二塊,怎么將堆調整為最大堆,這部分是重點
第三塊,堆排序介紹
第一塊,什么是堆,什么是最大堆
什么是堆
這里的堆(二叉堆),指得不是堆棧的那個堆,而是一種數據結構。
堆可以視為一棵完全的二叉樹,完全二叉樹的一個“優秀”的性質是,除了最底層之外,每一層都是滿的,這使得堆可以利用數組來表示,每一個結點對應數組中的一個元素.
數組與堆之間的關系

二叉堆一般分為兩種:最大堆和最小堆。
什么是最大堆
堆中每個父節點的元素值都大於等於其孩子結點(如果存在),這樣的堆就是一個最大堆
因此,最大堆中的最大元素值出現在根結點(堆頂)
節點與數組索引關系
對於給定的某個結點的下標i,可以很容易的計算出這個結點的父結點、孩子結點的下標,而且計算公式很漂亮很簡約

第二塊,怎么將堆調整為最大堆,這部分是重點
整個過程如下圖所示
在4,14,7這個小堆里邊,父節點4小於左孩子14,所以兩者交換
在4,2,8這個小堆里邊,父節點4小於右孩子8,所以兩者交換

上圖展示了一趟調整的過程,這個過程遞歸實現,直到調整為最大堆為止
第三塊,堆排序介紹
堆排序就是把堆頂的最大數取出,
將剩余的堆繼續調整為最大堆,具體過程在第二塊有介紹,以遞歸實現
剩余部分調整為最大堆后,再次將堆頂的最大數取出,再將剩余部分調整為最大堆,這個過程持續到剩余數只有一個時結束
下邊三張圖詳細描述了整個過程



具體PHP實現:
/**
* 使用異或交換2個值,原理:一個值經過同一個值的2次異或后,原值不變
* @param int $a
* @param int $b
*/
function swap(&$a,&$b){
$a = $a^$b;
$b = $a^$b;
$a = $a^$b;
}
/**
* 整理當前樹節點($n),臨界點$last之后為已排序好的元素
* @param int $n
* @param int $last
* @param array $arr
*
*/
function adjustNode($n,$last,&$arr){
$l = $n<<1; // 左孩子
if( !isset($arr[$l])||$l>$last ){
return ;
}
$r = $l+1; // 右孩子
// 如果右孩子比左孩子大,則讓父節點與右孩子比
if( $r<=$last&&$arr[$r]>$arr[$l] ){
$l = $r;
}
// 如果其中子節點$l比父節點$n大,則與父節點$n交換
if( $arr[$l]>$arr[$n] ){
swap($arr[$l],$arr[$n]);
// 交換之后,父節點($n)的值可能還小於原子節點($l)的子節點的值,所以還需對原子節點($l)的子節點進行調整,用遞歸實現
adjustNode($l, $last, $arr);
}
}
/**
* 堆排序(最大堆)
* @param array $arr
*/
function heapSort(&$arr){
// 最后一個蒜素位
$last = count($arr);
// 堆排序中常忽略$arr[0]
array_unshift($arr, 0);
// 最后一個非葉子節點
$i = $last>>1;
// 整理成最大堆,最大的數放到最頂,並將最大數和堆尾交換,並在之后的計算中,忽略數組最后端的最大數(last),直到堆頂(last=堆頂)
while(true){
adjustNode($i, $last, $arr);
if( $i>1 ){
// 移動節點指針,遍歷所有節點
$i--;
}
else{
// 臨界點$last=1,即所有排序完成
if( $last==1 ){
break;
}
swap($arr[$last],$arr[1]);
$last--;
}
}
// 彈出第一個元素
array_shift($arr);
}
