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

目录

复杂度和稳定性

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)核心动作拆解

相邻比较:每次只比较数组中相邻的两个元素(如arr[j]和arr[j+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)、几乎有序的数据(可触发提前退出优化) | 小规模数据、对交换次数敏感的场景 |

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

相关推荐
二倍速播放3 小时前
贪心算法 with Gemini
算法·贪心算法
非凡ghost3 小时前
bkViewer小巧精悍数码照片浏览器 中文绿色版
前端·javascript·后端
是苏浙3 小时前
零基础入门C语言之深入了解指针3
c语言·开发语言
陌路203 小时前
C17值类别概念
开发语言·c++
止观止3 小时前
JSON Web Token (JWT) 全面解析:原理、优缺点与最佳实践
jwt·1024程序员节·authz·authn
三小河3 小时前
JS 自定义事件:从 CustomEvent 到 dispatchEvent
前端
西洼工作室3 小时前
前端监控:错误捕获与行为日志全解析
前端·javascript
oliveira-time3 小时前
整数划分问题
算法
liu****3 小时前
笔试强训(十三)
开发语言·c++·算法·1024程序员节