冒泡排序及其优化

冒泡排序及其优化方式 本文介绍了冒泡排序算法的基本概念、实现方式及多种优化策略。冒泡排序通过相邻元素比较和交换将较大元素逐渐"浮"到数列末端,基础实现时间复杂度为O(n²)。文章详细展示了Java、Python和Golang的基础实现代码,并提出了三种优化方法:提前终止优化、记录最后交换位置优化和双向冒泡排序(鸡尾酒排序),分析了各种优化方式的时间复杂度表现。最后指出冒泡排序适合小规模或基本有序数据,在实际工程中多用于教学目的,建议优先使用语言内置的高效排序算法。 阅读详情

一 概述

冒泡排序算法是一种典型的比较交换排序算法,在一个序列中进行数据的两两比较,如何和目标序列的规则相反就进行位置交换,如果相符,则两者的位置不变。故而冒泡排序是一个稳定的排序算法。

冒泡排序中所产生的有序子序列一定时全局有序的(不同与直接插入排序),也就是说,有序子序列中的所有元素的关键字一定小于或者大于无序子序列中所有元素的关键字,这样每趟排序都会将一个元素放置到其最终的位置上。

二 冒泡排序的实现过程

  • 比较相邻的元素,如果第一个元素比第二个元素与目标队列元素的顺序规则相反,就进行交换。
  • 对于每一对相邻元素进行同样的比较,从开始第一对到结尾的最后一对,那么最后的元素应该会是序列中最大或最小的数。
  • 对序列中所有的元素重复以上的步骤,除最后一个元素除外;
  • 重复以上几个步骤,直至排序完成。

 

三 冒泡排序性能

算法最好时间最坏时间平均时间额外空间稳定性
冒泡O(n)O(n^2)O(n^2)O(1)稳定

  关于稳定性:因为在比较的过程中,当两个相同大小的元素相邻,只比较大或者小,所以相等的时候是不会交换位置的。而当两个相等元素离着比较远的时候,也只是会把他们交换到相邻的位置。他们的位置前后关系不会发生任何变化,所以算法是稳定的。

冒泡排序的空间效率:仅仅使用了常数个辅助单元,因而空间复杂度为O(1)。

冒泡排序的时间复杂度:当待排序的序列为有序序列时,仅仅只需要比较n-1次,移动次数为0,从而最好的情况下的时间复杂度为O(n);当初序列为逆序时,需要进行n-1趟排序,第i趟排序要进行n-i次关键字的比较,而且每次比较之后都必须移动元素3次来交换元素位置。

从而,冒泡排序的最坏情况下的时间复杂度为O(n^2),其平均时间复杂度也为O(n^2)。

稳定性:由于i>j时,且A[i] = A[j]时,不会发生交换,所以冒泡排序时一种稳定的排序算法。

四 冒泡排序常规版

  下面详细分析一下常规版的冒泡排序,整个算法流程其实就是上面实例所分析的过程。可以看出,我们在进行每一次大循环的时候,还要进行一个小循环来遍历相邻元素并交换。所以我们的代码中首先要有两层循环。

  外层循环:即主循环,需要辅助我们找到当前第 i 小的元素来让它归位。所以我们会一直遍历 n-2 次,这样可以保证前 n-1 个元素都在正确的位置上,那么最后一个也可以落在正确的位置上了。

  内层循环:即副循环,需要辅助我们进行相邻元素之间的比较和换位,把大的或者小的浮到水面上。所以我们会一直遍历 n-1-i 次这样可以保证没有归位的尽量归位,而归位的就不用再比较了。

  而上面的问题,出现的原因也来源于这两次无脑的循环,正是因为循环不顾一切的向下执行,所以会导致在一些特殊情况下得多余。例如 5,4,3,1,2 的情况下,常规版会进行四次循环,但实际上第一次就已经完成排序了。

/**
 * 冒泡排序常规版
 */
public class BubbleSortNormal {
    public static void main(String[] args) {
        int[] list = {3,4,1,5,2};
        int temp = 0; // 开辟一个临时空间, 存放交换的中间值
        // 要遍历的次数
        for (int i = 0; i < list.length-1; i++) {
            System.out.format("第 %d 遍:\n", i+1);
            //依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
            for (int j = 0; j < list.length-1-i; j++) {
                // 比较相邻的元素,如果前面的数小于后面的数,就交换
                if (list[j] < list[j+1]) {
                    temp = list[j+1];
                    list[j+1] = list[j];
                    list[j] = temp;
                }
                System.out.format("第 %d 遍的第%d 次交换:", i+1,j+1);
                for(int count:list) {
                    System.out.print(count);
                }
                System.out.println("");
            }
            System.out.format("第 %d 遍最终结果:", i+1);
            for(int count:list) {
                System.out.print(count);
            }
            System.out.println("\n#########################");
        }
    }
}

  运行结果:

                                                

五 算法的第一次优化

  经过了上述的讨论和编码,常规的冒泡排序已经被我们实现了。那么接下来我们要讨论的就是刚刚分析时候提出的问题。

  首先针对第一个问题,当我们进行完第三遍的时候,实际上整个排序都已经完成了,但是常规版还是会继续排序。

  可能在上面这个示例下,可能看不出来效果,但是当数组是,5,4,3,1,2 的时候的时候就非常明显了,实际上在第一次循环的时候整个数组就已经完成排序,但是常规版的算法仍然会继续后面的流程,这就是多余的了。

                                                     

  为了解决这个问题,我们可以设置一个标志位,用来表示当前第 i 趟是否有交换,如果有则要进行 i+1 趟,如果没有,则说明当前数组已经完成排序。实现代码如下:

/**
 * 冒泡排序优化第一版
 */
public class BubbleSoerOpt1 {
    public static void main(String[] args) {
        int[] list = {5,4,3,1,2};
        int temp = 0; // 开辟一个临时空间, 存放交换的中间值
        // 要遍历的次数
        for (int i = 0; i < list.length-1; i++) {
            int flag = 1; //设置一个标志位
            //依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
            for (int j = 0; j < list.length-1-i; j++) {
                // 比较相邻的元素,如果前面的数小于后面的数,交换
                if (list[j] < list[j+1]) {
                    temp = list[j+1];
                    list[j+1] = list[j];
                    list[j] = temp;
                    flag = 0;  //发生交换,标志位置0
                }
            }
            System.out.format("第 %d 遍最终结果:", i+1);
            for(int count:list) {
                System.out.print(count);
            }
            System.out.println("");
            if (flag == 1) {//如果没有交换过元素,则已经有序
                return;
            }
        }
    }
}

  运行结果中可以看到优化效果非常明显,比正常情况下少了两次的循环:

                                      

     这个时候我们就来讨论一下上面留下的一个小地方!没错就是最优时间复杂度为O(n)的问题,我们在进行了这一次算法优化之后,就可以做到了。

  当给我们一个数列,5,4,3,2,1,让我们从大到小排序。没错,这是已经排好序的啊,也就是说因为标志位的存在,上面的循环只会进行一遍,flag没有变成1,整个算法就结束了,这也就是 O(n) 的来历了!

六 算法的第二次优化

除了上面这个问题,在冒泡排序中还有一个问题存在,就是第 i 趟排的第 i 小或者大的元素已经在第 i 位上了,甚至可能第 i-1 位也已经归位了,那么在内层循环的时候,有这种情况出现就会导致多余的比较出现。例如:6,4,7,5,1,3,2,当我们进行第一次排序的时候,结果为6,7,5,4,3,2,1,实际上后面有很多次交换比较都是多余的,因为没有产生交换操作。

  我们用刚刚优化过一次的算法,跑一下这个数组。

/**
 * 冒泡排序优化第一版
 */
public class BubbleSoerOpt1 {
    public static void main(String[] args) {
        int[] list = {6,4,7,5,1,3,2};
        int len = list.length-1;
        int temp = 0; // 开辟一个临时空间, 存放交换的中间值
        // 要遍历的次数
        for (int i = 0; i < list.length-1; i++) {
            int flag = 1; //设置一个标志位
            //依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
            for (int j = 0; j < len-i; j++) {
                // 比较相邻的元素,如果前面的数小于后面的数,交换
                if (list[j] < list[j+1]) {
                    temp = list[j+1];
                    list[j+1] = list[j];
                    list[j] = temp;
                    flag = 0;  //发生交换,标志位置0

                }
                System.out.format("第 %d 遍第%d 趟结果:", i+1, j+1);
                for(int count:list) {
                    System.out.print(count);
                }
                System.out.println("");
            }

            System.out.format("第 %d 遍最终结果:", i+1);
            for(int count:list) {
                System.out.print(count);
            }
            System.out.println("");
            if (flag == 1) {//如果没有交换过元素,则已经有序
                return;
            }
        }
    }
}

运行结果中可以看出,第三趟的多次比较实际上可以没有,因为中间几个位置在第二趟就没有过交换。

                                   

针对上述的问题,我们可以想到,利用一个标志位,记录一下当前第 i 趟所交换的最后一个位置的下标,在进行第 i+1 趟的时候,只需要内循环到这个下标的位置就可以了,因为后面位置上的元素在上一趟中没有换位,这一次也不可能会换位置了。基于这个原因,我们可以进一步优化我们的代码。

/**
 * 冒泡排序优化第二版
 */
public class BubbleSoerOpt2 {
    public static void main(String[] args) {
        int[] list = {6,4,7,5,1,3,2};
        int len = list.length-1;
        int temp = 0; // 开辟一个临时空间, 存放交换的中间值
        int tempPostion = 0;  // 记录最后一次交换的位置
        // 要遍历的次数
        for (int i = 0; i < list.length-1; i++) {
            int flag = 1; //设置一个标志位
            //依次的比较相邻两个数的大小,遍历一次后,把数组中第i小的数放在第i个位置上
            for (int j = 0; j < len; j++) {
                // 比较相邻的元素,如果前面的数小于后面的数,交换
                if (list[j] < list[j+1]) {
                    temp = list[j+1];
                    list[j+1] = list[j];
                    list[j] = temp;
                    flag = 0;  //发生交换,标志位置0
                    tempPostion = j;  //记录交换的位置
                }
                System.out.format("第 %d 遍第%d 趟结果:", i+1, j+1);
                for(int count:list) {
                    System.out.print(count);
                }
                System.out.println("");
            }
            len = tempPostion; //把最后一次交换的位置给len,来缩减内循环的次数
            System.out.format("第 %d 遍最终结果:", i+1);
            for(int count:list) {
                System.out.print(count);
            }
            System.out.println("");
            if (flag == 1) {//如果没有交换过元素,则已经有序
                return;
            }
        }
    }
}

  运行结果中,可以清楚的看到,部分内循环多余的比较已经被去掉了,算法得到了进一步的优化:

                                  

综上所述,冒泡排序是比较交换排序算法中的一种。

冒泡排序(超详细) 1、什么是冒泡排序? 冒泡排序的英文Bubble Sort,是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。 冒泡排序的原理: 每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。 而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面 阅读详情

相关推荐

Open UI5 源代码解析之912:FieldSelect.js

摘要 FieldSelect.js 是 OpenUI5 中为 FieldBase 定制的选择控件,扩展了 sap.m.Select 并与 ValueHelp 机制结合。该文件实现以下核心功能: 模块定位:专为 FieldBase 设计的选择输入组件,处理值帮助、导航条件和格式化逻辑的统一性。 生命周期管理:通过 ManagedObjectObserver 监听属性变化,优化 ariaAttributes 的 DOM 直接更新策略,避免重渲染。 条件格式化:与 FieldBase 协作,使用 Conditio

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 650

冒泡排序详解

冒泡排序详解

weixin_42571338的博客 1万+

LIN协议栈 AUTOSAR架构下 状态管理

LIN State Manager 通过对这些状态的管理和转换,实现了对 LIN 总线通信状态的精确控制,以满足汽车电子系统中不同场景的需求。

汽车电子从业者,电子爱好者。 2051

冒泡排序时间复杂度计算和优化

简介 冒泡排序是一种较简单排序算法。它重复地走访过要排序的元素列,依次比较和交换两个相邻的元素,每一次遍历会将一个元素“浮”到数列的顶端,所以命名为冒泡排序。 排序过程 对于数组[5, 10, 13, 15, 10, 100, 78, 46],要求从小到大排序。 从下标为j开始,比较相邻两个元素,如果arr[j] > arr[j + 1],则交换元素。 然后j++,比较下一对元素。...

yftk765768540的专栏 4320

VerilogEval实战指南:从基准测试到模型微调的全流程解析

本文提供了VerilogEval实战指南,详细解析了从基准测试到模型微调的全流程。VerilogEval是一个自动化、标准化的测试框架,用于客观评估大语言模型生成Verilog硬件代码的能力。文章涵盖了环境搭建、核心机制解析、利用其生成合成数据进行模型微调的策略,以及如何将其工程化集成到自动化评估流程中,为硬件设计领域的AI应用提供了完整的方法论。

yy01234的博客 1005

手把手教你用Foobar2000播放DSD音频(含DOP设置与SPDIF输出教程)

本文详细指导如何在Foobar2000中播放DSD音频,重点解析了DOP协议的原理与设置方法。通过安装SACD插件、配置输出设备及启用DOP模式,实现DSD音频的原生比特流输出,并特别讲解了如何通过SPDIF接口传输DSD信号,帮助发烧友充分释放高清音频设备的潜力。

weixin_29290963的博客 1823

冒泡排序详解(Bubble Sort)

构造的过程就是将无序的序列构造成一个堆的过程。堆的定义是:对于任意一个非叶子节点 i,其左子节点为 2i+1,右子节点为 2i+2,且父节点的值大于等于(或小于等于)其子节点的值。因此,我们可以从最后一个非叶子节点开始,依次将其与其子节点比较,如果不满足堆的定义,则交换位置,直到整个序列构造成一个堆为止。排序的过程就是将堆顶元素与最后一个元素交换位置,然后将剩余元素重新构造成一个堆的过程。每次交换后,堆的大小减一,因此我们可以通过一个变量来记录堆的大小,从而将排序的过程限制在堆的范围内。

Wzill 1万+

IP白名单校验

支持格式: 1、127.0.0.1 // 指定固定IP 2、127.0.0.1-127.0.0.128 // 可使用“-”来表示一个IP区间,支持跨IP段(一般不会出现这种情况,跨IP段则配置多个规则) 3、127.0.* // 2~4位IP段可...

2199

细说冒泡排序及其五种优化算法

​01冒泡排序 冒泡排序算法思想简单来说:在内层一次遍历中,arr[j] 与 arr[j - 1] 进行比较,如arr[j - 1] < arr[j], 不改变,反之互换值,保证arr[j]存储着 0~j - 1中的最大值,随一次遍历当前数组最大值也下沉至末尾,经过n - 1次外层循环,可使n - 1个元素下沉,最后一个元素位置确定,排序完成。 举个例子方便理解, 例1:例如原数组为:0,34,66,12,100,98 进入第一次冒泡排序: ...

少年 2779

使用sklearn学习多项式回归(三)

1,什么是线性 我们无数次提到”线性“这个名词。在支持向量机中,我们也曾经提到最初的支持向量机只能够分割线性可分的数据,然后引入了”核函数“来帮助我们分类那些非线性可分的数据。我们也曾经说起过,比如说决策树,支持向量机是”非线性“模型。所有的这些概念,让我们对”线性“这个词非常熟悉,却又非常陌生——因为我们并不知道它的真实含义。接下来我们看看线性回归模型改进的核心之一:帮助线性回归解决非线性问题。 1.1,变量之间的线性关系 ”线性“这个词用于描述不同事物时有着不同的含义。我们最常使用的线性是指“变量

理科男同学 5100

Cadence PSpice 仿真0:绘制电路图方法图文教程

在使用PSpice进行电路的仿真时,首先需要使用带有PSpice模型的元器件来绘制电路图,本文简单介绍进行电路图绘制是常用的几种需要掌握的方法,以满足PSpice仿真绘制简单电路图所用。

fydar的博客 4703

java代码之冒泡排序及其优化

1、交换排序的基本思想 两两比较待排序记录的关键字,如果发现两个记录的次序相反则进行交换,知道所有记录都没有反序为止。 2、常见的交换排序 冒泡排序、快速排序 3、冒泡排序的基本思想 冒泡排序属于比较简单的排序,以非递减为例,依次遍历数组,发现a[i]&amp;gt;a[i+1}的情况,swap(a[i],a[i+1]),直到没有逆序的数据,完成排序,可以用两个for循环嵌套实现,外层控制遍历次数,内层用...

奔跑的蜗牛@1997 2000

银河麒麟操作系统最佳实践专栏介绍

本专栏聚焦国产银河麒麟操作系统(桌面版/服务器版)的实战应用,提供系统安装、配置优化、应用部署、故障排查等实用指南。内容基于真实操作场景,涵盖Linux通用技术(如RAID配置、环境变量管理)、服务器系统专项(如数据库迁移、驱动安装、安全加固)及桌面系统技巧(如PXE安装、镜像封装)。面向IT运维、开发者和国产化转型人员,突出实战性和差异化,帮助用户规避常见问题,提升系统管理效率。所有教程均配有详细步骤和截图,确保可操作性,并持续更新适配新版系统。

信创运维工程师 · 银河麒麟 & 容器实战 | 金融生产环境迁移·等保加固 | 142篇原创·17万+阅读 532

冒泡排序算法及其优化

思想: 同之前介绍的两种排序方式一样,冒泡排序也是最简单最基本的排序方法之一。冒泡排序的思想很简单,就是以此比较相邻的元素大小,将小的前移,大的后移,就像水中的气泡一样,最小的元素经过几次移动,会最终浮到水面上。 举例分析说明一下,如下数据: 2 7 4 6 9 1 首先比较最后两个数字,发现1比9小,于是前移 2 7 4 6 1 9 然后比较6和1 2 7 4 1 6 9 继...

diaosu3231的博客 125

蓝鸟四轴飞控系统C程序V3.0(基础版本)深度解读

2. 解析遥控通道数据(油门 / 横滚 / 俯仰 / 偏航),转换为标准化值(-1000~+1000);3. 封装PID初始化(PID_Init,配置KP/KI/KD、输出限幅、积分限幅);,包含姿态角(Attitude_Data.Roll/Pitch/Yaw)、角速度(Gyro_Data.X/Y/Z)、遥控值(RC_Data[0~5])、电池电压(Bat_Volt)、电机PWM值(Motor_PWM[0~3])、系统状态(Sys_State),校验和通过Check_Sum()函数计算;

搏博的专栏 344

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

冒泡排序法及其优化

冒泡排序法及其优化, C/C++ 版. 如有错误,欢迎指正。 注:本人非计算机专业,纯属业余,欢迎拍砖。呵呵。 直接上代码: #include #include #include #include using std::cout; using std::endl; //冒泡排序法 std::pair<int, int> bubbleSort(int[], const int);...

pbzhang_csdn的博客 203

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

冒泡排序及其优化详解

最近看了比较多的排序及其优化方案,感觉有许多写的不错。于是自己也写了一点笔记,如果还有其他优化方案也希望大家能积极分享。 我就话不多说直接贴代码吧: public void BubleSort(int[] arr) { for (int i = 0; i < arr.Length; i++) { for (int j = 0...

A_Pro_Cat的博客 391

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

上一篇: 栈的链式存储结构
下一篇: 插入排序算法
至学者
博客等级 码龄9年 180粉丝 773原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值