冒泡、选择、插入排序的Go语言实现

发布时间 2024-01-09 14:29:57作者: realcp1018
这三种排序之所以放到一起说,是因为他们都比较基础,其最差时间复杂度皆为O(n²),空间复杂度皆为O(1)。
最差时间复杂度为O(n²)并不意味着他们就一定比快排、归并等排序算法差,因为时间复杂度只是一个理论相关值,实际排序的耗时和数组规模、数组的有序程度以及 比较/交换次数 都有关系。
鉴于算法较为简单,就不做额外的介绍了,直接看代码和注释即可。
一、冒泡排序
// BubbleSort 挨个排序相邻元素,最终末尾就是最大值,把去除末尾元素的数组作为新的未排序数组,重复此行为直到未排序数组为空
func BubbleSort(array []int) []int {
	n := len(array)
	// 遍历n次
	for i := 0; i < n; i++ {
		// 每次循环都会把相邻元素排序,最终实现把最大值放在末尾的效果,下次循环时未排序序列的右边界左移一位即可
		for j, v := range array[:n-1-i] {
			if array[j+1] < v {
				array[j], array[j+1] = array[j+1], v
			}
		}
	}
	return array
}

二、选择排序

// SelectionSort 初始时从数组中选出最大值,然后与末尾交换位置,之后未排序数组右界左移一位继续寻找最大值放在末尾,重复此行为直至整个数组有序
func SelectionSort(array []int) []int {
	n := len(array)
	var max int
	var maxIndex int
	// 遍历n次
	for i := 0; i < n; i++ {
		// 找到当前未排序序列的最大值然后置换到末尾,下次循环时未排序序列的右边界左移一位即可
		max = array[0]
		maxIndex = 0
		for j, v := range array[:n-i] {
			if v > max {
				max = v
				maxIndex = j
			}
		}
		array[maxIndex], array[n-1-i] = array[n-1-i], max
	}
	return array
}

三、插入排序

// InsertionSort 类似扑克牌的手牌排序方法,起始时将头部元素作为有序数组,剩下部分作为无序数组
// 从无序数组中依次取值作为候选值(candidate),然后倒叙遍历有序数组,每当发现候选值更小那么交换一次位置,否则直接break
func InsertionSort(array []int) []int {
	n := len(array)
	var candidate int
	// 遍历n-1次
	for i := 1; i < n; i++ {
		// 取array[i]为候选值,倒叙遍历有序数组,每当发现候选值小于遍历值则交换位置
		candidate = array[i]
		for j := i - 1; j >= 0; j-- {
			if candidate < array[j] {
				array[j+1], array[j] = array[j], candidate
			} else {
				break
			}
		}
	}
	return array
}
插入排序是通过在有序数组中寻找合适位置进行置换的因此推测有两种优化方式:
1.倒序遍历有序数组时不需要实时替换,只需要不断比较然后记下candidate的最终位置,把有序数组从candidate的位置分为左右两部分,然后把右部分整体右移一位,最后把candidate插入目标位置即可
2.倒序遍历时采用二分法进行比较,最终确定candidate位置之后,把有序数组从candidate的位置分为左右两部分,然后把右部分整体右移一位,最后把candidate插入目标位置即可
三种基础排序算法的优劣?
冒泡排序总是需要多次的比较,替换次数和数组的有序度强相关,数组越有序需要的替换次数越少。
选择排序总是需要多次的比较,替换次数是固定的。
插入排序的比较和替换次数与数组的有序度相关,在数组有序的场景下,比较和替换的次数都会极低。
因此插入排序在实践中一般会比冒泡和选择排序更快,但这只是平均场景下的评估,不要认为所有场景下插入排序都是最优的。