06-堆排序
less than a minute
0 堆的定义
堆通常是一个可以被看做一棵树的数组对象,其任一非叶节点满足以下性质:
1、堆中某个节点的值总是不大于或不小于其父节点的值:
每个节点的值都大于或等于其左右子节点的值,称为大顶堆。即:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]。
或:
每个节点的值都小于或等于其左右子节点的值,称为小顶堆。即:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]。
2、堆总是一棵完全二叉树。
注:上述公式,根节点从0开始。如果根节点从1开始,则左右子节点分别是2i和2i+1。
1 基本思想
以大顶堆为例,将待排序的序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将它移走(也就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小的值。如此反复执行,便能得到一个有序序列了。
2 算法过程
1、将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆; 2、将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端; 3、重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
3 算法图解
4 PHP代码实现
/**
* 堆排序: 将元素看成完全二叉树, 先从(n/2-1)号元素到0号元素调整为大顶堆, 然后交换首尾值, 继续从0号元素调整
* 时间复杂度: O(nlogn)
* 空间复杂度: O(1)
* 不稳定排序
*/
function heapSort(&$arr) {
$length = count($arr);
// 调整为大顶堆
for($i = floor($length/2 -1); $i >= 0; --$i) {
adjustHeap($arr, $i, $length);
}
// 循环交换首尾元素并调整为大顶堆, 直到所有元素有序
for($j = $length - 1; $j >= 0; --$j) {
list($arr[0], $arr[$j]) = [$arr[$j], $arr[0]];
adjustHeap($arr, 0, $j);
}
}
function adjustHeap(&$arr, $i, $length) {
$temp = $arr[$i];
// 从当前元素开始遍历左右子节点, 默认从左子节点开始遍历
for($j = 2 * $i + 1; $j < $length; $j = 2 * $j + 1) {
// 如果右子节点比左子节点大,则遍历右子树
if ($j + 1 < $length && $arr[$j] < $arr[$j + 1]) {
$j++;
}
// 如果当前遍历元素比当前所在子树的根大, 则将其父节点设为当前遍历元素的值
if ($arr[$j] > $temp) {
$arr[$i] = $arr[$j];
$i = $j;
}
}
$arr[$i] = $temp;
}
$arr = [38, 19, 29, 91, 85, 12, 41, 21];
heapSort($arr);
print_r($arr);
5 效率分析
1、时间复杂度:O(nlogn) 最坏,最好,平均时间复杂度均为O(nlogn)。 2、空间复杂度:堆排序仅需一个记录大小的供交换用的辅助存储空间,因此空间复杂度为O(1),是不稳定排序。
Last modified March 8, 2022: Fix image path (67fc7f5)