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语言的相关实现

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

    数据结构 2023年5月17日
    00
  • 纯C++代码详解二叉树相关操作

    纯C++代码详解二叉树相关操作 介绍 二叉树是一种非常常见的数据结构,适用于处理需要具有层级关系的数据。在本文中,我们将详细讲解如何使用C++来实现二叉树的基本操作,包括创建、遍历、插入、删除等。 创建二叉树 定义二叉树节点 在C++中实现二叉树的概念,需要先定义二叉树节点的结构,代码如下: struct BinaryTreeNode { int value…

    数据结构 2023年5月17日
    00
  • C++数据结构二叉搜索树的实现应用与分析

    C++数据结构二叉搜索树的实现应用与分析 什么是二叉搜索树? 二叉搜索树(Binary Search Tree,BST),也称二叉查找树、二叉排序树,它是一种特殊的二叉树。对于每个节点,其左子树上所有节点的值均小于等于该节点的值,右子树上所有节点的值均大于等于该节点的值。通过这种特殊的结构,二叉搜索树能够帮助我们快速地执行查找、插入、删除等操作。 如何实现二…

    数据结构 2023年5月17日
    00
  • 浅谈PHP链表数据结构(单链表)

    介绍 链表是一种常见的数据结构,它包括单链表和双链表,本文中我们将会介绍PHP的单链表数据结构实现,具体而言我们将会实现一个包括插入节点,删除节点,打印节点等基本操作的单链表,帮助读者深入理解PHP链表数据结构。 创建节点 链表数据结构是由一个个节点组成的,我们首先要实现一个节点的创建函数,这个函数接受两个参数,一个是节点数据,另一个是下一个节点的指针地址。…

    数据结构 2023年5月17日
    00
  • nginx内存池源码解析

    Nginx内存池源码解析 Nginx是一个高性能、高并发的Web服务器。为了提高其性能和速度,Nginx采用了特殊的内存管理机制,即内存池。 什么是内存池? 内存池是一种高效的内存分配和管理机制。它将一块内存划分成多个大小相等的块,并按需分配给系统。当内存块不再使用时,它并不被立即释放,而是留在内存池中待重复利用。 Nginx内存池结构 Nginx内存池主要…

    数据结构 2023年5月17日
    00
  • 【ACM博弈论】SG函数入门(2):博弈树SG函数的转移与子游戏的合并

    上一篇文章我们讲了两种经典的博弈模型:《【ACM博弈论】SG函数入门(1):从巴什博奕到尼姆游戏》,这一节我们开始讲解SG函数。 ? 作者:Eriktse? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?? 阅读原文获得更好阅读体验:https:…

    算法与数据结构 2023年4月17日
    00
  • MySQL数据库体系架构详情

    MySQL数据库体系架构是MySQL数据库自身的发展和演变过程中逐渐形成的一个庞大的体系。这个体系由多个组件构成,包括连接器、查询缓存、解析器、优化器、执行器、存储引擎等多个部分,其中存储引擎是其中最具有代表性的组件之一。在这篇攻略中,我们将详细讲解MySQL数据库体系架构的各个部分,介绍它们各自的功能和作用。 连接器 MySQL的连接器负责与客户端建立连接…

    数据结构 2023年5月17日
    00
  • PHP 数据结构队列(SplQueue)和优先队列(SplPriorityQueue)简单使用实例

    下面我来为大家详细讲解一下“PHP 数据结构队列(SplQueue)和优先队列(SplPriorityQueue)简单使用实例”的攻略。 一、SplQueue 首先,我们先来介绍一下SplQueue。SplQueue是一个双向队列,它基于一个双向链表实现,可以在队列的两端插入和删除元素,既可以按照先进先出的顺序来操作队列,也可以反过来按照先进后出的顺序来操作…

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