一 冒泡排序
1.1 冒泡排序概念
冒泡排序(Bubble Sort)是一种交换排序,基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序记录位置。
假设要对无序数列{2,3,4,5,6,7,8,1}排序:
冒泡排序规律:每一轮排序两两比较都会把最大的值移动到最后一位,最大值就像在不断的冒泡一样。
1.2 冒泡算法
func BubbleSort(arr []int) {
if arr == nil || len(arr) < 2 {
fmt.Println("数组不满足要求")
return
}
// 外层循环:确定扫描的次数
for i := 1; i <= len(arr) - 1; i++ {
// 内层循环:一轮扫描内,两两比较,进行交换
for j := 0; j <= len(arr) - 1 - i; j++ { // - i 的原因是后面的元素已经被排序过
if arr[j] > arr[j + 1] {
temp := arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
1.3 冒泡算法优化
如果要排序的数据序列已经完全有序了,那么冒泡算法仍然会按照两两比较策略继续走下去,这是不能容忍的,我们可以先记录该数据序列是否有序,只要内层循环没有发生交换,就证明整个数组现在已经有序,无需外层循环再次排序!
func BubbleSort(arr []int) {
if arr == nil || len(arr) < 2 {
fmt.Println("数组不满足要求")
return
}
isSorted := false
for i := 1; i <= len(arr)-1; i++ {
isSorted = true
for j := 0; j < len(arr)-1-i; j++ {
if arr[j] > arr[j+1] {
temp := arr[j+1]
arr[j+1] = arr[j]
arr[j] = temp
isSorted = false
}
}
if isSorted {
break
}
}
}
1.4 冒泡排序优化二
显然数据的完全有序概率是很低的,但是数据局部有序的情况概率还是很高的。如果如果最后几个元素都已经是排好的,那么这几个局部有序的数据就无需进行冒泡排序了,如:arr := []int{3, 2, 4, 1, 6, 0, 5, 7, 8, 9}
。在1.3优化一的基础上,我们可以通过记录最后一次排序比较的索引,来继续优化:
func BubbleSort(arr []int) {
if arr == nil || len(arr) < 2 {
fmt.Println("数组不满足要求")
return
}
isSorted := false
sortIndex := len(arr) - 1 - 1
lastIndex := 0 // 记录最后一次交换的位置
for i := 1; i <= len(arr)-1; i++ {
isSorted = true
for j := 0; j <= sortIndex; j++ {
if arr[j] > arr[j+1] { // 不能加入等号,这样会造成不稳定
temp := arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
isSorted = false
lastIndex = j
}
}
if isSorted {
break
}
sortIndex = lastIndex
}
}
二 冒泡排序复杂度分析
- 最好情况:数据本身是有序的,那么其时间复杂度应该是O(n)
- 最坏情况:表中所有的元素都是逆序的,时间复杂度为O(n^2)。
三 扩展:鸡尾酒排序
冒泡排序的每一轮都是从左到右比较,进行单向的位置交换。鸡尾酒排序则可以让比较和交换的过程是双向的。
对1.1图中的序列进行冒泡排序会造成大量浪费,鸡尾酒排序则很容易实现:
也就是说鸡尾酒的排序过程像钟摆一样,奇数轮和偶数轮来回排序,第1轮从左往右,第2轮从右往左,第3轮再从左往右,直到有一轮排序时没有发生交换,则退出循环。
我们也不难发现:当一个无需数列中,大多元素都有序的时,使用鸡尾酒排序则能达到很好的效果。