跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
Javajava算法

选择排序算法详解:直接、树形与堆排序

选择排序通过每趟从未排序序列中选出最小(或最大)元素放到已排序序列末尾来实现。主要包括直接选择排序、树形选择排序和堆排序三种变体。直接选择排序简单但效率低;树形选择排序减少比较次数;堆排序利用堆结构优化性能,时间复杂度稳定在 O(nlogn)。结合图解与 Java 代码,深入解析其原理与实现细节。

灵魂摆渡发布于 2026/3/16更新于 2026/5/96 浏览
选择排序算法详解:直接、树形与堆排序

前言

选择排序的核心思想是每一趟从待排序列中选取一个关键字值最小(或最大)的记录,将其放到已排序序列的末尾。第 1 趟从 n 个记录中选出最小值,第 2 趟从剩下的 n-1 个记录中选出次小值,直到所有记录归位。

1. 直接选择排序

思想

首先在所有记录中选出关键字值最小的记录,把它与第一个记录进行位置交换;然后在其余记录中再选出关键字值次小的记录与第二个记录交换,依此类推,直到所有记录排好序。

下图展示了直接选择排序的完整过程:

文章配图

示例

假设待排序的 8 个记录的关键字序列为 { 5, 3, 6, 4, 7, 1, 8, 2 }。初始序列视为无序序列,每趟从无序区选最小值与无序区首元素交换。

在这里插入图片描述

每趟遍历完成后,有序序列记录个数 +1,无序序列个数 -1。

代码实现

为了统一示例风格,这里使用 int[] 数组演示核心逻辑:

public void selectSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    // 进行 i-1 次遍历,arr.length:数组的长度
    for (int i = 0; i < arr.length - 1; i++) {
        int minIndex = i;
        // 在剩余未排序部分找到最小值的索引
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        // 如果找到的最小值不是当前位置,则交换
        if (minIndex != i) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

性能分析

直接选择排序的比较次数固定为 n(n-1)/2,移动次数取决于数据分布。时间复杂度始终为 O(n²),空间复杂度为 O(1)。它是不稳定排序。

文章配图

2. 树形选择排序

思想

树形选择排序(锦标赛排序)通过构建一棵完全二叉树来减少比较次数。先对 n 个记录两两比较,将较小者作为优胜者上升到父结点,得到当前最小值;然后对该记录再次参与比较,重复此过程直到选出所有有序记录。

这个过程可用一个含有 n 个叶子结点的完全二叉树来表示:

文章配图

示例

对于由 8 个关键字组成的序列 { 52, 39, 67, 95, 70, 8, 25, 52 },使用树形选择排序选出最小关键字值的过程如下:

在这里插入图片描述

可以看到关键字都处在最后一层的叶子节点上。怎么选呢?两两 PK,谁小谁上去到父亲节点,比如 52 和 39,39 上去;67 和 95,67 上去;70 和 8,8 上去。到上一层同样也是谁小谁上去,最后关键字最小的在最上面。

找出最小值后,将其替换为无穷大 "∞",再进行两两 PK 就可以找到次小值。以此类推,最后就排好序了。

在这里插入图片描述

性能分析

树形选择排序减少了比较次数,但需要额外的辅助空间存储树结构,且维护树结构的开销较大,实际应用中不如堆排序常见。

文章配图

3. 堆排序

定义

堆通常指完全二叉树,分为两种:

  • 大顶堆:所有父结点的值均大于或等于其左右孩子结点的值。根节点是整个序列的最大值。
  • 小顶堆:所有父结点的值均小于或等于其左右孩子结点的值。根节点是整个序列的最小值。

文章配图

方法

  1. 把待排序的记录序列对应成一棵完全二叉树,并把它转换成一个初始堆(建堆)。
  2. 此时根结点具有最大(或小)的关键字值,交换根结点和最后一个结点的位置。
  3. 除最后一个结点之外,前 n-1 个结点仍构成一棵完全二叉树,再把它们调整为一个堆。
  4. 重复上述步骤,直到只剩下一个根结点为止。

如何'筛选'?

所谓'筛选',指的是调整的过程。对一棵左/右子树均为堆的完全二叉树,'调整'根结点使整个二叉树也成为一个堆。

示例

排序之前的关键字序列为 { 40, 55, 49, 73, 12, 27, 98, 81, 64, 36 }。

筛选是一个从上往下进行的过程。可以看到每个父亲结点都比子节点大,所以是大顶堆。

在这里插入图片描述

但在排好序后最大值 98 应该放在最后面,下标为 9 的这个位置。这样就需要把 12 挪上去,即最大值需要跟最后一个结点的值进行交换。

在这里插入图片描述

但在 98 和 12 进行互换之后,它就不是堆了,因此需要对它进行'筛选',仍然把他调整成大顶堆。这时候需要从上到下筛选,但需要注意的是:因为已经找到最大值了,并且把他放在最后面,所以不需要再排最大值 98 了。

先看 12、81、49,如果是大顶堆,需要把 81 放在最上面,81 和 12 交换。

在这里插入图片描述

但现在还不是大顶堆,接下来 73、12、36 比较,谁大谁上去,并交换,所以 73 和 12 交换。

在这里插入图片描述

然后 55 和 64 比较,谁大谁上去。

在这里插入图片描述

这样的话又变成大顶堆了。根结点 81 最大,和无序序列的最后一个交换,这里是 12……以此类推,每次筛选都能找到最大的,这样就排好序了。

如何'建初始堆'?

建堆是一个从下往上进行'筛选'的过程。如果建一个大顶堆,最顶上的根结点一定是最大的。

示例

例如:排序之前的关键字序列为 { 40, 55, 49, 73, 12, 27, 98, 81, 64, 36 }。

在这里插入图片描述

先看下标 4 和 9,36 比 12 大,36 上去;再看左边下标 3、7、8,81 比 73、64 大,81 上去;再看右边下标 2、5、6,98 比 27、49 大,98 上去。

在这里插入图片描述

然后再来看下标 1、3、4,81 比 55、36 大,81 上去。

在这里插入图片描述

但这样我们就发现一个问题:下标 3、7、8,这三个数(55、73、64)就不能构成大顶堆了。所以三个数比较后,73 上去。

在这里插入图片描述

然后看下标 0、1、2,98 比 81、40 大,98 上去。

在这里插入图片描述

但现在下标 2、5、6,这三个数(40、27、49)就不能构成大顶堆了。所以三个数比较后,49 上去。

在这里插入图片描述

现在,左/右子树都已经调整为堆,最后只要调整根结点,使整个二叉树是个'堆'即可。这个就是建堆的过程,从下往上筛选(按照编号最大,并且有孩子的,此题中从 4 开始调整,然后 3、2、1),调整的过程中注意有可能打破子树上的堆,需要继续调。

代码实现

public class HeapSort {
    public static void heapSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        int len = arr.length;
        // 构建大顶堆,这里其实就是把待排序序列,变成一个大顶堆结构的数组
        buildMaxHeap(arr, len);
        // 交换堆顶和当前末尾的节点,重置大顶堆
        for (int i = len - 1; i > 0; i--) {
            swap(arr, 0, i);
            len--;
            heapify(arr, 0, len);
        }
    }

    private static void buildMaxHeap(int[] arr, int len) {
        // 从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
        for (int i = (int) Math.floor(len / 2) - 1; i >= 0; i--) {
            heapify(arr, i, len);
        }
    }

    private static void heapify(int[] arr, int i, int len) {
        // 先根据堆性质,找出它左右节点的索引
        int left = 2 * i + 1;
        int right = 2 * i + 2;
        // 默认当前节点(父节点)是最大值
        int largestIndex = i;
        
        if (left < len && arr[left] > arr[largestIndex]) {
            // 如果有左节点,并且左节点的值更大,更新最大值的索引
            largestIndex = left;
        }
        if (right < len && arr[right] > arr[largestIndex]) {
            // 如果有右节点,并且右节点的值更大,更新最大值的索引
            largestIndex = right;
        }
        
        if (largestIndex != i) {
            // 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
            swap(arr, i, largestIndex);
            // 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整
            heapify(arr, largestIndex, len);
        }
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

性能分析

堆排序的时间复杂度为 O(nlogn),空间复杂度为 O(1),是不稳定排序。它在处理大规模数据时表现优于直接选择排序。

文章配图

4. 总结

  • 直接选择排序:简单直观,但效率较低,适合小规模数据。
  • 树形选择排序:通过树结构减少比较次数,但空间开销较大。
  • 堆排序:利用堆结构优化性能,时间复杂度稳定在 O(nlogn),是工程中常用的排序算法之一。

想要排升序,就应该建立大堆;如果想要排降序,就应该建立小堆。筛选就是被破坏后比较、交换,谁大谁上去,从上到下的顺序;建堆是一个从下往上进行'筛选'的过程。

目录

  1. 前言
  2. 1. 直接选择排序
  3. 思想
  4. 示例
  5. 代码实现
  6. 性能分析
  7. 2. 树形选择排序
  8. 思想
  9. 示例
  10. 性能分析
  11. 3. 堆排序
  12. 定义
  13. 方法
  14. 如何“筛选”?
  15. 示例
  16. 如何“建初始堆”?
  17. 示例
  18. 代码实现
  19. 性能分析
  20. 4. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Spring AI 工具调用(Tool Calling)实战
  • Linux 入门:常用命令、软件安装与项目部署指南
  • 基于 AI 工具与 Astro 的开源官网重构实践
  • SpringBoot 国际化 i18n 实战:配置文件与动态切换方案
  • Dify Web 前端二次开发:隐藏探索功能与替换 Logo
  • OpenClaw 搭建 QQ AI 办公机器人:关键词触发与邮件集成
  • C++ 基础:auto 关键字、范围 for 循环与迭代器
  • MIT 电机模式控制:参数、场景与调试指南
  • Python 3 爬虫、数据清洗与可视化实战
  • 分布式事务与系统一致性:核心方案与实战解析
  • 1688.item_fee API:实现电商物流成本透明化管理
  • 眼镜店 AR 在线试戴小程序技术方案
  • 基于 PHP 与 Vue.js 的商城电商网站设计与实现
  • Claude Skills 功能特性与使用指南
  • 医疗 NLP 实战:电子病历分析与智能问答系统构建
  • Python 近期副业需求清单及常见项目类型
  • 昇腾 NPU 运行 Llama 模型:环境搭建与性能测试
  • Flutter 鸿蒙适配:基于 eip55 的以太坊地址校验方案
  • UI UX Pro Max:让 AI 编码助手具备专业前端设计能力
  • Python 基于 Flask 的小区停车场管理系统设计与实现

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online