讲师中心 微信公众号
AI工具推荐 视频效率加速

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

阿明酱_5685

阿明酱_5685

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

|

443人浏览过

|

来源于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<Integer> 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缓存命中率相对较低,在实际运行中可能不如归并排序或快速排序表现好,尽管它们的理论时间复杂度相同。
  • 不直观: 对于初学者来说,理解堆的数组表示和上浮/下沉操作确实需要一点时间去适应。

适用场景:

协同养育操作系统
协同养育操作系统

协同养育操作系统(Co-Parenting OS)— 离异家庭专业协同养育支持系统。12模块覆盖接送管理、成长记忆库、孩子观察站、亲子关系粘合剂、共同挑战中心、情绪冲突观察、父母成长、成长档案馆、成长阶段引擎、风险预警、驾驶舱、被恶意攻击急救包。当用户涉及离异家庭育儿、协同养育、抚养探视、接送安排、孩子成长记录、亲子关系、离异冲突、被恶意攻击、前任矛盾等话题时触发。

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

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

深入剖析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

热门AI工具

更多
豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
sort排序函数用法
sort排序函数用法

sort排序函数的用法:1、对列表进行排序,默认情况下,sort函数按升序排序,因此最终输出的结果是按从小到大的顺序排列的;2、对元组进行排序,默认情况下,sort函数按元素的大小进行排序,因此最终输出的结果是按从小到大的顺序排列的;3、对字典进行排序,由于字典是无序的,因此排序后的结果仍然是原来的字典,使用一个lambda表达式作为key参数的值,用于指定排序的依据。

1158

2023.09.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2701

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

376

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

417

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

472

2026.05.09

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

6347

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2668

2023.08.10

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

6456

2023.08.14

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

60

2026.10.28

热门下载

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

精品课程

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

共23课时 | 9.5万人学习

vscode其实很简单
vscode其实很简单

共72课时 | 32.5万人学习

JavaScript OOP调试技巧视频教程
JavaScript OOP调试技巧视频教程

共5课时 | 1.1万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn