算法排序-冒泡排序
简介
冒泡排序(Bubble Sort)是最简单直观的排序算法之一。它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端,就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样。
算法原理
冒泡排序的基本思想是:
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对
- 在这一点,最后的元素应该会是最大的数
- 针对所有的元素重复以上的步骤,除了最后一个
- 重复步骤1~4,直到排序完成
Go语言实现
基础版本
go
package main
import "fmt"
// BubbleSort 冒泡排序基础实现
func BubbleSort(arr []int) {
n := len(arr)
// 外层循环控制排序轮数
for i := 0; i < n-1; i++ {
// 内层循环进行相邻元素比较和交换
for j := 0; j < n-1-i; j++ {
if arr[j] > arr[j+1] {
// 交换元素
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func main() {
arr := []int{64, 34, 25, 12, 22, 11, 90}
fmt.Println("排序前:", arr)
BubbleSort(arr)
fmt.Println("排序后:", arr)
}
优化版本
go
package main
import "fmt"
// BubbleSortOptimized 优化的冒泡排序
func BubbleSortOptimized(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
swapped := false // 标记本轮是否发生交换
for j := 0; j < n-1-i; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = true
}
}
// 如果本轮没有发生交换,说明数组已经有序
if !swapped {
break
}
}
}
// BubbleSortWithSteps 带步骤显示的冒泡排序
func BubbleSortWithSteps(arr []int) {
n := len(arr)
fmt.Printf("初始数组: %v\n", arr)
for i := 0; i < n-1; i++ {
swapped := false
fmt.Printf("\n第 %d 轮排序:\n", i+1)
for j := 0; j < n-1-i; j++ {
if arr[j] > arr[j+1] {
fmt.Printf(" 交换 %d 和 %d\n", arr[j], arr[j+1])
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = true
}
}
fmt.Printf(" 本轮结果: %v\n", arr)
if !swapped {
fmt.Println(" 数组已有序,提前结束")
break
}
}
}
func main() {
// 测试基础版本
arr1 := []int{64, 34, 25, 12, 22, 11, 90}
fmt.Println("=== 基础冒泡排序 ===")
fmt.Println("排序前:", arr1)
BubbleSort(arr1)
fmt.Println("排序后:", arr1)
// 测试优化版本
arr2 := []int{5, 2, 8, 1, 9}
fmt.Println("\n=== 优化冒泡排序 ===")
fmt.Println("排序前:", arr2)
BubbleSortOptimized(arr2)
fmt.Println("排序后:", arr2)
// 测试带步骤显示的版本
arr3 := []int{5, 2, 8, 1}
fmt.Println("\n=== 带步骤显示的冒泡排序 ===")
BubbleSortWithSteps(arr3)
}
复杂度分析
时间复杂度
- 最坏情况: O(n²) - 数组完全逆序时
- 最好情况: O(n) - 数组已经有序时(优化版本)
- 平均情况: O(n²)
空间复杂度
- 空间复杂度: O(1) - 只需要常数级别的额外空间
算法特点
优点
- 实现简单: 代码逻辑清晰,容易理解和实现
- 原地排序: 只需要常数级别的额外空间
- 稳定排序: 相等元素的相对位置不会改变
- 自适应性: 对于已经部分有序的数组,优化版本可以提前结束
缺点
- 效率低下: 时间复杂度为O(n²),不适合大数据集
- 比较次数多: 即使数组已经有序,基础版本仍会进行所有比较
实际应用场景
冒泡排序虽然效率不高,但在以下场景中仍有其价值:
- 教学演示: 作为排序算法的入门教学
- 小数据集: 当数据量很小时(通常小于50个元素)
- 几乎有序的数据: 优化版本对于几乎有序的数据表现较好
- 嵌入式系统: 在内存极其有限的环境中,简单性比效率更重要
性能测试
go
package main
import (
"fmt"
"math/rand"
"time"
)
// 生成随机数组
func generateRandomArray(size int) []int {
rand.Seed(time.Now().UnixNano())
arr := make([]int, size)
for i := 0; i < size; i++ {
arr[i] = rand.Intn(1000)
}
return arr
}
// 性能测试
func benchmarkBubbleSort(size int) {
arr := generateRandomArray(size)
start := time.Now()
BubbleSortOptimized(arr)
duration := time.Since(start)
fmt.Printf("数组大小: %d, 排序时间: %v\n", size, duration)
}
func main() {
fmt.Println("=== 冒泡排序性能测试 ===")
sizes := []int{100, 500, 1000, 2000}
for _, size := range sizes {
benchmarkBubbleSort(size)
}
}
总结
冒泡排序是一个经典的排序算法,虽然效率不高,但其简单性使其成为学习排序算法的绝佳起点。通过理解冒泡排序的原理和实现,可以为学习更复杂的排序算法打下坚实的基础。
在实际开发中,除非是处理非常小的数据集或者在资源极其有限的环境中,否则建议使用更高效的排序算法如快速排序、归并排序或者直接使用语言内置的排序函数。
关键要点:
- 冒泡排序通过重复比较相邻元素并交换来实现排序
- 时间复杂度为O(n²),空间复杂度为O(1)
- 是稳定的排序算法
- 适合教学和小数据集排序
- 优化版本可以在数组已有序时提前结束