冒泡排序详解_超详细电音

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

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

1、什么是冒泡排序?

冒泡排序的英文Bubble Sort,是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。

冒泡排序的原理:

每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。

而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。

2、冒泡排序到底是如何排序的呢?

下面通过一个动图来看一看冒泡排序到底是怎么样移动的

在这里插入图片描述

具体是如何移动的呢?这里参考了(帅地的冒泡排序)

(1)起始时,左下标指向第一个石子,右下标指向第二个石子,然后比较

在这里插入图片描述

(2)然后左右下标同时向右移动,再次比较

在这里插入图片描述

(3)第一趟结束之后,最大的石子就移动到了最右边

在这里插入图片描述

(4)接下来就从剩下 3 个没排好序的石子中继续选出最大的,规则和上面一样

在这里插入图片描述

(5)下面给出这 4 个石子完整的演示过程

在这里插入图片描述

3、时间复杂度

由上图可知,4 个石子的时候排完序需要 3 趟,第一趟需要比较3次,第二趟需要比较2次,第三趟需要比较1次,那一共比较了 3 + 2 + 1 次;

那如果有 n 个石子呢?

那就需要 (n-1) + (n-2) +…+2+1 次,这不就是一个等差数列吗,很显然:

在这里插入图片描述

根据复杂度的规则,去掉低阶项(也就是 n/2),并去掉常数系数,那复杂度就是 **O(n^2)**了;

冒泡排序也是一种稳定排序,因为在两个数交换的时候,如果两个数相同,那么它们并不会因为算法中哪条语句相互交换位置。

4、冒泡排序代码原始版

//按照刚才那个动图进行对应
//冒泡排序两两比较的元素是没有被排序过的元素--->
public void bubbleSort(int[] array){ 
   
    for(int i=0;i<array.length-1;i++){ 
   //控制比较轮次,一共 n-1 趟
        for(int j=0;j<array.length-1-i;j++){ 
   //控制两个挨着的元素进行比较
            if(array[j] > array[j+1]){ 
   
                int temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
}
疑问环节

小花:第一层循环是用来控制趟数,也就是 n 个数就要比较 n-1 趟;那么第二层循环能不能具体解答一下呢?

小明:第二层是控制第 i+1 趟(因为循环中 i 是从 0 开始的)的比较次数,那么 i+1 趟就是比较了 N-1-i 次

小花:还是不是很清楚,能不能用图形来跟我描述一下呀?

小明:当然可以呢,下面通过几张图来给你讲解一下。

在这里插入图片描述
在这里插入图片描述

小花:这个明白了,但是如果遇到下面这种情况呢?也就是执行到最后三轮的时候,发现整个数列已经是有序的了,可以按上面的代码执行的话算法还是仍然“兢兢业业”地继续执行第七轮、第八轮。这个可不可以优化一下呢?

小明:当然可以优化,如果我们能判断出数列已经有序,并且做出标记,剩下的几轮排序就可以不必执行,提早结束工作。

小花:那到底又怎么样优化上面的代码才能得到这种效果呢?

小明:其实也很简单,就是将上面代码做一点点小小改动即可,也就是利用布尔变量 isSorted作为标记。如果在本轮排序中,元素有交换,则说明数列无序;如果没有元素交换,说明数列已然有序,直接跳出大循环。

5、冒泡排序代码优化版

public static int[] bubbleSort(int[] arr) { 
   
     if (arr == null || arr.length < 2) { 
   
          return arr;
     }
    for (int i = 0; i < arr.length - 1; i++) { 
   
         boolean isSorted  = true;//有序标记,每一轮的初始是true
         for (int j = 0; j < arr.length -i - 1; j++) { 
   
             if (arr[j + 1] < arr[j]) { 
   
                 isSorted  = false;//有元素交换,所以不是有序,标记变为false
                 int t = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = t;
             }
         }
         //一趟下来是否发生位置交换,如果没有交换直接跳出大循环
         if(isSorted )
              break;
     }
     return arr;
}

小花:懂了,我还有一个问题。那如果数列中前半部分是无序的,后半部分是有序的呢?比如(3,4,2,1,5,6,7,8)这个数组,其实后面的许多元素已经是有序的了,但是每一轮还是白白比较了许多次呢?例如:

第一轮:

在这里插入图片描述

元素 3 和元素 4 比较,3 大于 4,所以位置不变

接着元素 4 和元素 2 比较,4 大于 2,所以 4 和 2 交换
在这里插入图片描述

接着元素 4 和元素 1 交换

在这里插入图片描述

再接着就发现后面其实就是有序数列了,但是还是要每一次两两相比,这样就白白比较了很多次了

在这里插入图片描述

第二轮:

在这里插入图片描述

元素 3 和元素 2 比较,3 大于 2,所以 3 和 2 交换

在这里插入图片描述

元素 3 和 1 比较,发现 3 大于 1,所以 3 和 1 交换

在这里插入图片描述

再接着就发现后面其实又是有序数列了,但是还是要每一次两两相比,这样也白白比较了很多次了

在这里插入图片描述

小明:对于上面你提出的问题,关键在于对这数列有序区的界定。如果按冒泡排序代码原始版来分析的话,有序区的长度和排序的轮数是相等的。比如第一轮排序过后的有序区长度是1,第二轮排序过后的有序区长度是2 ……。

但是呢,实际数列真正的有序区可能会大于这个长度,也就是你上面这个例子,第二轮中后面 5 个实际上都已经属于有序区了。因此后面的比较是没有意义的了。

我们可以这样做来避免这种情况:在每一轮排序的最后,记录一下最后一次元素交换的位置,那个位置也就是无序数列的边界,再往后就是有序区了。

6、冒泡排序代码升级版

public static int[] bubbleSort(int[] arr) { 
   
     if (arr == null || arr.length < 2) { 
   
          return arr;
     }
    //记录最后一次交换的位置
    int lastExchangeIndex = 0;
    //无序数列的边界,每次比较只需要比到这里为止
    int sortBorder = arr.length - 1;
    
    for (int i = 0; i < arr.length - 1; i++) { 
   
         boolean isSorted  = true;//有序标记,每一轮的初始是true
         for (int j = 0; j < sortBorder; j++) { 
   
             if (arr[j + 1] < arr[j]) { 
   
                 isSorted  = false;//有元素交换,所以不是有序,标记变为false
                 int t = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = t;
                 lastExchangeIndex = j;
             }
         }
        sortBorder = lastExchangeIndex
         //一趟下来是否发生位置交换,如果没有交换直接跳出大循环
         if(isSorted )
              break;
     }
     return arr;
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/180211.html原文链接:https://javaforall.net

(0)
上一篇 2022年10月19日 下午5:16
下一篇 2022年10月19日 下午5:16


相关推荐

  • telnet命令详解_Telnet命令

    telnet命令详解_Telnet命令前面的Telnet的命令使用方法部分是我在网上搬过来的,后面的内容是我在自己初学实践时所遇到的一些问题及解决方法。Telnet使用介绍  1、点击开始 → 运行 → 输入CMD,回车。  2、在出来的DOS界面里,输入telnet测试端口命令:telnetIP端口或者telnet域名端口,回车。  如果端口关闭或者无法连接,则显示不能打开到主机的链接,链接失败;端口打开的…

    2026年2月27日
    3
  • 科学计算编程语言_中国发明了什么编程语言

    科学计算编程语言_中国发明了什么编程语言本文是《打破国外垄断,开发中国人自己的编程语言》系列文章的第1篇。本系列文章的主要目的是教大家学会如何从零开始设计一种编程语言(marvel语言),并使用marvel语言开发一些真实的项目,如移动App、Web应用等。

    2022年10月5日
    4
  • killall使用

    killall使用Linux 系统中的 killall 命令用于杀死指定名字的进程 killprocesse 我们可以使用 kill 命令杀死指定进程 PID 的进程 如果要找到我们需要杀死的进程 我们还需要在之前使用 ps 等命令再配合 grep 来查找进程 而 killall 把这两个过程合二为一 是一个很好用的命令 1 命令格式 killall 参数 进程名 2 命令功能 用来结束同名的的所有进程 3 命令参数 Z 只杀死拥有 scontext 的进程 e 要求匹配进程名称 I 忽略

    2026年3月17日
    3
  • IdentityServer4 入门之一/之二

    IdentityServer4 入门之一/之二IdentityServ 快速入门 官网示例基于 NETCore2 0 这里使用 NETCore2 1 重新实现 IdentityServ 快速入门之一 IdentityServ 官网示例地址 https github com IdentityServ IdentityServ Samples tree release Quickstarts 1 ClientCreden 此示例使用 NETCore2 0 开发 我们这里升级为 NETCor

    2026年3月18日
    1
  • centos7下kafka集群搭建

    centos7下kafka集群搭建概述集群安装或者单机安装都可以,这里介绍集群安装。Kafka本身安装包也自带了zookeeper,也可以使用其自带的zookeeper。建议试用自己安装的zookeeper,本教程试用单独安装的zookeeper。安装环境3台centos7虚拟机:10.15.21.6210.10.182.16810.10.182.169kafka_2.10-0.10.2.0zookeeper-3.4.9

    2022年6月5日
    33
  • bigdecimal保留2位小数_java bigdecimal保留两位小数

    bigdecimal保留2位小数_java bigdecimal保留两位小数本篇分享Java注解方法实现BigDecimal保留两位小数!欢迎打卡学习!

    2026年2月22日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号