前言冒泡排序bubble sort是最早接触的排序算法之一反复比较相邻的两个元素逆序就交换每一轮把一个极值「冒」到区间末尾。它的最坏和平均时间复杂度都是 O(n²)实际项目里几乎不会用它但它非常适合用来说明「有序区间的扩张」「循环不变式」「提前退出」这些概念也经常作为优化题出现。常见的误解有两个。一是以为加了标志位之后算法就「变快了一个数量级」——标志位只改善了最好情况原本就有序时从 O(n²) 降到 O(n)最坏和平均仍是 O(n²)。二是以为「优化过的冒泡」能和插入排序、快速排序相提并论——在随机数据上冒泡的交换次数远多于插入排序常数也更大优化改变不了这个量级。本文从最基本的双层循环讲起依次加入三种常见优化提前退出标志、记录最后一次交换位置、双向冒泡鸡尾酒排序并说明每种优化改善的是哪一类输入。最后给出复杂度对照表和实现时最容易写错的边界。一、基本冒泡排序算法维护一个不变式第 i 轮结束时区间末尾的 i 个元素已经是全局最大的 i 个且有序。每一轮从下标 0 开始比较相邻元素逆序则交换直到本轮区间的末尾。// 适用于 C17#include cstddef#include utility#include vectorvoid bubbleSort(std::vectorint a) {const std::size_t n a.size();for (std::size_t i 0; i 1 n; i) {for (std::size_t j 0; j 1 n - i; j) {if (a[j] a[j 1]) {std::swap(a[j], a[j 1]);}}}}外层循环执行 n-1 轮就够了每轮至少把一个元素放到最终位置n-1 轮之后剩下的那个自然也在正确位置。内层上界是n - i因为末尾 i 个元素已经有序不需要再比较。关于下标类型这里用std::size_t循环条件写成j 1 n - i而不是j n - i - 1。后者在n - i为 0 时会发生无符号下溢变成一个极大的数循环失控。写成j 1 n - i时j 1最多等于n - i不会溢出j本身远小于SIZE_MAX条件自然为假。这是无符号下标倒序/边界循环最常见的防御写法。正确性可以用不变式说明内层循环每比较一次较大者向右移动一位因此一轮结束后本轮区间的最大值一定停在区间末尾。下一轮把它排除在外重复下去整个数组有序。基本版本的比较次数固定为 n(n-1)/2与输入无关。即使数组已经有序它仍然会把所有比较做完这是后面优化的出发点。二、优化一提前退出标志如果某一轮里一次交换都没有发生说明任意相邻元素都满足顺序整个数组已经有序可以立即结束。用一个布尔标志记录本轮是否交换过// 适用于 C17void bubbleSortFlag(std::vectorint a) {const std::size_t n a.size();for (std::size_t i 0; i 1 n; i) {bool swapped false;for (std::size_t j 0; j 1 n - i; j) {if (a[j] a[j 1]) {std::swap(a[j], a[j 1]);swapped true;}}if (!swapped) {break;}}}这个优化让最好情况输入已经升序只做一轮 n-1 次比较复杂度 O(n)。对「几乎有序、只有少量逆序对」的数据也有帮助因为一旦某一轮没有交换就提前结束。但要注意最坏情况完全逆序仍然要做满 n-1 轮比较次数不变交换次数是 n(n-1)/2。标志位必须每轮重新置为false不能放在外层循环外面只初始化一次否则第一轮之后永远为真提前退出失效或者反过来永远为假第一轮就退出。这是实现时最常见的错误之一。三、优化二记录最后一次交换的位置观察内层循环如果某一轮中从某个位置之后再也没有发生交换说明那段后缀已经有序下一轮不必再比较到末尾。记录本轮最后一次交换发生的下标lastSwap下一轮的内层上界就收缩到那里。// 适用于 C17void bubbleSortLastSwap(std::vectorint a) {std::size_t n a.size();while (n 1) {std::size_t lastSwap 0;for (std::size_t j 0; j 1 n; j) {if (a[j] a[j 1]) {std::swap(a[j], a[j 1]);lastSwap j 1; // 最后一次交换的右端点}}n lastSwap; // 之后的元素已经有序}}这里用n表示「还需要处理的区间长度」每轮结束后把它更新为lastSwap。如果整轮没有交换lastSwap保持 0n变成 0循环自然结束所以这个版本连标志位都不需要。正确性在于最后一次交换发生在lastSwap - 1与lastSwap之间说明从lastSwap到本轮末尾的相邻元素都满足顺序而本轮末尾又是当前最大值因此这段后缀整体有序可以永久排除。这个优化对「尾部已经有序、头部还有少量乱序」的数据效果明显但它不改变最坏复杂度完全逆序时每轮最后一次交换都在末尾n只减少 1比较次数仍是 O(n²)。四、优化三双向冒泡基本冒泡有一个弱点如果一个很小的元素位于数组末尾它每轮只能向左移动一位需要很多轮才能到位常被称为「乌龟」问题。双向冒泡鸡尾酒排序cocktail shaker sort在一轮里先从左到右把最大值送到右端再从右到左把最小值送到左端两个方向交替进行。// 适用于 C17void cocktailSort(std::vectorint a) {std::size_t begin 0;std::size_t end a.size();bool swapped true;while (swapped begin 1 end) {swapped false;for (std::size_t i begin; i 1 end; i) {if (a[i] a[i 1]) {std::swap(a[i], a[i 1]);swapped true;}}--end; // 右端已就位if (!swapped) {break;}swapped false;for (std::size_t i end - 1; i begin; --i) {if (a[i - 1] a[i]) {std::swap(a[i - 1], a[i]);swapped true;}}begin; // 左端已就位}}边界要仔细核对。初始end a.size()正向循环i从begin到end - 2把最大值送到end - 1随后--end把它排除。反向循环i从end - 1开始比较i - 1与i一直做到begin 1把最小值送到begin随后begin。外层条件begin 1 end保证区间里至少还有两个元素空数组和单元素数组直接不进循环。双向冒泡对「小元素在末尾」的输入改善明显最好情况仍是 O(n)最坏和平均仍是 O(n²)。它并没有改变量级只是把某些输入形态下的轮数减少了一半左右。实现时容易把--end和begin的位置放错导致跳过元素或重复处理建议先用小数组手工推演边界。五、复杂度与稳定性版本最好平均最坏额外空间稳定基本冒泡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(n)O(n²)O(n²)O(1)是稳定性来自交换条件a[j] a[j 1]只有严格逆序才交换相等元素不会被交换相对次序保持不变。如果把条件写成相等元素也会交换排序结果仍然正确但不再稳定。这一点在只关心数值时无所谓在按某个键排序、要求保持原有次序时就必须注意。交换次数方面冒泡排序的交换次数等于数组的逆序对数量这一点和插入排序相同但冒泡的比较次数固定为 O(n²)除非提前退出而插入排序在部分有序时比较次数接近 O(n)。所以即使都做了优化插入排序通常也更适合「基本有序」的数据。实际工程中应当直接用std::sort它的最坏复杂度是 O(N log N)手写冒泡主要用于教学或面试题。如果确实要在小数组上用手写排序插入排序的常数更小、代码同样简单是更现实的选择。冒泡排序的价值在于它清晰地展示了「每轮确定一个极值」和「提前终止」的思想。常见坑点1. 内层上界写错❌for (std::size_t j 0; j n - i - 1; j)当i n - 1时n - i - 1下溢。 ✅for (std::size_t j 0; j 1 n - i; j)避免无符号减法。2. 标志位不重置❌bool swapped false;写在外层循环之外第一轮之后状态残留。 ✅ 每轮进入内层前重新置false。3. 提前退出条件写反❌if (swapped) break;—— 只要发生过交换就退出排序不完整。 ✅if (!swapped) break;。4. 用比较破坏稳定性❌if (a[j] a[j 1]) std::swap(...)相等元素被交换。 ✅if (a[j] a[j 1])只在严格逆序时交换。5. 最后交换位置更新错❌ 记录lastSwap j左端点下一轮上界少算一位。 ✅ 记录lastSwap j 1它是「已有序后缀的起点」。6. 双向冒泡边界偏移❌ 正向结束后忘记--end或反向循环从end开始访问a[end]越界。 ✅ 正向把最大值送到end - 1后--end反向从end - 1开始比较i - 1与i。7. 认为优化后最坏也是 O(n)❌ 宣称「加了标志位冒泡排序平均 O(n)」。 ✅ 最好情况 O(n)平均与最坏仍是 O(n²)。8. 对空数组或单元素数组没验证❌ 外层直接写for (i 0; i n - 1; i)n 0时下溢。 ✅ 用i 1 n或先判断n 2直接返回。总结优化改善的输入不改善的部分提前退出标志已有序、几乎有序完全逆序仍 O(n²)记录最后交换位置尾部已有序最坏仍 O(n²)双向冒泡小元素集中在末尾最坏仍 O(n²)稳定比较严格大于保持相等元素次序不改变复杂度三种优化针对的是不同的输入形态共同点是都不改变 O(n²) 的最坏复杂度。理解它们的价值在于看清「哪一段已经有序」「什么时候可以停」这两个判断如何减少无用比较而不是指望手写冒泡在实际项目里替代标准库排序。真正需要排序时用std::sort需要稳定时用std::stable_sort手写冒泡留在教学和面试场景即可。
企业数字化 ERP 产品动态
相关推荐
可运行Java宿舍管理系统源码拆解:核心链路、部署排错与二次开发 简介:这套基于Java的学生宿舍管理系统源码包,面向计算机专业毕业设计、课程设计及Java Web入门进阶学习者,帮助读者快速搭建一个具备多角色权限的宿舍管理演示项目。系统前端采用Bootstrap构建管理界面,后端基于SpringBoot框架与M… · 2026/11/7 22:50:21
Android Studio 复刻微信界面:单Activity多Fragment架构与避坑指南 简介:这份资源面向具备一定 Android 基础、希望从零搭建仿微信界面的开发者与学习者,围绕 Android Studio 开发环境,系统演示一个微信风格 App 的界面实现路径。压缩包共 1041 个文件,约 24.25MB,以 flat、json、xml 布… · 2026/11/6 5:30:59
Android Studio仿微信界面实战:模块化拆解与交互实现 简介:这份资源面向具备一定 Android 基础、希望借助 Android Studio 实践界面开发的开发者,通过复刻微信主界面来串联布局设计、组件使用与视觉还原等技能点。内容围绕底部导航栏、聊天列表、个人信息页等典型模块展开,涉及 ConstraintLayout… · 2026/11/6 5:30:59
中级电工证能做电工吗?学历不高也能拿全国通用证书 中级电工证能做电工吗?学历不高也能拿全国通用证书 你是不是也担心学历不高报不了电工证?其实中级电工证全国通用,不管你是初中、高中还是职高毕业,只要符合报考条件,就能顺利拿到证。今天我就从一个过来人的角度,告诉你中级电工证到底能不能做电工,实… · 2026/11/8 4:35:23
张家港电工证培训学校多久拿证?别再被中介坑了 张家港电工证培训学校多久拿证?别再被中介坑了 你是不是也这样?想考个电工证,但不知道该去哪报名,怕被中介忽悠,又担心拿证时间太长,影响找工作?别急,今天就给你讲明白张家港电工证培训学校到底靠不靠谱,多久能拿到证,以及怎么避坑。… · 2026/11/8 4:35:23
福建电工证查成绩真实经历:电工证在手,才能找到好工作 福建电工证查成绩真实经历:电工证在手,才能找到好工作 现在找工作越来越难,尤其是特种作业岗位, 没有电工证根本进不了门 。很多人在找工作的时候被问到:“你有电工证吗?”如果你没证,直接被pass。我认识一个朋友,就是因为没电工证,面试都没进… · 2026/11/8 4:35:23
专升本需要电工证吗 良心建议帮你避开实操考试踩坑 专升本需要电工证吗 良心建议帮你避开实操考试踩坑 实操考试心里没底怕挂科,这是很多准备考电工证的人共同的焦虑。尤其是像你这样正在专升本阶段的同学,一边要应对学业压力,一边又要为未来职业发展做准备,真的很难两全。今天就从郑州金水区一位公路工程… · 2026/11/8 4:35:23
高压电工证怎么抵个税?多久拿证?平顶山房建工程从业者避坑指南 高压电工证怎么抵个税?多久拿证?平顶山房建工程从业者避坑指南 不知道去哪报名怕被中介坑,这是很多想要考高压电工证的小伙伴最头疼的事。尤其是像我们平顶山房建工程从业者,干的都是技术活,对证照要求高,想考个高压电工证提升竞争力,但又怕被那些打着… · 2026/11/8 4:34:23
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 11:25:08
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 12:02:18
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/8 1:18:25