Java数据结构之最小堆和最大堆的原理及实现详解

Java数据结构之最小堆和最大堆的原理及实现详解

什么是堆?

堆是一种特殊的树形数据结构,它满足以下两个条件:

  • 堆是一个完全二叉树,即除了最后一层,其他层都必须填满,最后一层从左到右填满
  • 堆中每个节点的值必须满足某种特定的条件,例如最小堆要求每个节点的值都小于等于其子节点的值。

堆一般分为两种类型:最小堆和最大堆。

最小堆:每个节点的值都小于等于其子节点的值。根节点是堆中最小的元素。

最大堆:每个节点的值都大于等于其子节点的值。根节点是堆中最大的元素。

堆结构用于解决从一堆元素中获取一组高(或低)优先级元素的场景,例如堆排序、优先队列、最短路径等。

最小堆和最大堆的实现

下面我们将介绍如何实现最小堆和最大堆。

最小堆的实现

最小堆中,每个节点的值都小于等于其子节点的值。本实例中我们将实现插入元素,删除最小元素和更新元素等基本操作。

首先,我们定义最小堆的属性和方法:

class MinHeap {
    private int[] heap;    // 堆数组
    private int size;      // 堆中元素个数
    private int maxSize;   // 堆可以容纳的最大元素个数

    // 构造方法
    public MinHeap(int maxSize) {
        this.maxSize = maxSize;
        heap = new int[maxSize];
        size = 0;
    }

    // 获取父节点的下标
    private int getParent(int pos) {
        return (pos - 1) / 2;
    }

    // 获取左子节点的下标
    private int getLeftChild(int pos) {
        return 2 * pos + 1;
    }

    // 获取右子节点的下标
    private int getRightChild(int pos) {
        return 2 * pos + 2;
    }
}

然后,我们实现插入操作:

// 插入元素
public void insert(int value) {
    if (size == maxSize) {
        System.out.println("Heap is full");
        return;
    }
    // 加入新元素
    heap[size++] = value;
    int current = size - 1;
    // 将新元素移至其合适位置
    while (current != 0 && heap[current] < heap[getParent(current)]) {
        swap(current, getParent(current));
        current = getParent(current);
    }
}

插入一个元素时,我们将其添加到下一个空闲位置,并将其移至合适位置,即比父节点元素小。

现在,我们实现删除操作:

// 删除最小元素
public int extractMin() {
    if (size <= 0) {
        System.out.println("Heap is empty");
        return Integer.MAX_VALUE;
    }
    int minElement = heap[0];
    // 将最后一个元素置为根节点
    heap[0] = heap[size - 1];
    size--;
    // 移至合适位置
    minHeapify(0);
    return minElement;
}

private void minHeapify(int pos) {
    int left = getLeftChild(pos);
    int right = getRightChild(pos);
    int smallest = pos;

    // 找到最小元素的下标
    if (left < size && heap[left] < heap[smallest]) {
        smallest = left;
    }

    if (right < size && heap[right] < heap[smallest]) {
        smallest = right;
    }

    // 如果最小元素的下标不是根节点的下标,则交换元素
    if (smallest != pos) {
        swap(pos, smallest);
        minHeapify(smallest);
    }
}

删除最小元素时,我们将堆中最后一个元素移至根节点的位置,然后自顶向下调整堆,保证最小堆的性质不被破坏。

最后,我们实现更新操作:

// 更新元素的值
public void decreaseKey(int pos, int newValue) {
    heap[pos] = newValue;
    while (pos != 0 && heap[pos] < heap[getParent(pos)]) {
        swap(pos, getParent(pos));
        pos = getParent(pos);
    }
}

更新操作将一个元素的值降低,我们将其移至合适位置,即比父节点元素小。

最大堆的实现

最大堆中,每个节点的值都大于等于其子节点的值。我们可以将最小堆的实现稍作改动,就可以得到最大堆。

首先,我们定义最大堆的属性和方法:

class MaxHeap {
    private int[] heap;    // 堆数组
    private int size;      // 堆中元素个数
    private int maxSize;   // 堆可以容纳的最大元素个数

    // 构造方法
    public MaxHeap(int maxSize) {
        this.maxSize = maxSize;
        heap = new int[maxSize];
        size = 0;
    }

    // 获取父节点的下标
    private int getParent(int pos) {
        return (pos - 1) / 2;
    }

    // 获取左子节点的下标
    private int getLeftChild(int pos) {
        return 2 * pos + 1;
    }

    // 获取右子节点的下标
    private int getRightChild(int pos) {
        return 2 * pos + 2;
    }
}

然后,我们实现插入操作:

// 插入元素
public void insert(int value) {
    if (size == maxSize) {
        System.out.println("Heap is full");
        return;
    }
    // 加入新元素
    heap[size++] = value;
    int current = size - 1;
    // 将新元素移至其合适位置
    while (current != 0 && heap[current] > heap[getParent(current)]) {
        swap(current, getParent(current));
        current = getParent(current);
    }
}

插入一个元素时,我们将其添加到下一个空闲位置,并将其移至合适位置,即比父节点元素大。

现在,我们实现删除操作:

// 删除最大元素
public int extractMax() {
    if (size <= 0) {
        System.out.println("Heap is empty");
        return Integer.MIN_VALUE;
    }
    int maxElement = heap[0];
    // 将最后一个元素置为根节点
    heap[0] = heap[size - 1];
    size--;
    // 移至合适位置
    maxHeapify(0);
    return maxElement;
}

private void maxHeapify(int pos) {
    int left = getLeftChild(pos);
    int right = getRightChild(pos);
    int largest = pos;

    // 找到最大元素的下标
    if (left < size && heap[left] > heap[largest]) {
        largest = left;
    }

    if (right < size && heap[right] > heap[largest]) {
        largest = right;
    }

    // 如果最大元素的下标不是根节点的下标,则交换元素
    if (largest != pos) {
        swap(pos, largest);
        maxHeapify(largest);
    }
}

删除最大元素时,我们将堆中最后一个元素移至根节点的位置,然后自顶向下调整堆,保证最大堆的性质不被破坏。

最后,我们实现更新操作:

// 更新元素的值
public void increaseKey(int pos, int newValue) {
    heap[pos] = newValue;
    while (pos != 0 && heap[pos] > heap[getParent(pos)]) {
        swap(pos, getParent(pos));
        pos = getParent(pos);
    }
}

更新操作将一个元素的值增加,我们将其移至合适位置,即比父节点元素大。

示例说明

示例1

下面我们将举一个例子以说明最小堆和最大堆的使用方法。

假设我们要从一堆数中找出前k个最大的数,我们可以使用最小堆实现。先将k个数插入最小堆中,然后对于每个数n,如果n比堆中最小的数要大,则将堆中最小的数删除,将n插入堆中。这样最后堆中的k个数就是要求的前k个最大数。

代码如下:

public static List<Integer> findKMax(int[] nums, int k) {
    MinHeap minHeap = new MinHeap(k);
    for (int i = 0; i < nums.length; i++) {
        if (i < k) {
            minHeap.insert(nums[i]);
        } else {
            if (nums[i] > minHeap.getMin()) {
                minHeap.extractMin();
                minHeap.insert(nums[i]);
            }
        }
    }
    return minHeap.getHeapList();
}

示例2

下面我们将再举一个例子以说明最大堆的使用方法。

假设我们要在一个大小为n的数组中查找第k大的元素,我们可以使用最大堆实现。将前k个元素插入最大堆中,然后对于从第k+1个元素到第n个元素,如果该元素比堆中最大的数要小,则将堆中最大的数删除,将该元素插入堆中。这样最终堆中的最大元素就是所求的Top k。

代码如下:

public static int findKthLargest(int[] nums, int k) {
    MaxHeap maxHeap = new MaxHeap(k);
    for (int i = 0; i < nums.length; i++) {
        if (i < k) {
            maxHeap.insert(nums[i]);
        } else {
            if (nums[i] < maxHeap.getMax()) {
                maxHeap.extractMax();
                maxHeap.insert(nums[i]);
            }
        }
    }
    return maxHeap.getMax();
}

总结

本文介绍了最小堆和最大堆的原理和实现。最小堆和最大堆是常用的数据结构之一,可用于解决各种算法问题。开发人员可以根据自己的需求,选择合适的堆结构进行实现。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java数据结构之最小堆和最大堆的原理及实现详解 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • C语言数据结构之算法的时间复杂度

    关于C语言数据结构之算法的时间复杂度,需要先了解一些基本概念。 什么是时间复杂度 时间复杂度是算法的一种衡量标准,用于评估算法的执行效率。表示代码执行的时间和数据量之间的关系,通常用大O符号来表示,称为“大O记法”。 时间复杂度的分类 时间复杂度可分为以下几类: 常数阶:O(1) 对数阶:O(log n) 线性阶:O(n) 线性对数阶:O(n log n) …

    数据结构 2023年5月17日
    00
  • Python嵌套式数据结构实例浅析

    Python嵌套式数据结构实例浅析 介绍 在Python中,数据结构是非常重要的。Python中的嵌套数据结构给我们提供了非常灵活的使用方式。例如,我们可以使用嵌套式列表和字典来处理复杂的数据结构问题。在本文中,我将向您介绍Python中嵌套式数据结构的使用方法和示例代码。 嵌套式列表 首先,让我们来看看使用Python中的嵌套式列表。嵌套式列表是列表嵌套的…

    数据结构 2023年5月17日
    00
  • C语言数据结构时间复杂度及空间复杂度简要分析

    C语言数据结构时间复杂度及空间复杂度简要分析 什么是时间复杂度和空间复杂度? 在分析算法和数据结构的性能时,时间复杂度和空间复杂度是必须考虑的因素。 时间复杂度:衡量算法执行时间所需的资源,也就是算法的速度。通常使用“大O符号”来表示时间复杂度,例如O(1)、O(n)、O(nlogn)等。 空间复杂度:衡量算法使用的内存资源,也就是算法的空间利用率。通常使用…

    数据结构 2023年5月17日
    00
  • Java数据结构之常见排序算法(上)

    Java数据结构之常见排序算法(上) 本篇文章将介绍常见的排序算法,包括冒泡排序、选择排序、插入排序、快速排序和归并排序。这些排序算法既是学习算法和数据结构的入门知识,也是在实际工作中常用的基础算法。 冒泡排序 冒泡排序是一种简单的排序算法,它的基本思想是从前往后依次比较相邻的两个元素,如果前面的元素比后面的元素大,则交换它们的位置,重复这个过程,每一轮比较…

    数据结构 2023年5月17日
    00
  • C++抽象数据类型介绍

    C++抽象数据类型介绍 什么是抽象数据类型? 抽象数据类型(Abstract Data Type,ADT),是数据类型的一个数学模型。它实现了数据类型的抽象过程,将数据与操作分离,使得操作具有独立性,而数据只作为函数参数和返回值存在。 举个例子,ADT可以定义一个栈(Stack),栈的实现需要以下操作: 初始化栈 压入数据 弹出数据 获取栈顶数据 检查栈是否…

    数据结构 2023年5月17日
    00
  • 手撕HashMap(二)

    这里再补充几个手撕HashMap的方法 1、remove() remove 方法参数值应该是键值对的键的值,当传入键值对的键的时候,remove 方法会删除对应的键值对 需要利用我们自己先前创建的 hashcodeList 来实现,hashcodeList 存入了所有被使用的 hashcode 值,方便后续的操作 在 put() 中,当添加新的键值对时,就会…

    算法与数据结构 2023年4月18日
    00
  • Java数据结构BFS广搜法解决迷宫问题

    Java数据结构BFS广搜法解决迷宫问题 什么是BFS广搜法? 广度优先搜索(BFS)是一种遍历或搜索数据结构(例如树或图)的算法经典方法之一,也是解决迷宫问题的有效解法之一。BFS方法是从图的某个节点出发,以广度优先的方式依次访问与该节点相通的各节点,直到访问所有节点。BFS算法主要借助队列的数据结构来实现。 解决迷宫问题的具体实现 数据准备: 在解决迷宫…

    数据结构 2023年5月17日
    00
  • C语言实现通用数据结构之通用链表

    C语言是一门广泛应用于低级别系统编程的语言,也是数据结构和算法学习的重要工具之一,而在C语言中实现通用数据结构的方法之一就是通用链表。 通用链表是一种使用节点来组织数据的通用数据结构,每个节点包含一定量的数据以及指向链表中下一个节点的指针,因此,它可以用来实现许多不同的数据结构,例如栈、队列、树、图、哈希表等等。 具体实现通用链表的方法如下: 步骤一:定义节…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部