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语言举例讲解数据结构中的算法复杂度结与顺序表”的完整攻略。具体如下: 一、算法复杂度 1.1 什么是算法复杂度 算法复杂度是衡量算法运行效率的重要指标。包括时间复杂度和空间复杂度。时间复杂度指算法解决问题所用的时间,通常用大O符号表示;空间复杂度指算法解决问题所需的内存空间大小。 1.2 如何分析算法复杂度 可以从以下三个方面来分析算法复…

    数据结构 2023年5月17日
    00
  • 数据结构与算法之并查集(不相交集合)

    下面是详细的内容讲解。 数据结构与算法之并查集(不相交集合) 什么是并查集? 并查集,也叫不相交集合,是一种树形的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常是在使用 Kruskal 算法或者 Prim 算法来求解最小生成树(Minimum Spanning Tree)时用到的一种数据结构。 并查集的基本操作 Make…

    数据结构 2023年5月17日
    00
  • js处理层级数据结构的方法小结

    “JS处理层级数据结构的方法小结”是一篇讲解JavaScript如何处理嵌套数据结构的文章。在现代的web应用中,嵌套结构是非常常见的,比如JSON数据、树形数据等。以下是对该话题的详细讲解: 1. 嵌套数据结构的概念 指的是包含嵌套关系的数据类型,如数组、对象、树形结构、XML文档等。这些类型之间有着固定层级关系,包含多个层次的数据。嵌套数据结构的处理,往…

    数据结构 2023年5月17日
    00
  • 一步步带你学习设计MySQL索引数据结构

    一步步带你学习设计MySQL索引数据结构 索引原理 在MySQL中,索引是一种数据结构,用于快速查找表中的记录。在一张表中,可以使用不同的列来创建索引,索引可以大大提高查询效率,减少扫描行数,加快数据查询速度。 索引的实现一般使用的是B树和B+树这两种数据结构,因为它们都具有良好的平衡性,可以快速查找,插入和删除。 如何设计MySQL索引 确认需要优化的查询…

    数据结构 2023年5月17日
    00
  • C语言数据结构之队列的定义与实现

    C语言数据结构之队列的定义与实现 什么是队列 队列是一种特殊的线性数据结构,它只允许在队列的头部进行删除操作,在队列的尾部进行插入操作,这种操作方式被成为“先进先出”或“FIFO(first in first out)”。 队列的实现方式 队列可以通过数组和链表两种方式进行实现。 1. 数组实现 数组实现队列时,可以定义一个存放元素的数组,同时需要记录队列的…

    数据结构 2023年5月17日
    00
  • 【ACM算法竞赛日常训练】DAY5题解与分析【储物点的距离】【糖糖别胡说,我真的不是签到题目】| 前缀和 | 思维

    DAY5共2题: 储物点的距离(前缀和) 糖糖别胡说,我真的不是签到题目(multiset,思维) ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 原文链接(阅读原文获得更好阅读体验):https://www.eri…

    算法与数据结构 2023年4月18日
    00
  • C语言数据结构之 折半查找实例详解

    C语言数据结构之 折半查找实例详解 什么是折半查找? 折半查找是一种在有序数组中查找某个特定元素的算法。其基本思想是将查找的值与数组的中间元素比较,如果比中间元素小,则在数组的左半部分查找;如果比中间元素大,则在数组的右半部分查找,直到找到该元素或查找范围为空。 折半查找算法的流程 确定要查找的数组arr和其元素个数n,以及要查找的元素value。 设定左边…

    数据结构 2023年5月17日
    00
  • 字典树的基本知识及使用C语言的相关实现

    字典树的基本知识 字典树,英文名为Trie树,又称单词查找树或键树,是一种树形数据结构,用于表示关联数组或映射。它的优点是,可以大大减少无谓的字符串比较,查询效率比哈希表高。 字典树的核心概念是节点,每个节点包含一个字符和指向子节点的指针。根节点为空字符,每个字符串以一个独立的路径插入节点。如果一个字符串是另一个字符串的前缀,那么这个字符串的节点是另一个字符…

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