冒泡排序总结

一、冒泡排序的核心原理

冒泡排序的名字源于其排序过程像 “水中气泡上浮”——每一轮遍历中,最大(或最小)的元素会像气泡一样,通过相邻元素的比较与交换,逐步 “浮” 到数组的末尾(或开头)

以 “升序排序” 为例,核心逻辑可概括为:

  1. 从数组的第一个元素开始,依次比较相邻的两个元素(如第 1 个和第 2 个、第 2 个和第 3 个……);
  2. 若前一个元素大于后一个元素,就交换它们的位置(确保较大的元素向后移动);
  3. 完成一轮遍历后,数组中最大的元素会 “浮” 到数组的最后一位(无需再参与后续比较);
  4. 排除已 “归位” 的元素,对剩余未排序部分重复步骤 1-3,直到所有元素都归位。

1.1 直观示例:数组 [3,1,4,1,5] 的冒泡过程

为了更清晰理解,我们以升序排序数组[3,1,4,1,5]为例,拆解每一轮的操作(括号内为已归位的元素):

轮次初始数组比较交换过程本轮结果归位元素
1[3,1,4,1,5]3>1→交换→[1,3,4,1,5];3<4→不交换;4>1→交换→[1,3,1,4,5];4<5→不交换[1,3,1,4,5]5
2[1,3,1,4,(5)]1<3→不交换;3>1→交换→[1,1,3,4,(5)];3<4→不交换[1,1,3,4,(5)]4
3[1,1,3,(4,5)]1=1→不交换;1<3→不交换[1,1,3,(4,5)]3
4[1,1,(3,4,5)]1=1→不交换[1,1,(3,4,5)]无(已排序)

1.2 时间复杂度与空间复杂度

冒泡排序是原地排序算法(无需额外数组存储数据),也是稳定排序算法(相等元素的相对位置不会改变),其性能分析如下:

场景时间复杂度空间复杂度说明
最坏情况O(n²)O(1)数组完全逆序(如 [5,4,3,2,1]),每一轮需交换 n-i 次(i 为轮次),总操作数为 n (n-1)/2
最好情况O(n)O(1)数组已完全有序(如 [1,2,3,4,5]),若加 “提前终止” 优化,一轮遍历后即可结束
平均情况O(n²)O(1)随机数组,平均需遍历 n/2 轮,每轮需交换 n/2 次,总操作数接近 n²/4

为什么时间复杂度是 O (n²)?冒泡排序的核心是 “双重循环”:外层循环控制 “轮次”(最多 n-1 轮),内层循环控制 “每轮的比较交换”(最多 n-i 次),总操作次数与 n² 成正比,因此时间复杂度为平方级。

二、基础实现

原始版冒泡排序严格遵循 “每轮浮起一个最大元素” 的逻辑,代码简洁,适合入门学习。

2.1 代码实现(升序排序)

def bubble_sort_basic(nums):
    """
    原始版冒泡排序(升序)
    :param nums: 待排序数组(列表)
    :return: 排序后的数组
    """
    n = len(nums)
    # 外层循环:控制轮次(最多n-1轮,因为最后1个元素无需比较)
    for i in range(n - 1):
        # 内层循环:控制每轮比较(每轮排除已归位的i个元素,比较到n-1-i为止)
        for j in range(n - 1 - i):
            # 前一个元素大于后一个元素,交换位置
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
    return nums

# 测试案例
if __name__ == "__main__":
    test_nums1 = [3, 1, 4, 1, 5, 9, 2, 6]  # 随机数组
    test_nums2 = [5, 4, 3, 2, 1]            # 逆序数组
    test_nums3 = [1, 2, 3, 4, 5]            # 有序数组
    
    print("随机数组排序结果:", bubble_sort_basic(test_nums1))  # [1, 1, 2, 3, 4, 5, 6, 9]
    print("逆序数组排序结果:", bubble_sort_basic(test_nums2))  # [1, 2, 3, 4, 5]
    print("有序数组排序结果:", bubble_sort_basic(test_nums3))  # [1, 2, 3, 4, 5]

2.2 代码解析

  • 外层循环for i in range(n-1),最多执行 n-1 轮(例如 n=5 的数组,4 轮即可完成排序);
  • 内层循环for j in range(n-1-i),每轮比上一轮少比较 i 次(因为已有 i 个最大元素归位到末尾);
  • 交换逻辑if nums[j] > nums[j+1],仅当相邻元素逆序时才交换,确保较大元素向后移动。

三、冒泡排序的三重优化

原始版冒泡排序存在明显缺陷:即使数组已提前有序,仍会执行完所有轮次的循环,造成性能浪费。通过以下三重优化,可大幅提升其在特定场景下的效率,甚至让 “最好情况” 时间复杂度降至 O (n)。

3.1 优化 1:提前终止(解决 “有序数组” 浪费问题)

问题根源:原始版冒泡排序对已完全有序的数组(如 [1,2,3,4,5]),仍会执行 n-1 轮循环,而实际上 1 轮遍历后即可确定数组有序,无需继续。优化方案:增加一个 “是否交换” 的标志位swapped,每轮遍历前初始化swapped=False;若本轮发生过交换,则设swapped=True;若本轮未发生任何交换(swapped=False),说明数组已完全有序,直接终止循环。

优化后代码
def bubble_sort_optim1(nums):
    """
    冒泡排序优化1:提前终止(检测到有序则退出)
    """
    n = len(nums)
    for i in range(n - 1):
        swapped = False  # 标志位:本轮是否发生交换
        for j in range(n - 1 - i):
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                swapped = True  # 发生交换,设为True
        # 若本轮无交换,说明数组已有序,直接退出
        if not swapped:
            break
    return nums

# 测试有序数组(优化效果最明显)
if __name__ == "__main__":
    sorted_nums = [1, 2, 3, 4, 5, 6]
    bubble_sort_optim1(sorted_nums)
    print("有序数组排序轮次:", i + 1)  # 输出1(仅执行1轮)
优化效果
  • 最好情况(完全有序数组):仅需 1 轮遍历,时间复杂度从 O (n²) 降至O(n)
  • 最坏情况(完全逆序数组):无额外开销,仍为 O (n²);
  • 平均情况:减少不必要的轮次,性能小幅提升。

3.2 优化 2:记录最后一次交换位置(减少无效比较)

问题根源:即使数组未完全有序,每轮遍历中 “最后一次交换的位置” 之后的元素也已完全有序(因为这些元素未参与交换,说明它们比前面的元素都小)。原始版仍会比较到n-1-i,存在无效比较。优化方案:记录每轮 “最后一次交换的位置”last_swap_idx,下一轮遍历仅需比较到last_swap_idx(而非n-1-i),减少后续的无效比较。

优化后代码
def bubble_sort_optim2(nums):
    """
    冒泡排序优化2:记录最后一次交换位置(减少无效比较)
    """
    n = len(nums)
    last_swap_idx = n - 1  # 初始化为数组末尾(表示需比较到最后一个元素)
    for i in range(n - 1):
        swapped = False
        current_last_swap = 0  # 记录本轮最后一次交换的位置
        for j in range(last_swap_idx):  # 仅比较到上一轮最后一次交换的位置
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                swapped = True
                current_last_swap = j  # 更新本轮最后一次交换的位置
        # 更新下一轮的比较边界
        last_swap_idx = current_last_swap
        # 提前终止(无交换则有序)
        if not swapped:
            break
    return nums

# 测试案例:数组尾部已有部分有序
if __name__ == "__main__":
    test_nums = [3, 1, 4, 2, 5, 6, 7]  # 尾部[5,6,7]已有序
    bubble_sort_optim2(test_nums)
    print("优化后排序结果:", test_nums)  # [1, 2, 3, 4, 5, 6, 7]
优化效果
  • 针对 “局部有序” 的数组(如尾部有序),每轮比较次数从n-1-i减少到last_swap_idx,无效比较大幅减少;
  • 例如数组[3,1,4,2,5,6,7],第一轮最后一次交换位置是j=3(交换 2 和 4),下一轮仅需比较到j=3,无需再比较j=4,5(尾部已有序)。

3.3 优化 3:双向冒泡(解决 “小元素沉底慢” 问题)

问题根源:原始冒泡排序每轮仅能将 “最大元素” 浮到末尾,但 “最小元素” 需要多轮才能沉到开头(如数组[4,3,2,1,0],最小元素 0 需 5 轮才能到开头),效率低下。优化方案双向冒泡(也叫 “鸡尾酒排序”)—— 每轮分为 “正向遍历” 和 “反向遍历”:

  • 正向遍历:将最大元素浮到末尾;
  • 反向遍历:将最小元素沉到开头;
  • 交替进行,直到数组有序。
优化后代码
def bubble_sort_optim3(nums):
    """
    冒泡排序优化3:双向冒泡(鸡尾酒排序)
    """
    n = len(nums)
    left = 0  # 左边界(反向遍历的起点)
    right = n - 1  # 右边界(正向遍历的终点)
    while left < right:
        # 1. 正向遍历:将最大元素浮到right位置
        swapped = False
        for j in range(left, right):
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                swapped = True
        right -= 1  # 右边界左移(最大元素已归位)
        if not swapped:
            break
        
        # 2. 反向遍历:将最小元素沉到left位置
        swapped = False
        for j in range(right, left, -1):
            if nums[j] < nums[j - 1]:
                nums[j], nums[j - 1] = nums[j - 1], nums[j]
                swapped = True
        left += 1  # 左边界右移(最小元素已归位)
        if not swapped:
            break
    return nums

# 测试案例:最小元素在末尾(优化效果最明显)
if __name__ == "__main__":
    test_nums = [4, 3, 2, 1, 0]  # 最小元素0在末尾
    bubble_sort_optim3(test_nums)
    print("双向冒泡排序结果:", test_nums)  # [0, 1, 2, 3, 4]
优化效果
  • 针对 “最小元素在末尾” 或 “两端无序、中间有序” 的数组(如[2,3,4,5,1]),效率提升显著;
  • 例如数组[4,3,2,1,0],原始冒泡需 5 轮,双向冒泡仅需 3 轮(正向 2 轮、反向 1 轮)。

四、冒泡排序的应用场景与局限性

4.1 适用场景

冒泡排序虽然时间复杂度是 O (n²),但在某些场景下仍有其价值:

  1. 小规模数据排序:当数据量 n≤100 时,O (n²) 的性能开销可忽略,且冒泡排序代码简单、易于实现和调试;
  2. 近乎有序的数据排序:若数据已接近有序(如仅少数元素逆序),加 “提前终止” 优化后,冒泡排序可接近 O (n) 的效率,甚至比快速排序(有递归开销)更快;
  3. 教学与面试场景:作为排序算法的入门案例,冒泡排序能帮助理解 “比较 - 交换” 的核心逻辑,也是面试中常考的 “手写排序” 题目(尤其是优化方案);
  4. 需要稳定排序且空间有限的场景:冒泡排序是稳定排序,且原地排序(空间 O (1)),适合内存受限的嵌入式系统或简单场景。

4.2 局限性

  1. 大规模数据效率低:当 n>1000 时,O (n²) 的时间复杂度会导致排序耗时急剧增加(如 n=10000 时,需约 1 亿次操作),远不如 O (n log n) 的快速排序、归并排序;
  2. 比较交换次数多:即使是优化版,每轮仍需多次相邻元素比较,相比 “选择排序”(每轮仅 1 次交换),交换操作的开销更大(交换需 3 次赋值,比较仅 1 次判断)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值