0

0

java代码如何实现堆结构及堆排序功能 java代码堆数据结构的基础编写教程​

看不見的法師

看不見的法師

发布时间:2025-08-14 20:00:02

|

423人浏览过

|

来源于php中文网

原创

堆结构在Java中通过数组模拟树形结构,核心是维护堆属性的上浮和下沉操作,堆排序利用大顶堆进行原地排序,时间复杂度稳定为O(n log n),适用于优先级队列和Top K问题。

java代码如何实现堆结构及堆排序功能 java代码堆数据结构的基础编写教程​

在Java中实现堆结构和堆排序,核心在于利用数组模拟树形结构,并精心维护其“堆属性”(父节点总是大于或小于其子节点)。堆排序则是在此基础上,通过反复取出最大/最小元素来达到排序目的。说实话,这玩意儿初看有点绕,但一旦你理解了数组索引和树节点的关系,就豁然开朗了。

解决方案

要实现一个堆结构,我们通常会选择用数组来承载。一个Max-Heap(大顶堆)的核心是每个父节点的值都大于或等于其子节点。堆排序,顾名思义,就是利用这种堆结构来完成数组的排序。

1. 堆结构(Max-Heap)的实现

我们可以定义一个

MaxHeap
类,用
ArrayList
或者固定大小的数组来存储元素,这样更灵活些。这里我倾向于用
ArrayList
,因为它动态扩容省心。

立即学习Java免费学习笔记(深入)”;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

public class MaxHeap {
    private List heap;

    public MaxHeap() {
        this.heap = new ArrayList<>();
    }

    // 插入元素
    public void insert(int value) {
        heap.add(value);
        heapifyUp(heap.size() - 1); // 新元素可能破坏堆属性,需要上浮
    }

    // 提取最大元素(堆顶)
    public int extractMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("Heap is empty.");
        }
        int max = heap.get(0);
        int lastElement = heap.remove(heap.size() - 1); // 移除最后一个元素
        if (!isEmpty()) {
            heap.set(0, lastElement); // 将最后一个元素放到堆顶
            heapifyDown(0); // 新堆顶可能破坏堆属性,需要下沉
        }
        return max;
    }

    // 查看最大元素(不移除)
    public int peekMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("Heap is empty.");
        }
        return heap.get(0);
    }

    public boolean isEmpty() {
        return heap.isEmpty();
    }

    public int size() {
        return heap.size();
    }

    // 元素上浮操作:当新元素插入或元素值增大时,将其向上移动以维护堆属性
    private void heapifyUp(int index) {
        int parentIndex = (index - 1) / 2; // 计算父节点索引
        while (index > 0 && heap.get(index) > heap.get(parentIndex)) {
            swap(index, parentIndex);
            index = parentIndex;
            parentIndex = (index - 1) / 2;
        }
    }

    // 元素下沉操作:当堆顶元素被移除或元素值减小时,将其向下移动以维护堆属性
    private void heapifyDown(int index) {
        int leftChildIndex = 2 * index + 1;
        int rightChildIndex = 2 * index + 2;
        int largestIndex = index; // 假设当前节点最大

        // 检查左子节点
        if (leftChildIndex < heap.size() && heap.get(leftChildIndex) > heap.get(largestIndex)) {
            largestIndex = leftChildIndex;
        }

        // 检查右子节点
        if (rightChildIndex < heap.size() && heap.get(rightChildIndex) > heap.get(largestIndex)) {
            largestIndex = rightChildIndex;
        }

        // 如果最大值不是当前节点,则交换并继续下沉
        if (largestIndex != index) {
            swap(index, largestIndex);
            heapifyDown(largestIndex);
        }
    }

    // 交换两个位置的元素
    private void swap(int i, int j) {
        int temp = heap.get(i);
        heap.set(i, heap.get(j));
        heap.set(j, temp);
    }

    // 用于调试,打印堆内容
    public void printHeap() {
        System.out.println(heap.toString());
    }
}

2. 堆排序功能的实现

堆排序通常是原地排序,直接在原数组上操作。它分为两个主要阶段:

  • 建堆(Build Heap):将一个无序数组构建成一个大顶堆(或小顶堆)。这个过程从最后一个非叶子节点开始,依次向上对其进行下沉操作。
  • 排序(Sort):重复地将堆顶元素(最大值)与堆的最后一个元素交换,然后将剩余的元素重新调整为堆。每次交换后,堆的大小减一。
public class HeapSort {

    // 堆排序主方法
    public static void sort(int[] arr) {
        int n = arr.length;

        // 1. 构建大顶堆(从最后一个非叶子节点开始向上heapify)
        // 最后一个非叶子节点的索引是 (n/2 - 1)
        for (int i = n / 2 - 1; i >= 0; i--) {
            heapify(arr, n, i);
        }

        // 2. 逐个提取元素进行排序
        for (int i = n - 1; i > 0; i--) {
            // 将当前最大元素(堆顶)与当前堆的最后一个元素交换
            swap(arr, 0, i);
            // 对剩余的元素(不包括已排序的元素)重新进行堆化
            heapify(arr, i, 0); // 注意这里的n变成了i,表示堆的有效大小
        }
    }

    // 维护堆属性的下沉操作(与MaxHeap中的heapifyDown类似,但操作的是数组)
    private static void heapify(int[] arr, int n, int i) {
        int largest = i; // 初始化最大元素为根节点
        int left = 2 * i + 1; // 左子节点
        int right = 2 * i + 2; // 右子节点

        // 如果左子节点存在且大于当前最大元素
        if (left < n && arr[left] > arr[largest]) {
            largest = left;
        }

        // 如果右子节点存在且大于当前最大元素
        if (right < n && arr[right] > arr[largest]) {
            largest = right;
        }

        // 如果最大元素不是根节点,则交换并递归下沉
        if (largest != i) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    // 交换数组中两个元素的位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        // 测试MaxHeap
        System.out.println("--- MaxHeap 示例 ---");
        MaxHeap maxHeap = new MaxHeap();
        maxHeap.insert(3);
        maxHeap.insert(2);
        maxHeap.insert(15);
        maxHeap.insert(5);
        maxHeap.insert(4);
        maxHeap.insert(45);
        maxHeap.printHeap(); // 应该看到一个符合大顶堆特性的数组表示

        System.out.println("提取最大值: " + maxHeap.extractMax()); // 45
        maxHeap.printHeap();

        System.out.println("提取最大值: " + maxHeap.extractMax()); // 15
        maxHeap.printHeap();

        // 测试HeapSort
        System.out.println("\n--- 堆排序示例 ---");
        int[] data = {12, 11, 13, 5, 6, 7};
        System.out.println("原始数组: " + java.util.Arrays.toString(data));
        HeapSort.sort(data);
        System.out.println("排序后数组: " + java.util.Arrays.toString(data));

        int[] data2 = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
        System.out.println("原始数组2: " + java.util.Arrays.toString(data2));
        HeapSort.sort(data2);
        System.out.println("排序后数组2: " + java.util.Arrays.toString(data2));
    }
}

堆结构在Java中为何如此重要?其优缺点与适用场景分析

说实话,堆这个数据结构在计算机科学里地位挺特殊的,它既不像链表那么直观,也不像哈希表那样“快得离谱”,但它在某些场景下就是无可替代。在Java里,最常见的应用就是

java.util.PriorityQueue
,它底层就是用堆实现的。

优点:

  • 高效的优先级队列实现: 这是堆最核心的价值。无论是插入元素还是取出最高(或最低)优先级的元素,时间复杂度都是O(log n)。这比数组或链表实现的优先级队列效率高得多。
  • 稳定的时间复杂度: 堆排序在最坏、平均、最好情况下的时间复杂度都是O(n log n),这一点比快速排序(最坏O(n^2))要稳定得多。对于需要稳定性能保证的场景,堆排序是个不错的选择。
  • 原地排序(对于堆排序): 堆排序只需要常数级的额外空间,因为它直接在原数组上进行操作。这对于内存受限的环境来说是个大优点。
  • 查找Kth最大/最小元素: 堆可以非常高效地找到数组中第K个最大或最小的元素,时间复杂度通常是O(n log k)。

缺点:

  • 不稳定排序: 堆排序是一种不稳定的排序算法,这意味着相同值的元素的相对顺序在排序后可能会改变。如果你对元素的原始顺序有要求,这可能会是个问题。
  • 缓存局部性差: 堆在数组中的表示是跳跃的,父子节点在内存中不一定是连续的。这会导致CPU缓存命中率相对较低,在实际运行中可能不如归并排序或快速排序表现好,尽管它们的理论时间复杂度相同。
  • 不直观: 对于初学者来说,理解堆的数组表示和上浮/下沉操作确实需要一点时间去适应。

适用场景:

  • 优先级队列: 任务调度(操作系统、网络路由器)、事件模拟、Dijkstra最短路径算法、Prim最小生成树算法等。
  • Top K 问题: 从海量数据中找出最大的K个元素,或者最小的K个元素。比如,找出访问量最高的100个网页,或者销售额最高的50个商品。
  • 外部排序: 当数据量大到内存无法一次性加载时,可以利用堆进行分块排序。
  • 在线算法: 实时处理数据流,比如实时中位数计算。

总的来说,堆虽然有些“怪脾气”,但它在需要高效处理“最大/最小”或“优先级”这类问题的场景中,简直就是个MVP。

Artbreeder
Artbreeder

创建令人惊叹的插画和艺术

下载

深入剖析Java堆操作核心:上浮(Swim)与下沉(Sink)机制详解

堆操作的核心,毫无疑问就是那两个听起来有点玄乎的“上浮”(Swim,有时也叫

heapifyUp
)和“下沉”(Sink,或
heapifyDown
)。这俩是维护堆属性的基石,所有的插入、删除操作都离不开它们。我个人觉得,理解了这两个操作,就等于抓住了堆的灵魂。

1. 上浮(Swim /

heapifyUp

想象一下,你往一个已经整理好的大顶堆里塞了一个新元素。这个新元素,我们暂时把它放在数组的最后面。但问题来了,它可能比它的父节点还大!这就破坏了堆的“父大子小”的规矩。怎么办?

上浮操作就是来解决这个问题的。它会不断地把这个“不守规矩”的新元素和它的父节点进行比较。如果新元素比父节点大,那就交换它们的位置。然后,新元素就“上浮”了一层,它会继续和新的父节点比较,直到它找到了一个比它大的父节点(或者它自己成了堆顶),这样堆的属性就恢复了。

  • 逻辑: 从当前节点开始,与其父节点比较。如果当前节点值大于父节点值,则交换两者位置,然后将当前节点索引更新为原父节点索引,继续向上比较,直到根节点或不再大于父节点。
  • 时间复杂度: O(log n),因为每次操作都向上移动一层,而堆的高度是log n。

2. 下沉(Sink /

heapifyDown

下沉操作通常发生在两种情况:

  • 你从堆顶取走了最大(或最小)的元素后,为了填补空缺,把堆里最后一个元素挪到了堆顶。
  • 某个元素的值变小了,它可能不再比它的子节点大。

无论是哪种情况,堆顶(或某个节点)都可能不再符合堆的属性。下沉操作就是让这个“不守规矩”的元素向下移动,直到它找到一个合适的位置。它会比较自己和它的两个子节点,找出其中最大的那个(对于大顶堆)。如果它自己不是最大的,就和最大的那个子节点交换位置,然后继续向下沉,直到它比两个子节点都大(或者它已经到了叶子节点)。

  • 逻辑: 从当前节点开始,与其左、右子节点比较。找出当前节点、左子节点、右子节点中值最大的那个。如果最大值不是当前节点,则将当前节点与最大值的子节点交换,然后将当前节点索引更新为交换后的子节点索引,继续向下比较,直到叶子节点或不再小于子节点。
  • 时间复杂度: O(log n),原理同上浮,每次操作向下移动一层。

这两个操作就像是堆的“自愈”机制。无论你往堆里加什么,或者从堆里拿走什么,它们都能确保堆的结构始终保持着那种有序性。理解它们是理解堆性能的关键,也是自己手写堆代码时最容易出错但也最能体现功力的地方。

Java实现堆结构时常见的挑战与优化策略

手写堆结构和堆排序,虽然理论上清晰,但实际敲代码时总会遇到些“坑”。这就像你看着地图觉得路很好走,真走起来才发现有小石子绊脚。

常见的挑战:

  • 索引计算错误: 这是最常见的,尤其是对于0-based数组(Java默认)和1-based数组(一些理论教材)之间的转换。
    • 父节点:
      (i - 1) / 2
    • 左子节点:
      2 * i + 1
    • 右子节点:
      2 * i + 2
      稍微写错一个数字,整个堆可能就乱了。
  • 边界条件处理: 比如堆为空时
    extractMax
    peekMax
    的异常处理;在
    heapifyUp
    heapifyDown
    中,判断子节点或父节点是否存在(
    index > 0
    childIndex < heap.size()
    )。这些细节处理不好,就容易出现
    IndexOutOfBoundsException
  • 大顶堆/小顶堆的判断逻辑: 到底是
    >
    还是
    <
    ?如果混淆了,你可能把一个大顶堆写成了小顶堆,或者反之。对于排序,大顶堆通常用于升序排序(每次取最大),小顶堆用于降序排序(每次取最小)。
  • 原地排序的理解: 堆排序的第二阶段,每次把堆顶元素放到数组末尾已排序区域时,要记得更新堆的有效大小(
    n
    i
    参数),否则会把已排序的元素又

相关文章

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

832

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

737

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

734

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16925

2023.08.03

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 2.7万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.5万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号