堆-最小堆

346 阅读2分钟

什么是最小堆

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

应用场景

最小堆的时间复杂度为 O(log n),其中 n 是堆中元素的个数。最小堆的应用场景非常广泛,比如优先队列、堆排序、最小生成树算法和最短路径算法等。

操作

插入

  1. 将新元素插入堆的最后位置;
  2. 如果新元素父节点的值大于新元素的值,则将它们交换,直到满足最小堆的性质。
graph TD
将新元素添加到堆的最后一个位置 --> 新元素的下标
新元素的下标 --> 如果新元素小于它的父节点
如果新元素小于它的父节点 --> 交换新元素和它的父节点
交换新元素和它的父节点 --> 将新元素的下标更新为其父节点的下标

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

/**
 * 插入
 * @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 = (curIndex - 1) / 2;
    //当前节点小于父节点
    while (curIndex > 0 && nums[curIndex] < nums[parentIndex]) {
        //交换当前节点和父节点
        swap(nums, curIndex, parentIndex);
        //更新当前索引为父节点索引
        curIndex = parentIndex;
        //更新父节点索引
        parentIndex = (curIndex - 1) / 2;
    }
}

删除

  1. 将根节点删除,并将堆的最后一个元素移动到根节点的位置;
  2. 如果新的根节点的值大于它的子节点的值,则将它与其中最小的子节点交换,直到满足最小堆的性质。
graph TD
堆大小 --> 堆内元素大于等于1
堆内元素大于等于1 --> 保存堆顶元素 --> 将堆的最后一个元素移动到根节点的位置
将堆的最后一个元素移动到根节点的位置 --> 默认左节点的值比较小为small
默认左节点的值比较小为small --> 如果右节点小于small --> 将右结点更新为small
默认左节点的值比较小为small --> 如果small大于当前节点 --> 将左节点与当前节点交换 --> 更新下标
/**
 * 删除堆顶元素
 * @param nums
 */
public static void delete(int[] nums) {
    int n = nums.length - 1;
    //将堆顶元素设置为数组末尾元素
    nums[0] = nums[n];
    int curIndex = 0;
    int leftIndex = 1;
    int rightIndex = 2;
    while (curIndex < n) {
        //smallIndex为当前节点的左右节点中较小的一个
        int smallIndex = leftIndex;
        if (rightIndex < n && nums[smallIndex] > nums[rightIndex]) {
            smallIndex = rightIndex;
        }
        //如果当前节点大于较小的节点
        if (nums[curIndex] > nums[smallIndex]) {
            //交换当前节点和较小节点
            swap(nums, curIndex, smallIndex);
            //更新当前节点和左右节点的索引
            curIndex = smallIndex;
            leftIndex = curIndex * 2 + 1;
            rightIndex = curIndex * 2 + 2;
        } else {
            break;
        }
    }
    nums[n] = 0;
}

交换函数

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 MinHeap {
    public static void main(String[] args) {
        int[] nums = new int[] {2, 3, 4, 5, 6, 0};
        System.out.println(Arrays.toString(nums));
        insert(nums, 1);
        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 = (curIndex - 1) / 2;
        //当前节点小于父节点
        while (curIndex > 0 && nums[curIndex] < nums[parentIndex]) {
            //交换当前节点和父节点
            swap(nums, curIndex, parentIndex);
            //更新当前索引为父节点索引
            curIndex = parentIndex;
            //更新父节点索引
            parentIndex = (curIndex - 1) / 2;
        }
    }

    /**
     * 删除堆顶元素
     * @param nums
     */
    public static void delete(int[] nums) {
        int n = nums.length - 1;
        //将堆顶元素设置为数组末尾元素
        nums[0] = nums[n];
        int curIndex = 0;
        int leftIndex = 1;
        int rightIndex = 2;
        while (curIndex < n) {
            //smallIndex为当前节点的左右节点中较小的一个
            int smallIndex = leftIndex;
            if (rightIndex < n && nums[smallIndex] > nums[rightIndex]) {
                smallIndex = rightIndex;
            }
            //如果当前节点大于较小的节点
            if (nums[curIndex] > nums[smallIndex]) {
                //交换当前节点和较小节点
                swap(nums, curIndex, smallIndex);
                //更新当前节点和左右节点的索引
                curIndex = smallIndex;
                leftIndex = curIndex * 2 + 1;
                rightIndex = curIndex * 2 + 2;
            } else {
                break;
            }
        }
        nums[n] = 0;
    }



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