06-堆排序

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)