冒泡排序
冒泡排序是我接触算法的第一个算法。😶😶😶
算法介绍
属于一种比较简单的算法,先来看看他的排序流程:
现在有有一组乱序的数,比如:5 9 1 6 8 14 6 49 25 4 6 3
- 第一次迭代:从第一个数开始,依次比较相邻的两个数,如果前面的数比后面的数大,那么两个数会交换位置,直到处理到了倒数第二位时候,现在的最后一位就是最大的数
49。[5 1 6 8 9 6 14 25 4 6 3 49] - 第二次迭代:与第一次迭代工作一致。
[1 5 6 8 6 9 14 4 6 3 25 49] - 9 次迭代后:….
- 第十一次:
[1 3 4 5 6 6 6 8 9 14 25 49],这就得出来排序好的一组元素了。
你会发现大的元素会像泡泡一样浮到顶端,就像可乐里面气泡一样,所以这也是为什么叫做冒泡排序的原因。
为了更好的展示过程,采用动画方式取展示上面所有的具体的流程。
算法实现
package main
import "fmt"
func BubbleSort(list []int) {
n:=len(list)
for i:=n-1;i>0;i--{
for j:=0;j<i;j++{
if list[j]>list[j+1]{
list[j],list[j+1]=list[j+1],list[j]
}
}
}
}
func main() {
//排序算法
list := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3}
//冒泡排序
BubbleSort(list)
fmt.Println(list)
}
时间复杂度
当一组元素个数为 N,迭代过程中进行比较的次数:
- 第一次迭代比较次数:
N-1次 - 第二次迭代比较次数:
N-2次 - 第三次迭代比较次数:
N-3次 - …(省略多次迭代)
- 第
N-1次(也是最后一次)迭代比较次数:1次
总体的比较次数:1+2+3+4+...+N-1=(N^2 - N)/2,这是平方级别的时间复杂度,我们记为 O(n^2)。
空间复杂度
很多编程语言不允许这样:list[j], list[j+1] = list[j+1], list[j],会要求交换两个值时必须建一个临时变量 a 来作为一个过渡,如:
a := list[j+1]
list[j+1] = list[j]
list[j] = aCopy to clipboardErrorCopied
但是 Golang 允许我们不那么做,它会默认构建一个临时变量来中转。
这里只用到了常量进行交换,所以空间复杂度:O(1)。
算法改进
怎么去改进这个算法,减少他的时间复杂度呢?
[5 1 6 8 9 6 14 25 4 6 3 49]
[1 5 6 8 6 9 14 4 6 3 25 49]
[1 5 6 6 8 9 4 6 3 14 25 49]
[1 5 6 6 8 4 6 3 9 14 25 49]
[1 5 6 6 4 6 3 8 9 14 25 49]
[1 5 6 4 6 3 6 8 9 14 25 49]
[1 5 4 6 3 6 6 8 9 14 25 49]
[1 4 5 3 6 6 6 8 9 14 25 49]
[1 4 3 5 6 6 6 8 9 14 25 49]
[1 3 4 5 6 6 6 8 9 14 25 49]
[1 3 4 5 6 6 6 8 9 14 25 49]
通过上面的打印,发现好像多进行一次的比较,我们可以通过引入了 didSwap 的变量,记录当前这一轮中是否存在交换数值的情况,代表此轮其实已经是有序的一组数了,就可以退出不用继续比较了。
func BubbleSort2(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
}
}
}
时间复杂度
最好的情况:这组数是有序的状态,我只需要判断一轮,确定是有序的状态,后续就不用比较了O(n)。
最坏的情况:这组数是乱序的状态,每次比较都要交换值,O(n^2)。
空间复杂度
其实没有改变,O(1)。