《Java 实现堆排序:深入理解与代码剖析》

一、引言

排序算法在计算机科学领域中占据着重要的地位,它们能够帮助我们高效地组织和处理数据。堆排序作为一种高效的排序算法,基于二叉堆的数据结构,具有时间复杂度相对较低且性能稳定的优点。在这篇博客中,我们将详细解析一段用 Java 实现堆排序的代码,帮助大家深入理解堆排序的原理及实现细节。

二、堆排序原理

堆排序的核心思想是利用二叉堆这种数据结构的特性来进行排序。二叉堆分为大顶堆和小顶堆,在堆排序中通常使用大顶堆。大顶堆要求每个节点的值都不小于它的子节点的值。

堆排序主要分为两个步骤:

  1. 首先,将给定的数组构建成一个大顶堆。通过从最后一个非叶子节点开始,依次向上调整每个节点及其子节点的关系,使得整个数组满足大顶堆的性质。
  2. 然后,将堆顶元素(即数组中的最大值)与堆底元素交换位置,此时数组的末尾就放置了已排序好的最大值。接着,对除堆底元素外的剩余数组元素再次构建大顶堆,重复这个过程,直到整个数组都被排序完成。

三、代码分析

1. 代码整体结构

以下是我们要详细分析的 Java 堆排序代码:

java 复制代码
package 排序;

import java.util.Arrays;

public class HeapSort {

    public static void main(String[] args) {
        int[] arr = {5, 7, 4, 2, 0, 3, 1, 6};
        //第一步,构建大顶堆
        for (int p = arr.length - 1; p >= 0; p--) {
            adjust(arr, p, arr.length);
        }
        //第二步 堆顶和堆底元素进行交换,除堆底外剩余元素继续构建大顶堆
        for (int i = arr.length - 1; i >= 0; i--) {
            int temp = arr[i];
            arr[i] = arr[0];
            arr[0] = temp;
            adjust(arr, 0, i);
        }

        System.out.println(Arrays.toString(arr));
        //重复这样的操作
    }

    //堆的维护
    public static void adjust(int[] arr, int parent, int length) {
        //定义左孩子
        int child = 2 * parent + 1;
        while (child < length) {
            //定义右孩子
            int rchild = child + 1;
            if (rchild < length && arr[rchild] > arr[child]) {
                child++;
            }
            //child指向左右孩子的最大值
            //父子节点进行比较
            if (arr[parent] < arr[child]) {
                int temp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = temp;
                //parent指向child,child继续指向其左右孩子最大值
                parent = child;
                child = 2 * child + 1;
            } else {
                break;
            }
        }
    }
}

2. main方法

main 方法中,我们首先定义了一个整数数组 arr,并初始化它的值为 {5, 7, 4, 2, 0, 3, 1, 6}。这就是我们要进行排序的原始数组。

复制代码
int[] arr = {5, 7, 4, 2, 0, 3, 1, 6};

接下来,执行堆排序的两个主要步骤:

  • 构建大顶堆
    通过一个循环,从数组的最后一个非叶子节点开始,依次调用 adjust 函数来调整每个节点及其子节点的关系,使得整个数组逐步构建成大顶堆。循环条件 for (int p = arr.length - 1; p >= 0; p--) 确保我们从最后一个非叶子节点(即 arr.length / 2 - 1 位置的节点)开始,一直到数组的第一个节点(索引为 0),逐个对节点进行调整。

    for (int p = arr.length - 1; p >= 0; p--) {
    adjust(arr, p, arr.length);
    }

  • 交换与重新构建大顶堆
    在完成大顶堆的构建后,我们通过另一个循环,从数组的末尾开始,将堆顶元素(即数组中的最大值)与堆底元素交换位置。然后,调用 adjust 函数对除堆底元素外的剩余数组元素再次构建大顶堆。这个过程不断重复,直到整个数组都被排序完成。循环条件 for (int i = arr.length - 1; i >= 0; i--) 控制着每次交换和重新构建大顶堆的操作。

java 复制代码
for (int i = arr.length - 1; i >= 0; i--) {
    int temp = arr[i];
    arr[i] = arr[0];
    arr[0] = temp;
    adjust(arr, 0, i);
}

最后,通过 Arrays.toString 方法将排序后的数组以字符串的形式输出到控制台,以便我们直观地查看排序的结果。

复制代码
System.out.println(Arrays.toString(arr));

3. adjust 方法

adjust 方法是实现堆维护和调整的核心函数,用于确保在给定的节点及其子节点范围内,满足大顶堆的性质。

  • 初始化左孩子节点
    首先,通过公式 int child = 2 * parent + 1 计算出给定父节点 parent 的左孩子节点的索引。

    int child = 2 * parent + 1;

  • 寻找左右孩子中的最大值
    接着,定义右孩子节点的索引为 rchild = child + 1,并通过条件判断 if (rchild < length && arr[rchild] > arr[child]),如果右孩子存在(即 rchild < length)且右孩子的值大于左孩子的值,就将 child 指针指向右孩子,使得 child 始终指向左右孩子中的最大值。

java 复制代码
int rchild = child + 1;
if (rchild < length && arr[rchild] > arr[child]) {
    child++;
}
  • 父子节点比较与交换
    然后,将父节点 parent 的值与 child 指向的孩子节点(即左右孩子中的最大值)的值进行比较。如果父节点的值小于孩子节点的值,就通过临时变量 temp 进行交换操作,使得较大的值向上移动,满足大顶堆的性质。同时,更新 parentchild 的值,以便继续向下检查和调整子节点,确保整个子树都满足大顶堆的性质。
java 复制代码
if (arr[parent] < arr[child]) {
    int temp = arr[parent];
    arr[parent] = arr[child];
    arr[child] = temp;
    //parent指向child,child继续指向其左右孩子最大值
    parent = child;
    child = 2 * child + 1;
} else {
    break;
}

如果父节点的值不小于孩子节点的值,说明在当前节点及其子节点范围内已经满足大顶堆的性质,就可以停止调整,通过 break 语句跳出循环。

四、测试结果

当我们运行上述代码时,对于给定的初始数组 {5, 7, 4, 2, 0, 3, 1, 6},经过堆排序后,控制台会输出排序后的数组,其结果应该是 {0, 1, 2, 3, 4, 5, 6, 7}

相关推荐
祖力5511 小时前
数据结构的基本概念与单向链表(链表数据类型构造、创建、头插、尾插、头删、尾删)
数据结构·链表
大明者省12 小时前
WSL2 Ubuntu22.04 GPU训练环境配置指南
人工智能·算法·计算机视觉
白狐_79814 小时前
408数据结构第8章:排序②——性质对比秒杀、场景选择与外部排序
java·数据结构·算法
zander25814 小时前
LeetCode 84:柱状图中的最大矩形——单调栈如何确定左右边界
java·数据结构·算法
Shell运维手记14 小时前
Linux 常用基础命令学习笔记
linux·运维·笔记·学习·算法·github
杨航 AI15 小时前
O(n log n):线性对数原理拆解 这个是排序算法的黄金复杂度之一。
算法·排序算法
船厂电气自动化ai大模型15 小时前
AI大模型与数学 第32课 函数凹凸性与二阶导数:拐点求解、凹凸区间计算(10道二阶导数计算题)
数据结构·人工智能·python·深度学习·算法
旖旎夜光15 小时前
LeetCode 3:无重复字符的最长子串(滑动窗口) —— 题解
数据结构·c++·算法·leetcode·滑动窗口
叠层归一研究院17 小时前
AGI 系统(十一):二阶网络 — 二阶递归 × 多主体 (元符号网络)
开发语言·人工智能·算法·php·agi
zlinear数据采集卡17 小时前
D223的PWM电机控制:6路独立脉冲+加减速算法深度解析
arm开发·stm32·嵌入式硬件·算法·fpga开发·架构