概述
排序算法是最经典的算法知识。熟练掌握排序算法思想及其特点并能够熟练地手写代码至关重要。下面介绍几种常见的排序算法:冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序的思想,其代码均采用 Golang 实现。
冒泡排序
算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
算法实现
func BubbleSort(arr []int){
n := len(arr)
for i := n; i > 0; i--{
for j := 0; j < i; j++{
if arr[i] > arr[j]{
arr[i], arr[j] = arr[j], arr[i]
}
}
}
}
// 代码优化:
// 增加一个`swap`的标志,当前一轮没有进行交换时,说明数组已经有序,
// 说明没有必要再进行下一轮的循环了,直接退出
func BubbleSort(arr []int) {
n := len(arr)
flag := true
for i := 0; i < n && flag; i++ {
flag = false
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
flag = true
}
}
}
}
选择排序
算法描述
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 重复第二步,直到所有元素均排序完毕。
算法实现
func SelectSort(arr []int) {
n := len(arr)
for i := 0; i < n; i++ {
minIndex := i
for j := i+1; j < n; j++ {
if arr[j] < arr[minIndex] {
minIndex = j
}
}
if minIndex != i {
arr[minIndex], arr[i] = arr[i], arr[minIndex]
}
}
}
插入排序
算法描述
- 把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。
- 从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。
- 重复上述过程直到最后一个元素被插入有序子数组中。
算法实现
func InsertSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
if arr[i+1] < arr[i] {
for j := i+1; j > 0 && arr[j] < arr[j-1]; j-- {
arr[j], arr[j-1] = arr[j-1], arr[j]
}
}
}
}
归并排序
算法描述
- 将 n 个元素分成个含 n/2 个元素的子序列
- 对步骤 1 中的子序列进行排序,排序后每个子序列包含两/一个元素
- 若此时序列数不是 1 则将上述序列再次归并,形成 n/4 个序列,每个序列包含四/三个元素
- 重复步骤 3 ,直到所有元素排序完毕,即序列数为1
算法实现
func mergeSort(nums []int) []int {
if len(nums) < 2{
// 分治,两两拆分,一直拆到基础元素才向上递归。
return nums
}
i := len(nums) / 2
left := mergeSort(nums[0:i])
// 左侧数据递归拆分
right := mergeSort(nums[i:])
// 右侧数据递归拆分
result := merge(left, right)
// 排序 & 合并
return result
}
func merge(left, right []int) []int {
result := make([]int, 0)
i, j := 0, 0
l, r := len(left), len(right)
for i<l && j<r{
if left[i] > right[j]{
result = append(result, right[j])
j++
}else {
result = append(result, left[i])
i++
}
}
result = append(result, right[j:]...)
result = append(result, left[i:]...)
return result
}
快速排序
算法描述
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法实现
func QuickSort(nums []int, low int, high int) {
if low < high {
pivotloc := partition(nums, low, high)
QuickSort(nums, low, pivotloc-1)
QuickSort(nums, pivotloc+1, high)
}
}
func partition(nums []int, low int, high int) int {
pivotkey := nums[low]
for low < high {
for low < high && nums[high] >= pivotkey {
high--
}
nums[low] = nums[high]
for low < high && nums[low] <= pivotkey {
low++
}
nums[high] = nums[low]
}
nums[low] = pivotkey
return low
}
希尔排序
算法描述
- 取一个小于 N 的整数 d1,将位置是 d1 整数倍的数们分成一组,对这些数进行直接插入排序。
- 接着取一个小于 d1 的整数 d2,将位置是 d2 整数倍的数们分成一组,对这些数进行直接插入排序。
- 接着取一个小于 d2 的整数 d3,将位置是 d3 整数倍的数们分成一组,对这些数进行直接插入排序。
- …
- 直到取到的整数 d=1,接着使用直接插入排序。
算法实现
// 增量序列折半的希尔排序
func SortShell(list []int) {
// 数组长度
n := len(list)
// 每次减半,直到步长为 1
for step := n / 2; step >= 1; step /= 2 {
// 开始插入排序,每一轮的步长为 step
for i := step; i < n; i += step {
for j := i - step; j >= 0; j -= step {
// 满足插入那么交换元素
if list[j+step] < list[j] {
list[j], list[j+step] = list[j+step], list[j]
continue
}
break
}
}
}
}