news 2026/10/5 2:28:56

排序算法:快慢与稳不稳,你总得选一个

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
排序算法:快慢与稳不稳,你总得选一个

引言

在前面的博客中,我们学习了栈、队列等基础数据结构,它们解决的是“如何组织数据”的问题。而今天要聊的排序算法,解决的是“如何让数据有序”的问题。排序看似简单,却暗藏着一场效率与稳定的博弈:有的算法跑得快但“喜新厌旧”,有的算法四平八稳却要付出额外空间。两者看似矛盾,却在不同的业务场景下各显神通。

这篇博客将从三个角度展开:

  • 评价指标:先弄清时间复杂度、空间复杂度和稳定性这些“裁判标准”。
  • 过程演示:用状态图直观展示每种排序的“舞步”。
  • C 语言实现:给出完整可运行的代码,边看边练。

一、排序算法的评价指标

学习排序之前,先明确几个概念:

1. 思路

排序算法的好坏,主要看三个评价指标:

  • 时间复杂度:算法执行时间随数据规模增长的变化趋势。
  • 空间复杂度:算法运行过程中额外需要的存储空间。
  • 稳定性:如果排序前两个相等元素的相对顺序,在排序后保持不变,那么这个排序算法就是稳定的。

举个例子:

排序前:5(a), 3, 5(b), 1 排序后:1, 3, 5(a), 5(b)

如果5(a)仍然在5(b)前面,就是稳定排序;如果两者位置互换,就是不稳定排序。

2. 过程演示

常见复杂度一览:

O(n²) :冒泡、选择、插入 O(n log n):归并、快速、堆排序 O(n + k) :计数排序

3. C 语言实现

这里先给出一个通用的交换函数,后续所有排序都会用到:

/* ====== 通用交换函数 ====== */ void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }

二、冒泡排序:相邻元素的“擂台赛”

1. 思路

冒泡排序就像一场相邻元素的擂台赛:每次比较相邻两个元素,如果顺序错误就交换。每一轮结束后,最大的元素会像气泡一样“冒泡”到末尾。

  • 每一轮从第一个元素开始,两两比较相邻元素。
  • 如果前一个比后一个大,就交换它们。
  • 一轮结束后,最大的元素“沉底”。
  • 如果某一轮没有发生任何交换,说明已经有序,可以提前结束。

2. 过程演示

以数组[5, 3, 8, 1]为例:

初始状态:[5, 3, 8, 1] 第 1 轮: 比较 5 和 3 -> 交换 -> [3, 5, 8, 1] 比较 5 和 8 -> 不交换 -> [3, 5, 8, 1] 比较 8 和 1 -> 交换 -> [3, 5, 1, 8] (8 冒泡到末尾) 第 2 轮: 比较 3 和 5 -> 不交换 -> [3, 5, 1, 8] 比较 5 和 1 -> 交换 -> [3, 1, 5, 8] (5 冒泡到倒数第二) 第 3 轮: 比较 3 和 1 -> 交换 -> [1, 3, 5, 8] (3 冒泡到位) 结果状态:[1, 3, 5, 8]

3. C 语言实现

这里复用上面的swap函数:

/* ====== 冒泡排序 ====== */ void bubbleSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int swapped = 0; // 标记本轮是否发生交换 for (int j = 0; j < n - 1 - i; j++) { if (arr[j] > arr[j + 1]) { swap(&arr[j], &arr[j + 1]); swapped = 1; } } // 如果本轮没有发生交换,说明已经有序 if (!swapped) { break; } } }

三、选择排序:每轮挑出“最靓的仔”

1. 思路

选择排序的思路很直白:每一轮从未排序区间中选择最小元素,放到已排序区间的末尾。就像每次从一堆牌里挑出最小的一张,依次排好。

  • 把数组分成已排序区和未排序区。
  • 每一轮在未排序区中找到最小元素的下标。
  • 如果最小元素不在未排序区开头,就把它交换到开头。
  • 重复 n-1 轮,数组就有序了。

2. 过程演示

以数组[5, 3, 8, 1]为例:

初始状态:[5, 3, 8, 1] 第 1 轮:未排序区 [5, 3, 8, 1],最小是 1(下标 3) 交换 5 和 1 -> [1, 3, 8, 5] (1 归位) 第 2 轮:未排序区 [3, 8, 5],最小是 3(下标 1) 已在开头,不交换 -> [1, 3, 8, 5] (3 归位) 第 3 轮:未排序区 [8, 5],最小是 5(下标 3) 交换 8 和 5 -> [1, 3, 5, 8] (5 归位) 结果状态:[1, 3, 5, 8]

3. C 语言实现

这里复用上面的swap函数:

/* ====== 选择排序 ====== */ void selectionSort(int arr[], int n) { for (int i = 0; i < n - 1; i++) { int minIndex = i; // 记录最小元素下标 for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } if (minIndex != i) { swap(&arr[i], &arr[minIndex]); } } }

四、插入排序:像整理扑克牌一样

1. 思路

插入排序就像整理手中的扑克牌:每次从未排序区取出一张牌,插入到已排序区的正确位置。数据量小或基本有序时,它效率极高。

  • 把数组分成已排序区和未排序区。
  • 每次从未排序区取出第一个元素key。
  • 从后往前扫描已排序区,把比key大的元素依次后移。
  • 找到合适位置后,把key插入。

2. 过程演示

以数组[5, 3, 8, 1]为例:

初始状态:[5, 3, 8, 1] 第 1 轮:key = 3,已排序区 [5] 5 > 3,后移 -> [5, 5, 8, 1] 插入 3 -> [3, 5, 8, 1] 第 2 轮:key = 8,已排序区 [3, 5] 5 < 8,不移动 -> [3, 5, 8, 1] 插入 8 -> [3, 5, 8, 1] 第 3 轮:key = 1,已排序区 [3, 5, 8] 8 > 1,后移 -> [3, 5, 8, 8] 5 > 1,后移 -> [3, 5, 5, 8] 3 > 1,后移 -> [3, 3, 5, 8] 插入 1 -> [1, 3, 5, 8] 结果状态:[1, 3, 5, 8]

3. C 语言实现

/* ====== 插入排序 ====== */ void insertionSort(int arr[], int n) { for (int i = 1; i < n; i++) { int key = arr[i]; // 待插入的元素 int j = i - 1; // 从后往前找插入位置 while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = key; } }

五、希尔排序:插入排序的“跳跃升级”

1. 思路

希尔排序是插入排序的改进版。它通过设置一个增量gap,先对间隔为gap的元素进行插入排序,再逐渐缩小gap,最终当gap = 1时完成排序。这样可以让元素“大步跳跃”到接近最终位置,减少后续移动次数。

  • 初始gap = n / 2,每次循环gap /= 2。
  • 对每个间隔为gap的子序列做插入排序。
  • 当gap = 1时,就是普通的插入排序,但此时数组已基本有序。

2. 过程演示

以数组[5, 3, 8, 1]为例:

初始状态:[5, 3, 8, 1] gap = 2: 子序列1:[5, 8](下标 0, 2)-> 已有序 子序列2:[3, 1](下标 1, 3)-> 排序后 [1, 3] 结果:[5, 1, 8, 3] gap = 1: 普通插入排序 -> [1, 3, 5, 8] 结果状态:[1, 3, 5, 8]

3. C 语言实现

/* ====== 希尔排序 ====== */ void shellSort(int arr[], int n) { for (int gap = n / 2; gap > 0; gap /= 2) { for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } } }

六、归并排序:分而治之的“合纵连横”

1. 思路

归并排序采用分治法,把大问题拆成小问题,再合并结果:

  • 把数组分成左右两部分。
  • 分别对左右两部分排序(递归)。
  • 合并两个有序数组,得到整体有序。

归并排序是稳定的,但需要额外空间。

2. 过程演示

以数组[5, 3, 8, 1]为例:

初始状态:[5, 3, 8, 1] 拆分: [5, 3] 和 [8, 1] [5] [3] 和 [8] [1] 合并: [3, 5] 和 [1, 8] [1, 3, 5, 8] 结果状态:[1, 3, 5, 8]

3. C 语言实现

#include <stdlib.h> /* ====== 合并两个有序区间 ====== */ void merge(int arr[], int temp[], int left, int mid, int right) { int i = left; int j = mid + 1; int k = left; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } for (i = left; i <= right; i++) { arr[i] = temp[i]; } } /* ====== 归并排序(递归) ====== */ void mergeSort(int arr[], int temp[], int left, int right) { if (left >= right) { return; } int mid = left + (right - left) / 2; mergeSort(arr, temp, left, mid); mergeSort(arr, temp, mid + 1, right); merge(arr, temp, left, mid, right); } /* ====== 归并排序入口 ====== */ void mergeSortWrapper(int arr[], int n) { int *temp = (int *)malloc(n * sizeof(int)); if (temp == NULL) { return; } mergeSort(arr, temp, 0, n - 1); free(temp); }

七、快速排序:选个“基准”当裁判

1. 思路

快速排序也采用分治法,但思路更“霸道”:

  • 选择一个基准值pivot。
  • 把小于等于基准值的元素放到左边,大于基准值的放到右边。
  • 对左右区间递归排序。

快速排序平均性能极佳,但不稳定,最坏情况会退化到 O(n²)。

2. 过程演示

以数组[5, 3, 8, 1]为例(选最后一个元素为基准):

初始状态:[5, 3, 8, 1],pivot = 1 分区: 5 > 1,不动;3 > 1,不动;8 > 1,不动 把 1 换到开头 -> [1, 5, 3, 8] 递归左区间:[1](已有序) 递归右区间:[5, 3, 8],pivot = 8 5 < 8,不动;3 < 8,不动 8 已在末尾 -> [5, 3, 8] 递归左区间:[5, 3],pivot = 3 5 > 3,不动 把 3 换到开头 -> [3, 5] 结果状态:[1, 3, 5, 8]

3. C 语言实现

这里复用上面的

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 2:28:31

(142页PPT)PLM+ERP系统选型及建设方案建议书(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92796370/92933136 资料解读&#xff1a;《&#xff08;142页PPT&#xff09;PLMERP系统选型及建设方案建议书》 详细资料请看本解读文章的最后内容。…

作者头像 李华
网站建设 2026/10/5 2:28:10

数据湖Paimon 1.4.2 从理论到实践 —— 第 12 章 Paimon 1.4 多模态数据湖

数据湖Paimon 1.4.2 从理论到实践 —— 第 12 章 Paimon 1.4 多模态数据湖 课程定位:本系列教程以 Paimon 1.4.2 为核心湖存储格式,Flink 1.20.3 为流批一体计算引擎,Doris 4.1 为 OLAP 查询层,构建"湖存储 + 流批计算 + 实时查询"的湖仓一体技术体系,从原理到生…

作者头像 李华
网站建设 2026/10/5 2:28:03

能碳管理系统选型:五级勾稽门禁与落地顺序

能碳管理系统要把五级勾稽做成月结门禁&#xff0c;窗口期才少返工。五级指产量、能源、强度、碳排放、材料索引在同一把锁账月上互相指认&#xff1b;未关闭的逻辑矛盾不应允许锁账导出。结论&#xff1a;选型先看勾稽能不能拦住未关闭异常&#xff0c;再看大屏。 智碳能碳云…

作者头像 李华
网站建设 2026/10/5 2:28:02

终端安全基线加固:终端安全配置模块功能与实践思考

前言 企业内网终端规模持续扩张&#xff0c;同时存在 Windows、麒麟、统信等多类型操作系统&#xff0c;终端基线漂移、配置被篡改、弱口令、系统漏洞等问题层出不穷。 很多早期企业依赖 Windows 组策略完成终端基线加固&#xff0c;但在混合终端场景下&#xff0c;组策略的短板…

作者头像 李华
网站建设 2026/10/5 2:27:46

从中国采购铝塑管:供应商评估与出货前验货清单(完整版)

对进口商、批发商、管道分销商、品牌运营商和系统采购方而言&#xff0c;评估铝塑管供应商不能只看样品、证书图片或报价。采购前应把产品规格、目标市场要求、样品确认、批次检验、出货放行和随货文件纳入一套可追溯的采购流程&#xff0c;确保经检验批准的产品与实际装运批次…

作者头像 李华