堆-最大二叉堆

138 阅读3分钟

什么是最大堆

对于任意一个节点的值,它的子节点的值都不大于它的值。也就是说,最大堆的根节点是整个堆中最大的元素。在最大堆中,每个节点的值都大于等于其子节点的值。

应用场景

优先队列、堆排序等。

最大二叉堆

插入

逻辑

  1. 在堆的最后一个位置添加新元素,并将其上浮到正确位置。
  2. 将新元素与其父节点进行比较,如果新元素大于其父节点,则交换它们的位置,并继续向上比较,直到新元素不再大于其父节点或者到达根节点位置。
graph TD
将新元素添加到堆的最后一个位置 --> 新元素的下标
新元素的下标 --> 如果新元素大于它的父节点
如果新元素大于它的父节点 --> 交换新元素和它的父节点
交换新元素和它的父节点 --> 将新元素的下标更新为其父节点的下标

示意图

visualgo.net/zh/heap 这里可以进行堆的创建、插入、删除等操作

代码块

/**
 * 插入
 * @param nums 原数组
 * @param val 新插入的节点值
 */
public static void insert(int[] nums, int val) {
    //数组结尾的索引
    int n = nums.length - 1;
    nums[n] = val;
    //当前索引为数组结尾
    int curIndex = n;
    //当前索引的父节点索引
    int parentIndex = (n - 1) /2;
    //当前索引>0且当前索引节点的值大于父节点索引的值
    while (curIndex > 0 && nums[curIndex] > nums[parentIndex]) {
        //交换当前索引节点和父节点
        swap(nums, curIndex, parentIndex);
        //更新当前索引为父节点索引
        curIndex = parentIndex;
        //更新当前索引的父节点
        parentIndex = (curIndex - 1)/2;
    }
}

对于索引为index的结点,其父结点的索引为(index - 1)/2。

删除

逻辑

  1. 删除根节点,并将堆的最后一个元素移动到根节点的位置。
  2. 将新的根节点与其子节点进行比较,如果新的根节点小于其子节点,则与其子节点中的最大值进行交换,并继续向下比较,直到新的根节点不再小于其子节点或者到达叶节点位置。
graph TD
堆大小 --> 堆为空  --> return 
堆大小 --> 堆内元素大于等于1
堆内元素大于等于1 --> 保存堆最大值即堆顶元素 --> 将堆的最后一个元素移动到根节点的位置
将堆的最后一个元素移动到根节点的位置 --> 默认左节点的值比较大为larger
默认左节点的值比较大为larger --> 如果右节点大于larger --> 将右结点更新为larger
默认左节点的值比较大为larger --> 如果larger大于当前节点 --> 将左节点与当前节点交换 --> 更新下标

代码块

public static void delete(int[] nums) {
    if (nums.length == 0) {
        return;
    }

    int n = nums.length - 1;
    // 用堆中最后一个元素来替换根节点
    nums[0] = nums[n];
    nums[n] = 0;
    // 初始化当前索引及左右子节点索引
    int curIndex = 0;
    int leftIndex = 1;
    int rightIndex = 2;
    // 循环直到左子节点索引小于堆的大小
    while (curIndex < n - 1) {
        // 找到左右子节点中较大的一个
        int largerIndex = leftIndex;
        if (rightIndex < n - 1 && nums[rightIndex] > nums[leftIndex]) {
            largerIndex = rightIndex;
        }

        // 如果当前元素小于较大子节点,则交换这两个元素,并更新索引
        if (nums[curIndex] < nums[largerIndex]) {
            swap(nums, curIndex, largerIndex);
            curIndex = largerIndex;
            leftIndex = 2 * curIndex + 1;
            rightIndex = 2 * curIndex + 2;
        } else {
            // 否则跳出循环
            break;
        }
    }
}

交换函数

private static void swap(int[] nums, int index1, int index2) {
    int temp = nums[index1];
    nums[index1] = nums[index2];
    nums[index2] = temp;
}

总结

最大堆的插入,主打一个上浮,先将新元素插入到数组的末尾,然后将当前元素与其父节点比较。上浮到合适的位置即可。 最大堆的删除,主打一个下沉,每次删除都是先删除堆顶的元素,然后将最后一个元素放到堆顶的位置,然后通过下沉到合适的位置即可。

全部代码

package algo;

import java.util.Arrays;

public class MaxHeap {
    public static void main(String[] args) {
        int[] nums = new int[7];
        nums[0] = 13;
        nums[1] = 10;
        nums[2] = 12;
        nums[3] = 8;
        nums[4] = 6;
        nums[5] = 11;
        System.out.println(Arrays.toString(nums));
        insert(nums,  14);
        System.out.println(Arrays.toString(nums));

        delete(nums);
        System.out.println(Arrays.toString(nums));

    }

    /**
     * 插入
     * @param nums 原数组
     * @param val 新插入的节点值
     */
    public static void insert(int[] nums, int val) {
        int n = nums.length - 1;
        nums[n] = val;
        //当前索引为数组结尾
        int curIndex = n;
        //当前索引的父节点索引
        int parentIndex = (n - 1) /2;
        //当前索引>0且当前索引节点的值大于父节点索引的值
        while (curIndex > 0 && nums[curIndex] > nums[parentIndex]) {
            //交换当前索引节点和父节点
            swap(nums, curIndex, parentIndex);
            //更新当前索引为父节点索引
            curIndex = parentIndex;
            //更新当前索引的父节点
            parentIndex = (curIndex - 1)/2;
        }
    }

    public static void delete(int[] nums) {
        if (nums.length == 0) {
            return;
        }

        int n = nums.length - 1;
        // 用堆中最后一个元素来替换根节点
        nums[0] = nums[n];
        nums[n] = 0;
        // 初始化当前索引及左右子节点索引
        int curIndex = 0;
        int leftIndex = 1;
        int rightIndex = 2;
        // 循环直到左子节点索引小于堆的大小
        while (curIndex < n - 1) {
            // 找到左右子节点中较大的一个
            int largerIndex = leftIndex;
            if (rightIndex < n - 1 && nums[rightIndex] > nums[leftIndex]) {
                largerIndex = rightIndex;
            }

            // 如果当前元素小于较大子节点,则交换这两个元素,并更新索引
            if (nums[curIndex] < nums[largerIndex]) {
                swap(nums, curIndex, largerIndex);
                curIndex = largerIndex;
                leftIndex = 2 * curIndex + 1;
                rightIndex = 2 * curIndex + 2;
            } else {
                // 否则跳出循环
                break;
            }
        }
    }

    private static void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}