1 全文摘要
本文介绍了七种常见的排序算法,包括原理解释、时间/空间复杂度分析、稳定性等,并基于Go语言给出了代码实现。
2 算法介绍
2.1 总体描述
2.1.1性能指标定义
时间复杂度是指执行算法所需要的计算工作量
排序算法稳定性是指假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种是稳定的;否则称为不稳定的。注意:改变相同元素的相对位置是一种不稳定的表现,因为在某些应用场景下,我们需要保持相同元素的相对位置不变。
2.1.2 各排序算法的性能分析
- 插入排序
- 时间复杂度:最好情况下为 O(n),最坏情况下为 O(n^2),平均情况下为 O(n^2)。
- 空间复杂度:O(1)。
- 稳定性:稳定。
- 希尔排序
- 时间复杂度:最好情况下为 O(n),最坏情况下为 O(n^2),平均情况下为 O(nlogn) 或 O(n^(3/2)),具体取决于间隔序列的选择。
- 空间复杂度:O(1)。
- 稳定性:不稳定。
- 选择排序
- 时间复杂度:最好情况下为 O(n^2),最坏情况下为 O(n^2),平均情况下为 O(n^2)。
- 空间复杂度:O(1)。
- 稳定性:不稳定。
- 堆排序
- 时间复杂度:最好情况下为 O(nlogn),最坏情况下为 O(nlogn),平均情况下为 O(nlogn)。
- 空间复杂度:O(1)。
- 稳定性:不稳定。
- 冒泡排序
- 时间复杂度:最好情况下为 O(n),最坏情况下为 O(n^2),平均情况下为 O(n^2)。
- 空间复杂度:O(1)。
- 稳定性:稳定。
- 快速排序
- 时间复杂度:最好情况下为 O(nlogn),最坏情况下为 O(n^2),平均情况下为 O(nlogn)。
- 空间复杂度:最好情况下为 O(logn),最坏情况下为 O(n),平均情况下为 O(logn)。
- 稳定性:不稳定。
- 归并排序
- 时间复杂度:最好情况下为 O(nlogn),最坏情况下为 O(nlogn),平均情况下为 O(nlogn)。
- 空间复杂度:最好情况下为 O(n),最坏情况下为 O(n),平均情况下为 O(n)。
- 稳定性:稳定。
2.2 具体介绍
2.2.1 插入排序
2.2.1.1 直接插入排序
基本思想:遍历待检查的数据,一个一个地插入合适的位置。
可以具象化理解为:扑克牌一张一张地取新牌并不断插入到已排好的序列中的合适位置。
结合代码实现,可以很好理解:
func insertionSort(arr []int) {
n := len(arr) //插入排序:可理解为用插入方法整理扑克牌
for i := 1; i < n; i++ {
key := arr[i] //每次新拿一张牌
j := i - 1 //在j及其之前有序(已排好),找合适的位置插入,从当后往前检查
for j >= 0 && arr[j] > key {
arr[j+1] = arr[j] //把大于key的值往后移
j-- //从后往前检查
}
arr[j+1] = key //把key插到合适的位置
}
}
2.2.1.2 希尔排序
希尔排序是基于插入排序的一种改进版本,又称缩小增量法。
基本思想:选定一个整数gap,把所有距离为gap的值分在同一组内,并对每一组内的数据进行插入排序。然后改变 gap大小 ,重复上述分组和排序的工作。当 gap 到达 1 时,所有记录在同一组内排好序。
实现图解:
代码实现:
func shellSort(arr []int) {
n := len(arr)
gap := n / 2
for gap > 0 {
for i := gap; i < n; i++ {
key := arr[i]
j := i - gap
for j >= 0 && arr[j] > key {
arr[j+gap] = arr[j]
j -= gap
}
arr[j+gap] = key
}
gap /= 2
}
}
2.2.2 选择排序
2.2.2.1 选择排序
2.2.2.2 堆排序
2.2.3 交换排序
2.2.3.1 冒泡排序
基本流程:每轮从序列的前面开始向后遍历(冒泡),相邻两两比较,大的置后,则较大值会一直往后“沉”,直至遇上更大的值,显然每一轮的操作都能使一个当前最大值“沉底”,沉底后位置即确定,可不参与下一轮的排序。
依照上述的流程分析,显然有:
- 每轮能确保1个当前最大元素“沉底”。
- 那么,对于
n个元素的序列,只需“沉底”n-1轮即可实现排序(最后一个元素只能沉在原地)。
- 那么,对于
- “沉底后”无需再参与下次排序
- 每轮需要参与排序的范围不断缩小。(每轮减一)
代码实现:
func bubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ { //每一轮沉底一个元素,需要n-1轮(最后一个元素只能沉在原地)。
for j := 0; j < n-i-1; j++ { //需要排序的范围不断缩小(沉底数在增多)
if arr[j] > arr[j+1] { //较大的元素往后沉
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
2.2.3.2 快速排序
代码实现:
func quickSort(arr []int) {
if len(arr) <= 1 {//递归的停止条件,一定要写!
return
}
left, right := 0, len(arr)-1
pivot := arr[left] //用于比较的轴
//保证:小于轴的都放轴左边,大于轴的都放轴右边(但不管两边内部的顺序如何)
for left < right { //left在增大,right在减小,一直往中间靠拢检查
for left < right && arr[right] >= pivot { //从右边开始查找第一个小于枢轴的元素
right--
}
arr[left] = arr[right] //找到了小值,甩到左边
for left < right && arr[left] < pivot { //从左边开始查找第一个大于等于枢轴的元素
left++
}
arr[right] = arr[left] //找到了大值,甩到右边
}
arr[left] = pivot //此时left游历到靠中部(轴的位置)
quickSort(arr[:left])
quickSort(arr[left+1:])
}