【一文了解】八大排序-冒泡排序、选择排序

目录

复杂度和稳定性

1.冒泡排序

1.1.核心逻辑

1.2.适用场景

1.3.复杂度

1.4.稳定性

1.5.举例

1)核心动作拆解

2)实例分析(3,1,4,2升序为例)

1.6.代码实现

2.选择排序

2.1.核心逻辑

2.2.适用场景

2.3.复杂度

2.4.稳定性

2.5.举例

1)核心动作拆解

2)实例分析(3,1,4,2升序为例)

2.6.代码实现

3.测试

3.1.完整代码

3.2.测试结果

总结


本篇文章来分享一下八大排序中的冒泡排序与选择排序。

复杂度和稳定性

首先来了解一下算法中复杂度和稳定性的概念,以便更好地了解排序,其中复杂度包括时间复杂度和空间复杂度。

1)时间复杂度:基于"操作次数的数学建模"

时间复杂度描述算法执行时间随数据规模n增长的趋势(取最高阶项,忽略常数和低阶项),推导核心是统计"基本操作"的执行次数(如比较、交换、移动元素)。

2)空间复杂度:额外存储空间的需求

空间复杂度衡量算法所需的额外存储空间(不包括输入数据)

3)稳定性:相同元素的相对位置是否改变

稳定性由算法的"交换/移动逻辑"决定。相同元素的相对位置不改变是稳定排序,如冒泡排序仅相邻交换,相等不交换,而相同元素的相对位置改变是不稳定排序,如选择排序跨位置交换最值,可能打乱相同元素顺序。

1.冒泡排序

1.1.核心逻辑

重复遍历数组,相邻元素两两比较,逆序则交换;每轮将"最大/最小"元素"冒泡"到对应位置

1.2.适用场景

小规模数据(n<1000)、几乎有序的数据(可触发提前退出优化)

1.3.复杂度

1)时间复杂度:O (n²)(最坏/平均),O(n)(最优)

●最坏情况(完全逆序):逆序对数量为n(n-1)/2内层循环每轮n-i-1次比较(共(n-1)+(n-2)+...+1=n(n-1)/2次,最高阶 n²),需 n-1 轮,每轮 n-i-1 次比较,总比较次数 O (n²)。

●最优情况(完全有序):逆序对数量为0,加标志位后仅需1轮n-1次比较,总比较次数O (n)。

●平均情况(随机数据):逆序对数量的平均值约为n(n-1)/4(仍为 n² 阶)。无论数据如何随机,每轮都需要比较"未排序部分的相邻元素",平均需要n/2轮,每轮平均n/2次比较,总比较次数约为n/2*n/2= n²/4,仍属于O (n²)阶(忽略常数系数)。

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

1.4.稳定性

稳定,即相同元素不改变相对位置

1.5.举例

1)核心动作拆解

相邻比较:每次只比较数组中相邻的两个元素(如arrj和arrj+1)。

逆序交换:若相邻元素反序(升序时前大后小,降序时前小后大),则交换两者位置。

逐轮冒泡:每完成一轮遍历,当前未排序部分的"最大元素"(升序)或"最小元素"(降序)会像气泡一样"浮"到数组的末尾(已排序部分)。

2)实例分析(3,1,4,2升序为例)

第1轮:比较(3,1)→交换→1,3,4,2;比较(3,4)→不换;比较(4,2)→交换→1,3,2,4。本轮结束,最大元素4冒泡到末尾。

第2轮:比较(1,3)→不换;比较(3,2)→交换→1,2,3,4。本轮结束,次大元素3冒泡到倒数第2位。

第3轮:无交换(数组已序),提前退出。

1.6.代码实现

cs 复制代码
/// <summary>
/// 冒泡排序
/// </summary>
/// <param name="arr">待排序数组(会直接修改原数组)</param>
/// <param name="isAscending">排序方向:true=升序(从小到大),false=降序(从大到小)</param>
public static void BubbleSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
{
    //校验输入:空数组或长度为1,无需排序
    if (arr == null || arr.Length <= 1) return;

    int n = arr.Length;
    //外层循环:控制排序轮次(最多n-1轮,因每轮确定1个元素位置)
    for (int i = 0; i < n - 1; i++)
    {
        bool hasSwapped = false;//标记本轮是否发生交换(优化:无交换则数组已序,提前退出)

        //内层循环:遍历未排序部分,相邻元素比较交换
        //每轮后,末尾i个元素已有序,无需再遍历
        for (int j = 0; j < n - i - 1; j++)
        {
            //比较相邻元素:根据排序方向判断是否逆序
            int compareResult = arr[j].CompareTo(arr[j + 1]);
            bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);

            if (needSwap)
            {
                //交换元素(C# 7.0+ 解构赋值语法,简洁高效)
                (arr[j], arr[j + 1]) = (arr[j + 1], arr[j]);
                hasSwapped = true;
            }
        }

        //本轮无交换,说明数组已完全有序,直接退出循环(关键优化)
        if (!hasSwapped) break;
    }
}

2.选择排序

2.1.核心逻辑

每次从"未排序部分"找到"最小/最大"元素,放到"已序部分"的"末尾",逐步扩大有序范围。

2.2.适用场景

小规模数据、对交换次数敏感的场景(如硬件存储介质读写成本高时)

2.3.复杂度

1)时间复杂度:O (n²)(最坏/平均/最优)

●最坏情况下(逆序):每个元素需移动i次(i从1到n-1),总移动次数1+2+...+(n-1)=n(n-1)/2,时间复杂度O(n²)。

●平均情况(随机数据):时间复杂度O(n²)

●最优情况(完全有序):比较次数仍为n(n-1)/2,仅移动次数为0(无需交换),时间复杂度O(n²)。

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

2.4.稳定性

不稳定(如2,2,1,第一个2会与1交换,破坏相同元素相对位置)

2.5.举例

1)核心动作拆解

:每轮只找一次最值(内层循环仅比较,不交换)。

:每轮只交换一次(最值与未排序部分的第一个元素)。

:已排序部分从左到右逐步扩大,最终覆盖整个数组。

2)实例分析(3,1,4,2升序为例)

初始:未排序3,1,4,2,已排序\[\]。

第1轮:找到最小值1,与未排序第一个元素3交换→数组变为1,3,4,2。已排序1,未排序3,4,2

第2轮:从3,4,2找最小值2,与未排序第一个元素3交换→数组变为1,2,4,3。已排序1,2,未排序4,3

第3轮:从4,3找最小值3,与未排序第一个元素4交换→数组变为1,2,3,4。已排序1,2,3,未排序4(自然有序)。

2.6.代码实现

cs 复制代码
/// <summary>
/// 选择排序
/// </summary>
/// <param name="arr">待排序数组(会直接修改原数组)</param>
/// <param name="isAscending">排序方向:true=升序,false=降序</param>
public static void SelectionSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
{
    if (arr == null || arr.Length <= 1) return;

    int n = arr.Length;
    //外层循环:控制有序部分范围(i为未排序部分的起始索引)
    //有序部分最开始为空
    for (int i = 0; i < n - 1; i++)
    {
        //步骤1:找到未排序部分的" 目标元素(最值) "索引(升序找最小,降序找最大)
        int targetIndex = i;
        for (int j = i + 1; j < n; j++)
        {
            int compareResult = arr[j].CompareTo(arr[targetIndex]);
            bool needSwap = isAscending ? (compareResult < 0) : (compareResult > 0);

            if (needSwap)
            {
                targetIndex = j;//更新 目标元素(最值) 索引
            }
        }

        //步骤2:将 目标元素(最值) 与未排序部分第一个元素交换(若 目标元素(最值) 就是第一个,无需交换)
        if (targetIndex != i)
        {
            (arr[i], arr[targetIndex]) = (arr[targetIndex], arr[i]);
        }
    }
}

3.测试

3.1.完整代码

cs 复制代码
using System;
using System.Collections.Generic;
using UnityEngine;

public class SortTest : MonoBehaviour
{
    private void Start()
    {
        int[] arr1 = GenerateArray(10);
        PrintArray(arr1);
        BubbleSort(arr1);
        PrintArray(arr1, "冒泡排序后,数组内容");

        int[] arr2 = GenerateArray(10);
        PrintArray(arr2);
        SelectionSort(arr2);
        PrintArray(arr2, "选择排序后,数组内容:");
    }

    private int[] GenerateArray(int count, int minValue = 0, int maxValue = 100)
    {
        List<int> arr = new List<int>();
        for (int i = 0; i < count; i++)
        {
            int value = UnityEngine.Random.Range(minValue, maxValue);
            arr.Add(value);
        }
        return arr.ToArray();
    }

    /// <summary>
    /// 冒泡排序
    /// </summary>
    /// <param name="arr">待排序数组(会直接修改原数组)</param>
    /// <param name="isAscending">排序方向:true=升序(从小到大),false=降序(从大到小)</param>
    public static void BubbleSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
    {
        //校验输入:空数组或长度为1,无需排序
        if (arr == null || arr.Length <= 1) return;

        int n = arr.Length;
        //外层循环:控制排序轮次(最多n-1轮,因每轮确定1个元素位置)
        for (int i = 0; i < n - 1; i++)
        {
            bool hasSwapped = false;//标记本轮是否发生交换(优化:无交换则数组已序,提前退出)

            //内层循环:遍历未排序部分,相邻元素比较交换
            //每轮后,末尾i个元素已有序,无需再遍历
            for (int j = 0; j < n - i - 1; j++)
            {
                //比较相邻元素:根据排序方向判断是否逆序
                int compareResult = arr[j].CompareTo(arr[j + 1]);
                bool needSwap = isAscending ? (compareResult > 0) : (compareResult < 0);

                if (needSwap)
                {
                    //交换元素(C# 7.0+ 解构赋值语法,简洁高效)
                    (arr[j], arr[j + 1]) = (arr[j + 1], arr[j]);
                    hasSwapped = true;
                }
            }

            //本轮无交换,说明数组已完全有序,直接退出循环(关键优化)
            if (!hasSwapped) break;
        }
    }

    /// <summary>
    /// 选择排序
    /// </summary>
    /// <param name="arr">待排序数组(会直接修改原数组)</param>
    /// <param name="isAscending">排序方向:true=升序,false=降序</param>
    public static void SelectionSort<T>(T[] arr, bool isAscending = true) where T : IComparable<T>
    {
        if (arr == null || arr.Length <= 1) return;

        int n = arr.Length;
        //外层循环:控制有序部分范围(i为未排序部分的起始索引)
        //有序部分最开始为空
        for (int i = 0; i < n - 1; i++)
        {
            //步骤1:找到未排序部分的" 目标元素(最值) "索引(升序找最小,降序找最大)
            int targetIndex = i;
            for (int j = i + 1; j < n; j++)
            {
                int compareResult = arr[j].CompareTo(arr[targetIndex]);
                bool needSwap = isAscending ? (compareResult < 0) : (compareResult > 0);

                if (needSwap)
                {
                    targetIndex = j;//更新 目标元素(最值) 索引
                }
            }

            //步骤2:将 目标元素(最值) 与未排序部分第一个元素交换(若 目标元素(最值) 就是第一个,无需交换)
            if (targetIndex != i)
            {
                (arr[i], arr[targetIndex]) = (arr[targetIndex], arr[i]);
            }
        }
    }
    /// <summary>
    /// 打印数组内容
    /// </summary>
    /// <param name="arr"></param>
    public static void PrintArray<T>(T[] arr, string prefix = "数组内容:") where T : IComparable<T>
    {
        if (arr == null)
        {
            Debug.Log($"{prefix} null");
            return;
        }
        Debug.Log($"{prefix} [{string.Join(", ", arr)}]");
    }
}

3.2.测试结果

总结

冒泡排序是 "多交换、少比较(最优情况)、稳定",选择排序是 "少交换、多比较(固定)、不稳定"。两者均为基础的原地比较类排序算法,核心逻辑简单但时间复杂度较高(平均/最坏均为O (n²)),适合小规模数据排序。

|----------|----------------------------------------------------------|-------------------------------------------------|
| 对比维度 | 冒泡排序(BubbleSort) | 选择排序(SelectionSort) |
| 核心逻辑 | 重复遍历数组,相邻元素两两比较,逆序则交换;每轮将"最大/最小"元素"冒泡"到对应位置;可加"无交换标志"优化。 | 每次从"未排序部分"找到"最小/最大"元素,放到"已序部分"的"末尾",逐步扩大有序范围。 |
| 时间复杂度 | 最坏/平均:O(n²)(需完整比较+交换);最优:O(n)(完全有序,加标志位后1轮比较无交换)。 | 最坏/平均/最优:O(n²),因为找最值必须遍历完未排序部分,比较次数固定为n(n-1)/2。 |
| 空间复杂度 | O(1)(原地排序,仅需临时变量存储交换元素) | O(1)(原地排序,仅需临时变量存储最值索引和交换元素) |
| 稳定性 | 稳定(相邻元素交换时,相同值不交换,保留原相对顺序) | 不稳定(最值与首位元素交换时,可能打乱相同值的相对顺序 |
| 适用场景 | 小规模数据(n<1000)、几乎有序的数据(可触发提前退出优化) | 小规模数据、对交换次数敏感的场景 |

好了,本次的分享到这里就结束啦,希望对你有所帮助~

相关推荐
Momo__6 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富6 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇6 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇6 小时前
React中的forwardRef
前端·react.js·面试
复杂网络6 小时前
Stable Diffusion 视觉大模型微调技术深度调研
算法
复杂网络6 小时前
基于 Stable Diffusion 架构的视觉大模型代表性工作与原理深度解析
算法
槑有老呆6 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
MrZhao4006 小时前
Agent Loop 如何用 Hook 扩展:权限、日志与工具拦截
算法
风骏时光牛马6 小时前
Verilog开发常见问题汇总解析
前端
子兮曰6 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端