数据结构八大排序详解(一):四大简单排序

一、开篇前言:排序算法核心认知

1.1 排序算法的作用与应用场景

在数据结构与算法体系中,排序是最基础、最高频的核心模块,几乎所有后端开发、算法刷题、数据分析场景都离不开排序。简单来说,排序就是将一组无序的数据序列,按照从小到大(升序) 或**从大到小(降序)**的规则,重新整理为有序序列的过程。

日常开发中随处可见排序的应用:电商商品按价格、销量排序,通讯录按姓名排序,后台数据按创建时间排序,刷题时数组预处理有序结构、二分查找前置排序等。掌握排序算法,不仅是应对面试的必备技能,更是理解算法时间、空间优化思维的关键入门。

1.2 八大排序算法整体分类

经典八大内部排序算法,根据算法复杂度、实现难度分为简单排序高效排序两大类,也是行业通用的分类方式:

  • 简单排序(O(n²)):冒泡排序、选择排序、插入排序、希尔排序(本文详解)

  • 高效排序(O(nlogn)):快速排序、归并排序、堆排序、基数排序(下期更新)

其中前四种逻辑简单、容易理解、代码简洁,但数据量大时性能较差;后四种逻辑相对复杂,但时间复杂度更优,是工业级开发的主流排序算法。

1.3 算法衡量三大核心指标

评判一个排序算法的优劣,不只是看代码长短,核心看三大维度,也是面试必考知识点:

  • 时间复杂度:衡量算法执行的时间开销,随数据量n增长的变化趋势,分为最好、最坏、平均时间复杂度

  • 空间复杂度:衡量算法执行过程中,额外占用的内存空间大小

  • 稳定性若两个相等的元素,排序后相对位置不发生改变,则为稳定排序,反之不稳定

1.4 本文讲解范围

本文将零基础详解冒泡排序、选择排序、插入排序、希尔排序四种简单排序算法。全程摒弃晦涩公式堆砌,采用「通俗原理+流程拆解+完整代码+优缺点分析+场景总结」的保姆级讲解方式,新手也能完全看懂,同时适配面试、笔试、课程学习需求。


二、排序基础概念名词解析

2.1 稳定排序与不稳定排序

稳定排序数组中值相等的元素,排序前后相对顺序保持不变。

举例:原数组 [2,3`,1,3],稳定排序后一定是 [1,2,3`,3],两个3的顺序不会颠倒。在业务场景中,需要保留原始排序优先级时,必须使用稳定排序(如分数相同保留原排名)。

不稳定排序数组中值相等的元素,排序后相对顺序可能发生改变。不影响常规数值排序,但特殊业务场景会出现数据错乱。

分辨一个排序算法是否是稳定就看是否有跨位置交换元素,若有,则为不稳定排序;若无,则为稳定排序。

2.2 原地排序与非原地排序

原地排序 :算法执行过程中,仅使用常数级额外空间(O(1)),直接在原数组上完成排序,不额外开辟新数组,内存开销小。

非原地排序需要额外开辟和原数据量相关的内存空间(O(n)),借助辅助数组完成排序,内存开销更大。本文四种排序中,绝大多数为原地排序。

2.3 最好/最坏/平均时间复杂度

  • 最好时间复杂度:数据本身已有序时,算法的最优执行效率

  • 最坏时间复杂度:数据完全逆序、完全无序时,算法的最差执行效率

  • 平均时间复杂度:随机无序数据下,算法的常规执行效率,是评判算法性能的核心标准

平常的时间复杂度都是默认以最坏时间复杂度作为算法评判标准。


三、冒泡排序(Bubble Sort)

3.1 核心原理

冒泡排序是最经典、最入门的排序算法,原理通俗易懂:相邻两个元素两两比较,若前一个元素大于后一个元素(升序),则交换两者位置。每一轮遍历结束后,当前未排序区间的最大值都会像气泡一样,上浮到区间末尾,因此得名冒泡排序。

核心逻辑:逐轮遍历、相邻对比、大数后移、逐步确定有序尾部。

3.2 排序过程演示

以无序数组 [3,1,4,2,5] 升序排序为例:

第一轮遍历:对比3和1(交换)→[1,3,4,2,5],对比3和4(不换),对比4和2(交换)→[1,3,2,4,5],对比4和5(不换),本轮最大值5就位

第二轮遍历:仅遍历前4位,最终4就位,数组变为 [1,2,3,4,5]

后续遍历:依次确定3、2、1位置,最终实现全数组有序

3.3 基础版代码实现

cpp 复制代码
// 基础冒泡排序
void bubble_sort(int arr[], int n) {
    // 外层循环控制排序轮数
    for (int i = 0; i < n; i++) {
        // 内层循环两两对比,每轮减少一次遍历
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换两个元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

3.4 优化版冒泡排序

基础版冒泡排序存在明显缺陷:数组提前有序后,仍会执行剩余全部遍历,产生无效运算。我们可以加入有序标记,若某一轮遍历未发生任何交换,说明数组已有序,直接终止循环。

cpp 复制代码
// 优化版冒泡排序
void bubble_sort_optimize(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        int flag = 1; // 标记数组是否已有序,1=有序
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag= 0; // 发生交换,数组无序
            }
        }

        if (flag) 
            break; // 本轮无交换,直接退出
    }
}

3.5 算法特性与复杂度分析

  • 时间复杂度:最好O(n)(优化版,数组已有序)、最坏O(n²)、平均O(n²)

  • 空间复杂度:O(1),原地排序

  • 稳定性稳定排序(相等元素不交换位置,相对顺序不变)

  • 优点:逻辑简单、代码易懂、稳定、原地排序、无额外内存开销

  • 缺点:效率极低,存在大量无效对比,数据量大时性能极差

3.6 适用场景

仅适用于数据量极小、数据接近有序、对排序效率无要求的场景,工业级开发几乎不使用,主要用于算法入门学习。


四、选择排序(Selection Sort)

4.1 核心原理

选择排序核心思想:将数组分为有序区和无序区,每一轮遍历无序区,找出最小值(升序),与无序区首个元素交换位置,归入有序区。重复该操作,直至整个数组全部有序。

简单概括:每轮选最值,固定到数组前端,逐步扩大有序区。

4.2 排序过程演示

以无序数组 [3,1,4,2,5] 升序排序为例:

第一轮:无序区全部元素,最小值为1,与首位3交换 → [1,3,4,2,5],首位有序

第二轮:无序区 [3,4,2,5],最小值为2,与3交换 → [1,2,4,3,5],前两位有序

第三轮:无序区 [4,3,5],最小值为3,与4交换 → [1,2,3,4,5]

后续遍历仅确认有序,最终完成排序

4.3 完整代码实现

cpp 复制代码
// 选择排序
void selection_sort(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        int min_index = i; // 记录最小值下标
        // 遍历无序区,找到最小值
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[min_index]) {
                min_index = j;
            }
        }
        // 交换:将最小值放到无序区开头
        int temp = arr[i];
        arr[i] = arr[min_index];
        arr[min_index] = temp;
    }
}

4.4 核心特性解析(重点:不稳定原因)

选择排序是不稳定排序,这是面试高频考点。

举例验证:数组 [2`,2,1],第一轮遍历最小值为1,与首位2交换,排序后变为 [1,2,2`]。原本两个2的相对顺序被打乱,因此选择排序不具备稳定性。其本质原因是:排序过程中会跨位置交换元素,容易打乱相等元素的相对位置。

4.5 复杂度与优缺点

  • 时间复杂度:最好、最坏、平均均为O(n²)(无论数据是否有序,都需要完整遍历找最值,无优化空间)

  • 空间复杂度:O(1),原地排序

  • 优点:交换次数极少(仅每轮一次),对比冒泡排序,大幅减少交换操作,小数据量略快于冒泡

  • 缺点:全程固定O(n²)复杂度,无最优情况;排序不稳定;大数据量性能依旧拉胯

4.6 冒泡排序 vs 选择排序 核心区别

  • 交换次数:冒泡频繁交换,选择仅每轮一次交换

  • 稳定性:冒泡稳定,选择不稳定

  • 最优复杂度:优化后冒泡可达O(n),选择固定O(n²)

  • 执行效率:同等数据下,选择排序略优于冒泡排序


五、插入排序(Insertion Sort)

5.1 核心原理

插入排序是四大简单排序中综合性能最优、实际应用最多 的算法。核心逻辑:默认数组第一个元素为初始有序区,后续元素均为无序区;依次取出无序区首个元素,向前遍历有序区,找到合适位置插入,保证有序区始终有序,直至全数组排序完成。

5.2 通俗类比:打牌理牌逻辑

插入排序的逻辑和我们打牌理牌完全一致:手里的牌是有序区 ,桌上未拿的牌是无序区。每拿起一张新牌,就和手里已有的牌从后往前对比,找到合适的位置插入,最终手里的牌全部有序。

5.3 分步排序演示

以数组 [3,1,4,2,5] 升序排序为例:

初始有序区:[3],无序区:[1,4,2,5]

第一轮:取出1,向前对比3,1更小,插入3前方 → 有序区 [1,3]

第二轮:取出4,对比末尾3,4更大,直接后置 → 有序区 [1,3,4]

第三轮:取出2,向前依次对比4、3,均更大,插入1后方 → 有序区 [1,2,3,4]

第四轮:取出5,直接后置,全数组有序

5.4 基础插入排序代码实现

cpp 复制代码
// 插入排序
void insertion_sort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int temp = arr[i]; // 待插入元素
        int j = i - 1;     // 有序区最后一个元素下标
        
        // 向前遍历,大于temp的元素后移
        while (j >= 0 && arr[j] > temp) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = temp; // 插入到正确位置
    }
}

5.5 复杂度与特性分析

  • 时间复杂度:最好O(n)(数组已有序,仅遍历不移动)、最坏O(n²)、平均O(n²)

  • 空间复杂度:O(1),原地排序

  • 稳定性稳定排序(仅大于当前元素时后移,相等元素不移动,相对顺序不变)

  • 优点:数据接近有序时效率极高;稳定、原地排序;移动元素次数少,实际执行效率优于冒泡、选择排序

  • 缺点:完全无序大数据量场景下,平方级复杂度效率依旧偏低

5.6 简单排序中最优基础排序的原因

在三大基础简单排序(冒泡、选择、插入)中,插入排序实际性能最优,核心原因有两点:一是最好复杂度可达O(n),适配有序、接近有序数据;二是算法操作以「元素覆盖移动」替代「交换操作」,计算机执行效率更高,常数级开销远小于冒泡和选择排序。同时也是希尔排序、快速排序的底层优化基础。


六、希尔排序(Shell Sort)

6.1 核心定位

希尔排序,又称缩小增量排序 ,是直接插入排序的优化升级版。解决了插入排序的核心痛点:插入排序每次只能移动相邻元素,当最小元素在数组末尾时,需要大量移动操作,效率极低。希尔排序通过分组跳跃式插入,大幅优化这一问题。

6.2 核心原理

核心思想:先分组、后插入、缩增量、逐有序

  1. 设定一个增量间隔gap,将数组分为gap个分组;

  2. 对每个分组内部,单独执行直接插入排序;

  3. 不断缩小增量gap,重复分组排序操作;

  4. 当gap=1时,数组基本接近有序,最后一次完整插入排序即可完成排序。

简单来说:通过大步跳跃排序,让数组快速趋近有序,最后用插入排序收尾,规避插入排序无序数据低效的问题。

6.3 常用增量序列

  • 希尔增量(基础版):初始gap = n//2,每次迭代gap = gap / 2,直至gap=1。实现简单,是入门首选,但效率一般。

  • Hibbard增量(优化版):增量序列为 2ᵏ-1,效率优于希尔增量,时间复杂度更优,适合进阶场景。

6.4 排序流程演示

以数组 [8,7,6,5,4,3,2,1]、希尔增量为例:

第一轮gap=4:分为4组,组内插入排序,大数后置、小数前置,数组初步规整

第二轮gap=2:分为2组,继续组内插入排序,数组更趋近有序

第三轮gap=1:全局插入排序,此时数组基本有序,仅需少量移动即可完成排序

6.5 希尔排序代码实现(希尔增量)

cpp 复制代码
// 希尔排序
void shell_sort(int arr[], int n) {
    int gap = n / 2; // 初始化增量
    
    while (gap > 0) {
        // 对每个分组进行插入排序
        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;
        }
        gap /= 2; // 缩小增量
    }
}

6.6 复杂度与核心特性

  • 时间复杂度:取决于增量序列,希尔增量平均O(n^1.3),优化增量可达O(n^1.3);最好O(nlogn),最坏O(n²)

  • 空间复杂度:O(1),原地排序

  • 稳定性不稳定排序。原因是分组跳跃式交换元素,会打乱相等元素的相对位置

  • 优点:大幅优化插入排序的低效问题,无序数据下性能远优于基础三种简单排序,实现简单

  • 缺点:排序不稳定;增量序列无最优解,无法精准确定时间复杂度

6.7 相较于直接插入排序的提升点

直接插入排序仅能相邻移动元素,极端场景下移动次数极多;希尔排序通过跳跃式分组排序,让远距离元素快速归位,快速将无序数组调整为接近有序状态,最后仅需少量操作即可完成最终排序,极大降低了元素移动和对比的次数,整体性能大幅提升。


七、四大简单排序终极对比总结

7.1 核心参数对照表

排序算法 最好复杂度 最坏复杂度 平均复杂度 空间复杂度 稳定性 原地排序
冒泡排序 O(n) O(n²) O(n²) O(1) 稳定
选择排序 O(n²) O(n²) O(n²) O(1) 不稳定
插入排序 O(n) O(n²) O(n²) O(1) 稳定
希尔排序 O(nlogn) O(n²) O(nlogn) O(1) 不稳定

7.2 性能横向对比

常规无序数据下性能排序:希尔排序 > 插入排序 > 选择排序 > 冒泡排序

接近有序数据下性能排序:插入排序 ≈ 冒泡排序 > 希尔排序 > 选择排序

7.3 场景化算法选择建议

  • 数据量极小、要求排序稳定:优先选择插入排序

  • 数据接近有序:插入排序最优

  • 数据无序、中小体量、不要求稳定:优先希尔排序

  • 仅学习入门、逻辑演示:冒泡、选择排序

  • 业务需要保留相等元素顺序:禁用选择、希尔排序,选用冒泡、插入排序

7.4 面试高频考点梳理

  1. 四大简单排序的时间、空间复杂度及稳定性区分

空间复杂度:四种排序均为原地排序,空间复杂度均为 O(1)。

稳定性:冒泡排序、插入排序稳定,选择排序、希尔排序不稳定。

时间复杂度:最好复杂度:冒泡O(n)、选择O(n²)、插入O(n)、希尔O(nlogn);最坏与平均复杂度:冒泡、选择、插入均为O(n²),希尔排序平均O(n^1.3)、最坏O(n²)。

  1. 选择排序、希尔排序不稳定的核心原因

选择排序不稳定是因为存在跨位置直接交换元素 ,会打乱等值元素的相对顺序;希尔排序不稳定是因为采用分组跳跃式插入排序,不同分组的等值元素位置会被打乱,无法保留原始相对位置。

  1. 插入排序为什么是最优的基础简单排序

第一,插入排序最好时间复杂度可达 O(n),在数据接近有序时效率极高,适配性远优于冒泡、选择排序;

第二,核心操作是元素覆盖后移,无频繁交换,CPU执行常数开销更小;

第三,排序稳定、原地排序,实用性更强,同时是希尔排序、高级排序优化的底层基础。

  1. 希尔排序的优化思想、增量序列原理

优化思想是分组插入、由粗到细,解决直接插入排序只能相邻移动、极端场景效率低的问题;先通过大增量分组,让远距离元素快速归位,让数组快速趋近有序,最后增量为1时完成全局插入排序。

常用增量为基础希尔增量(n/2 递减),优化版为 Hibbard 增量(2^k-1)。

  1. 冒泡排序的优化方式(有序标记优化)

基础冒泡排序无论数据是否有序,都会走完所有循环,存在无效遍历。

优化方案是新增有序标记变量,每轮排序初始默认数组有序;若本轮发生元素交换,说明数组无序;若本轮无任何交换,证明数组已经完全有序,直接跳出循环,将最好时间复杂度优化至 O(n)。

  1. 四种排序的优缺点及适用场景对比

①冒泡:逻辑最简单、稳定,但效率最低,仅用于入门演示、极小数据有序场景;

②选择:交换次数少,但全程O(n²)、不稳定,几乎无实用场景;

③插入:稳定、有序数据极快、开销小,小数据、接近有序数据首选;

④希尔排序:简单排序中性能最高,中小体量无序数据适用,缺点是不稳定,不用于有序性要求高的业务。

八、结尾与下期预告

8.1 简单排序核心痛点总结

四大简单排序虽然逻辑简单、代码简洁、内存开销小,但核心短板十分明显:除希尔排序优化后略有提升,其余三种排序平均时间复杂度均为O(n²),数据量超过1000级别后性能急剧下降,无法满足工业级大数据量排序需求。因此实际开发中,复杂场景均使用时间复杂度更低的O(nlogn)高效排序算法。

8.2 下期预告

下一篇博客将详解八大排序剩余四大高效排序算法:快速排序、归并排序、堆排序、基数排序。重点拆解快排分治思想、归并排序递归逻辑、堆排序堆调整原理,对比四大高效排序的性能、稳定性、适用场景,同时梳理面试高频手撕代码、算法优化考点,敬请期待!

相关推荐
武子康3 分钟前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
Bruce_kaizy6 分钟前
c++ linux环境编程——文件io介绍以及open 、write 、read 三剑客深度详解
linux·服务器·c++·ubuntu·操作系统·文件io
花椒技术1 小时前
企业内部 Agent 落地复盘:Gateway、Skill 和二次确认如何串起受控业务执行
后端·agent·ai编程
PAK向日葵2 小时前
我用 C++ 写了一个轻量级 Python 虚拟机,刚刚开源
c++·python·开源
玖釉-2 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
我是一颗柠檬3 小时前
【MySQL全面教学】MySQL事务与ACID Day9(2026年)
数据库·后端·mysql
IT_陈寒3 小时前
React useEffect闭包陷阱差点把我整失业了
前端·人工智能·后端
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github