跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

初阶数据结构:常见排序算法原理与实现(上)

介绍数据结构中的排序算法,涵盖插入排序(直接插入、希尔排序)和选择排序(直接选择、堆排序)。详细阐述各算法的基本思想、排序原理、代码实现及时间空间复杂度分析。重点讲解升序排列实现,对比不同场景下的效率差异,适合初阶学习者掌握排序逻辑。

修罗发布于 2026/3/30更新于 2026/9/276 浏览
初阶数据结构:常见排序算法原理与实现(上)

1.排序的概念及分类

排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作

常见的排序算法可以按如图所示分类。

以下所有的排序算法均以升序的方式实现。

2.插入排序

2.1 直接插入排序(InsertSort)

直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

简单来说,平常玩的扑克牌游戏就是一种插入排序。

排序原理:

当插入第 i ( i >= 1 ) 个元素时,前面的 array[0] ,array[1] ,… ,array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i - 1] ,array[i - 2] ,… 的排序码顺序进行比较,找到插入位置即将 array[i] 插入,原来位置上的元素顺序后移。

💻排序实现:

void InsertSort(int* a, int n) {
    for (int i = 1; i < n; ++i) {
        int end = i - 1;
        int tmp = a[i];
        while (end >= 0) {
            if (tmp < a[end]) {
                a[end + 1] = a[end];
                --end;
            } else {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}

⌨️代码解读:

  1. 第一个 for 循环指的是从第一个数开始依次往后遍历,end 指向已排序部分的最后一个元素(即开始的 a[0] ),tmp = a[i] 提前保存该位置的数据,避免移动时的覆盖消除该数据。
  2. 从后往前遍历已排序部分,找到合适的插入位置,如果当前要插入的元素小于已排序部分的元素,将已排序部分的元素后移一位,--end 继续向前比较;如果找到合适的插入位置,break 退出循环。
  3. 找到合适位置后,a[end + 1] = tmp 将当前要插入的元素插入到合适的位置。

🖱️复杂度分析:

• 时间复杂度:

○ **最好情况:**数组已经是有序的,此时内层的 while 循环每次只需要比较一次就会退出,时间复杂度为 O(n)。

○ **最坏情况:**数组是逆序的,内层的 while 循环每次都需要比较到已排序部分的第一个元素,时间复杂度为 O(n²)。

○ **平均情况:**时间复杂度为 O(n²)。

• 空间复杂度:O(1)。

**总结:**元素集合越接近有序,直接插入排序算法的时间效率越高。

2.2 希尔排序(ShellSort)

希尔排序又称缩小增量法。其基本思想是:先选定一个整数 gap ,把待排序文件中所有记录分成多个组,所有距离为 gap 的记录分在同一组内,并对每一组内的记录进行排序。然后,取新的 gap ,重复上述分组和排序的工作。当到达 gap = 1 时,所有记录在统一组内排好序。

相同颜色的数字为同一组。

💻排序实现:

**版本 1:**按组分配排序

void ShellSort(int* a, int n) {
    int gap = n;
    for (int j = 0; j < gap; j++) {
        gap /= 2;
        for (int i = j; i < n - gap; i += gap) {
            int end = i;
            int tmp = a[i + gap];
            while (end >= 0) {
                if (tmp < a[end]) {
                    a[end + gap] = a[end];
                    end -= gap;
                } else {
                    break;
                }
                a[end + gap] = tmp;
            }
        }
    }
}

**版本 2:**依次排序

void ShellSort(int* a, int n) {
    int gap = n;
    while (gap > 1) {
        gap /= 2;
        // gap = n / 3 + 1;
        for (int i = 0; i < n - gap; i++) {
            int end = i;
            int tmp = a[i + gap];
            while (end >= 0) {
                if (tmp < a[end]) {
                    a[end + gap] = a[end];
                    end -= gap;
                } else {
                    break;
                }
                a[end + gap] = tmp;
            }
        }
    }
}

⌨️代码解读:

希尔排序其实和插入排序是十分相似的,希尔排序是对直接插入排序的优化,只不过是每间隔 gap 个数进行排序,插入排序间隔是 1 ,通过多次 gap 的改变,多次的分组排序最终能趋向于有序。

🔥值得注意的是:

  1. for 循环中 i < n - gap 是因为排完序后剩下的数根据 gap 的大小,剩下的数已经自动有序了。
  2. gap /= 2 和 gap = n / 3 + 1 都是可以的,并没有严格的限制。
  3. tmp 一直在 end 的下一个位置,插入的位置一直在 end 的下一个位置。
  4. 当 gap > 1 时都是预排序,目的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。
  5. 希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定。

《数据结构(C语言版)》— 严蔚敏:

🖱️复杂度分析:

• 时间复杂度:

○ **最好情况:**数组已经是有序的,时间复杂度为 O(n)。

○ **最坏情况:**数组是逆序的,时间复杂度为 O(n²)。

○ **平均情况:**时间复杂度大约为 O(n^1.3)。

• 空间复杂度:O(1)。

**总结:**希尔排序是不稳定的。

3.选择排序

3.1 直接选择排序(SelectSort)

直接选择排序可以说是最简单的一种排序了,其基本思想为:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

排序原理:

  1. 在元素集合 array[i] – array[n-1] 中选择关键码最大 (小) 的数据元素。
  2. 若它不是这组元素中的最后一个 (第一个) 元素,则将它与这组元素中的最后一个(第一个)元素交换。
  3. 在剩余的 array[i] – array[n-2](array[i+1] – array[n-1])集合中,重复上述步骤,直到集合剩余 1 个元素。

💻排序实现:

void Swap(int* p1, int* p2) {
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

void SelectSort(int* a, int n) {
    int left = 0, right = n - 1;
    while (left < right) {
        int mini = left, maxi = right;
        for (int i = left; i <= right; ++i) {
            if (a[i] < a[mini]) {
                mini = i;
            }
            if (a[i] > a[maxi]) {
                maxi = i;
            }
        }
        Swap(&a[left], &a[mini]);
        // 如果 left 和 maxi 重叠,交换后修正一下
        if (left == maxi) {
            maxi = mini;
        }
        Swap(&a[right], &a[maxi]);
        ++left;
        --right;
    }
}

⌨️代码解读:

  1. mini 初始化为 left,maxi 初始化为 right,用于记录当前未排序部分的最小值和最大值的索引。
  2. 通过 for 循环从 left 到 right 遍历数组,比较每个元素与 a[mini] 和 a[maxi] 的大小,如果发现更小的元素则更新 mini,如果发现更大的元素则更新 maxi。
  3. 调用 Swap 函数将最小值 a[mini] 交换到左边界 a[left] 的位置,最大值 a[maxi] 交换到右边界 a[right] 的位置。
  4. ++left 和 --right 分别将左边界右移和右边界左移,缩小未排序部分的范围。

🔥**值得注意的是:**如果 left 恰好等于 maxi,说明在交换最小值时,最大值的位置已经被交换到了 mini 处,所以需要将 maxi 更新为 mini。

🖱️复杂度分析:

• 时间复杂度:

○ **最好情况:**即使数组已经有序,每一轮仍然需要遍历未排序部分来确定最小值和最大值的位置,时间复杂度为 O(n)。

○ **最坏情况:**当数组是逆序排列时,同样需要进行完整的遍历和交换操作,时间复杂度为 O(n²)。

○ **平均情况:**时间复杂度为 O(n²)。

• 空间复杂度:O(1)。

**总结:**直接选择排序思考非常好理解,但是效率不是很好,实际中很少使用。

3.2 堆排序(HeapSort)

堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

在堆的专题章节已经有详细的分析了,这里就不过多讲解。

**总结:**堆排序使用堆来选数,效率就高了很多。

目录

  1. 1.排序的概念及分类
  2. 2.插入排序
  3. 2.1 直接插入排序(InsertSort)
  4. 2.2 希尔排序(ShellSort)
  5. 3.选择排序
  6. 3.1 直接选择排序(SelectSort)
  7. 3.2 堆排序(HeapSort)

更多推荐文章

查看全部
  • Linux Shell 运行原理及权限管理详解
  • WebRTC 调试实战:基于 Chromium 的高效问题定位方法
  • Fun-ASR WebUI 运行环境选择:Chrome 与 Edge 对比分析
  • Web 基础:动静态 Web、URL 与 HTTP 协议
  • Win10 系统关闭 Microsoft Copilot 弹窗的 6 种有效方案
  • 程序员发现以太坊关键漏洞获赏 1272 万元
  • 动态规划:买卖股票的最佳时机 III
  • OpenClaw Web UI 访问报错 Not Found 排查与修复
  • ComfyUI 运行 Wan 2.1 工作流:生成电影级视频(兼容 Mac/Windows)
  • ChatGPT-4o 发布:AI 大模型迈入感知时代
  • Python FastAPI 入门实战:从零构建 API 服务
  • 企业级风控接入:天远车辆出险查询API Java 集成指南
  • 企业级图像 AIGC 技术观察:Seedream 4.0 模型能力与应用场景
  • OpenClaw 在 Mac 上本地化部署及接入飞书教程
  • Qwen3-32B 模型部署:使用 Clawdbot 网关实现 WebSocket 长连接
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署推理与微调实践
  • 二分查找算法进阶:山脉数组与旋转排序
  • CCF-CSP 认证:机器人复健指南题解
  • Dify 工作流集成 Sambert-Hifigan 语音合成 API 实现对话机器人
  • Python 搭建具备记忆与人工干预的 Agent 机器人

相关免费在线工具

  • 加密/解密文本

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

  • Gemini 图片去水印

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

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online