冒泡排序

冒泡排序是大多数人学的第一种排序算法,在面试中,也是问的最多的一种,有时候还要求手写排序代码,因为比较简单。

冒泡排序属于交换类的排序算法。

一、算法介绍

现在有一堆乱序的数,比如:5 9 1 6 8 14 6 49 25 4 6 3

第一轮迭代:从第一个数开始,依次比较相邻的两个数,如果前面一个数比后面一个数大,那么交换位置,直到处理到最后一个数,最后的这个数是最大的。

第二轮迭代:因为最后一个数已经是最大了,现在重复第一轮迭代的操作,但是只处理到倒数第二个数。

第三轮迭代:因为最后一个数已经是最大了,最后第二个数是次大的,现在重复第一轮迭代的操作,但是只处理到倒数第三个数。

第N轮迭代:....

经过交换,最后的结果为:1 3 4 5 6 6 6 8 9 14 25 49,我们可以看到已经排好序了。

因为小的元素会慢慢地浮到顶端,很像碳酸饮料的汽泡,会冒上去,所以这就是冒泡排序取名的来源。

举个简单例子,冒泡排序一个 4 个元素的数列:4 2 9 1

[]表示排好序 {}表示比较后交换的结果

第一轮开始: 4 2 9 1 从第一个数开始,4 比 2 大,交换 4,2
第一轮: {2 4} 9 1 接着 4 比 9 小,不交换
第一轮: 2 {4 9} 1 接着 9 比 1 大,交换 9,1
第一轮: 2 4 {1 9} 已经到底,结束
第一轮结果: 2 4 1 [9] 第二轮开始:2 4 1 [9] 从第一个数开始,2 比 4 小,不交换
第二轮: {2 4} 1 [9] 接着 4 比 1 大,交换 4,1
第二轮: 2 {1 4} [9] 已经到底,结束
第二轮结果: 2 1 [4 9] 第三轮开始:2 1 [4 9] 从第一个数开始,2 比 1 大,交换 2,1
第三轮: (1 2} [4 9] 已经到底,结束
第三轮结果: 1 [2 4 9] 结果: [1 2 4 9]

首先第一个数4和第二个数2比较,因为比后面的数大,所以交换,交换后第二个数为4,然后第二个数4和第三个数9比较,因为比后面的数小,不交换,接着第三个数9和第四个数1比较,因为比后面的数大,交换,到达数列底部,第一轮结束。以此类推。

当数列的元素数量为N,冒泡排序有两种循环,需要比较的次数为:

第一次比较的次数为: N-1 次
第二次比较的次数为: N-2 次,因为排除了最后的元素
第三次比较的次数为: N-3 次,因为排除了后两个元素
...
第某次比较的次数为: 1 次

比较次数:1 + 2 + 3 + ... + (N-1) = (N^2 - N)/2,是一个平方级别的时间复杂度,我们可以记为:O(n^2)

交换次数:如果数列在有序的状态下进行冒泡排序,也就是最好情况下,那么交换次数为0,而如果完全乱序,最坏情况下那么交换的次数和比较的次数一样多。

冒泡排序交换和比较的次数相加是一个和N有关的平方数,所以冒泡排序的最好和最差时间复杂度都是:O(n^2)

我们可以改进最好的时间复杂度,使得冒泡排序最好情况的时间复杂度是O(n),请看下面的算法实现。

冒泡排序算法是稳定的,因为如果两个相邻元素相等,是不会交换的,保证了稳定性的要求。

二、算法实现

package main

import "fmt"

func BubbleSort(list []int) {
n := len(list)
// 在一轮中有没有交换过
didSwap := false // 进行 N-1 轮迭代
for i := n - 1; i > 0; i-- {
// 每次从第一位开始比较,比较到第 i 位就不比较了,因为前一轮该位已经有序了
for j := 0; j < i; j++ {
// 如果前面的数比后面的大,那么交换
if list[j] > list[j+1] {
list[j], list[j+1] = list[j+1], list[j]
didSwap = true
}
} // 如果在一轮中没有交换过,那么已经排好序了,直接返回
if !didSwap {
return
}
}
} func main() {
list := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3}
BubbleSort(list)
fmt.Println(list)
}

输出:

[1 3 4 5 6 6 6 8 9 14 25 49]

因为切片会原地排序,排序函数不需要返回任何值,处理完后可以直接打印:fmt.Println(list)

很多编程语言不允许这样:list[j], list[j+1] = list[j+1], list[j],会要求交换两个值时必须建一个临时变量a来作为一个过渡,如:

a := list[j+1]
list[j+1] = list[j]
list[j] = a

但是Golang允许我们不那么做,它会默认构建一个临时变量来中转。

我们引入了didSwap的变量,如果在一轮中该变量值没有变化,那么表示数列是有序的,所以不需要交换。也就是说在最好的情况下:对已经排好序的数列进行冒泡排序,只需比较N次,最好时间复杂度从O(n^2)骤减为O(n)

三、总结

冒泡排序是效率较低的排序算法,可以说是最慢的排序算法了,我们只需知道它是什么,在实际工作上切勿使用如此之慢的排序算法!

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

数据结构和算法(Golang实现)(19)排序算法-冒泡排序的更多相关文章

  1. 数据结构和算法(Golang实现)(25)排序算法-快速排序

    快速排序 快速排序是一种分治策略的排序算法,是由英国计算机科学家Tony Hoare发明的, 该算法被发布在1961年的Communications of the ACM 国际计算机学会月刊. 注:A ...

  2. 数据结构和算法(Golang实现)(18)排序算法-前言

    排序算法 人类的发展中,我们学会了计数,比如知道小明今天打猎的兔子的数量是多少.另外一方面,我们也需要判断,今天哪个人打猎打得多,我们需要比较. 所以,排序这个很自然的需求就出来了.比如小明打了5只兔 ...

  3. 数据结构和算法(Golang实现)(20)排序算法-选择排序

    选择排序 选择排序,一般我们指的是简单选择排序,也可以叫直接选择排序,它不像冒泡排序一样相邻地交换元素,而是通过选择最小的元素,每轮迭代只需交换一次.虽然交换次数比冒泡少很多,但效率和冒泡排序一样的糟 ...

  4. 数据结构和算法(Golang实现)(21)排序算法-插入排序

    插入排序 插入排序,一般我们指的是简单插入排序,也可以叫直接插入排序.就是说,每次把一个数插到已经排好序的数列里面形成新的排好序的数列,以此反复. 插入排序属于插入类排序算法. 除了我以外,有些人打扑 ...

  5. 数据结构和算法(Golang实现)(22)排序算法-希尔排序

    希尔排序 1959 年一个叫Donald L. Shell (March 1, 1924 – November 2, 2015)的美国人在Communications of the ACM 国际计算机 ...

  6. 数据结构和算法(Golang实现)(23)排序算法-归并排序

    归并排序 归并排序是一种分治策略的排序算法.它是一种比较特殊的排序算法,通过递归地先使每个子序列有序,再将两个有序的序列进行合并成一个有序的序列. 归并排序首先由著名的现代计算机之父John_von_ ...

  7. 数据结构和算法(Golang实现)(24)排序算法-优先队列及堆排序

    优先队列及堆排序 堆排序(Heap Sort)由威尔士-加拿大计算机科学家J. W. J. Williams在1964年发明,它利用了二叉堆(A binary heap)的性质实现了排序,并证明了二叉 ...

  8. 数据结构和算法(Golang实现)(26)查找算法-哈希表

    哈希表:散列查找 一.线性查找 我们要通过一个键key来查找相应的值value.有一种最简单的方式,就是将键值对存放在链表里,然后遍历链表来查找是否存在key,存在则更新键对应的值,不存在则将键值对链 ...

  9. 数据结构和算法(Golang实现)(27)查找算法-二叉查找树

    二叉查找树 二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下: 它是一颗二叉树,或者是空树. 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点. 左右子 ...

随机推荐

  1. angular 中嵌套 iframe 报错

    错误如下 Error: unsafe value used in a resource URL context at DomSanitizationServiceImpl.sanitize... 解决 ...

  2. C 2015年真题

    1.写出程序输出结果 void main() { char p[10]="abc"; char q[]="xyz"; int i,j; i=0; while(* ...

  3. http server部署discuz

    httpd服务器搭建discuz 第一步.数据库的配置 create database discuz; grant all privileges on discuz.* to 'discuz'@'lo ...

  4. Servlet(四)----Request

    ##  Request 1.request对象和response对象的原理 1.request和response对象是由服务器创建的.我们来使用他们. 2.request对象是来获取请求消息,resp ...

  5. ruby中的try catch

    1.在ruby中,try catch并不是用来进行异常处理的,而是一种程序流程结构,例如break,continue,go-to等 2.例如如下代码 def promptAndGet(prompt) ...

  6. 初识ASP.NET CORE

    首先创建一个asp.net core web应用程序 第二步 目前官方预置了7种模板项目供我们选择.从中我们可以看出,既有我们熟悉的MVC.WebAPI,又新添加了Razor Page,以及结合比较流 ...

  7. jmeter4.0介绍一

    apache JMeter™应用程序是开放源码软件, 一个100% 纯 Java 应用程序, 旨在加载测试功能行为和测量性能.它最初设计用于测试 Web 应用程序, 但后来扩展到其他测试功能. Jme ...

  8. WeChat-SmallProgram:组件的业务 slot 的使用

    1.调用组件向自定义组件插入内容,使用  slot 在自定义模板中有一对 <view><slot></slot></view> 这里是干什么用的呢? 在 ...

  9. idea打包或编译错误,错误为c盘idea路径某些文件被占用(非idea文件,项目生成的文件)

    方法列表(2的效果可能更好) 1.将被占用的文件删除之后,重新打包或编译. 2.多编译几次项目. 3.发现真正可能的原因.(貌似被南航企业版360拦截了,导致targe或maven等文件被占用问题) ...

  10. Vue里面提供的三大类钩子及两种函数

    在路由跳转的时候,我们需要一些权限判断或者其他操作.这个时候就需要使用路由的钩子函数. 定义:路由钩子主要是给使用者在路由发生变化时进行一些特殊的处理而定义的函数. 总体来讲vue里面提供了三大类钩子 ...