堆排序有點小復雜,分成三塊:
第一塊,什么是堆,什么是最大堆
第二塊,怎么將堆調整為最大堆,這部分是重點
第三塊,堆排序介紹
第一塊,什么是堆,什么是最大堆
什么是堆
這里的堆(二叉堆),指得不是堆棧的那個堆,而是一種數據結構。
堆可以視為一棵完全的二叉樹,完全二叉樹的一個“優秀”的性質是,除了最底層之外,每一層都是滿的,這使得堆可以利用數組來表示,每一個結點對應數組中的一個元素.
數組與堆之間的關系
二叉堆一般分為兩種:最大堆和最小堆。
什么是最大堆
堆中每個父節點的元素值都大於等於其孩子結點(如果存在),這樣的堆就是一個最大堆
因此,最大堆中的最大元素值出現在根結點(堆頂)
節點與數組索引關系
對於給定的某個結點的下標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); }