前言
冒泡,插入,选择时间复杂度O(n^2),三者中插入比较常用。
归并,快速排序时间复杂度O(nlogn),比插入更加受欢迎。
五种排序最受欢迎的是快速排序,常用程度为:快速排序>归并排序>插入排序>冒泡排序>选择排序
实现
归并排序
package main
import "fmt"
func MergeSort(arr []int) {
n := len(arr)
if n <= 1 {
return
}
MergeSortCore(arr, 0, n-1)
}
func MergeSortCore(arr []int, l, r int) {
if l >= r {
return
}
mid := (l+r) / 2
MergeSortCore(arr, l, mid)
MergeSortCore(arr, mid+1, r)
MergeCore(arr, l, mid, r)
}
func MergeCore(arr []int, left, mid, right int) {
tmp := make([]int, right-left+1)
l, r := left, mid+1
k := 0
for l <= mid && r <= right {
if arr[l] < arr[r] {
tmp[k] = arr[l]
l++
} else {
tmp[k] = arr[r]
r++
}
k++
}
for ;l<=mid;l++ {
tmp[k] = arr[l]
k++
}
for ;r<=right;r++ {
tmp[k] = arr[r]
k++
}
copy(arr[left:right+1], tmp)
}
func main() {
arr := []int{2,4,8,1,3,6,7,5,0}
fmt.Printf("arr : %v\n", arr)
MergeSort(arr)
fmt.Printf("res : %v\n", arr)
}
复杂度分析
- 时间复杂度O(nlogn)。
- 空间复杂度O(n)。
- 稳定排序算法。
- 不是原地排序,相比快速排序此是致命的缺点,所以不如快速排序应用广泛。
快速排序
// 版本 1
package main
import "fmt"
func InsertSort2(m map[int]int) {
l := len(m)
if l <= 1 {
return
}
for i := 1; i < l; i++ {
tmp := m[i]
j := i-1
for ;j >= 0; j-- {
if tmp < m[j] {
m[j+1] = m[j]
} else {
break
}
}
m[j+1] = tmp
}
}
// 简单版,但是空间复杂度比较高
func QuickSort(m []int) []int {
l := len(m)
if l == 0 {
return m
}
item := m[l-1]
left, right := []int{}, []int{}
for i := 0; i < l-1; i++ {
if m[i] < item {
left = append(left, m[i])
} else {
right = append(right, m[i])
}
}
return append(append(QuickSort(left), item), QuickSort(right)...)
}
func QuickSort(m map[int]int, p, r int) {
if r <= p {
return
}
// r 为基准元素
i, j := p, p
for q := p; q <= r-1; q++ {
if m[q] <= m[r] {
i++
j++
continue
} else {
j++
}
if m[j] < m[r] {
m[i], m[j] = m[j], m[i]
i++
}
}
m[i], m[r] = m[r], m[i]
QuickSort(m, p, i-1)
QuickSort(m, i+1, r)
}
func main() {
m := make(map[int]int)
m[0] = 9
m[1] = 2
m[2] = 8
m[3] = 3
m[4] = 7
m[5] = 4
m[6] = 1
//InsertSort2(m)
QuickSort(m, 0, 6)
for i := 0; i < len(m); i++ {
fmt.Printf("%v : %v \n", i, m[i])
}
}
// 版本 2
package main
import (
"fmt"
)
func QuickSort(arr []int) {
n := len(arr)
if n <= 1 {
return
}
QuickSortCore(arr, 0, n-1)
}
func QuickSortCore(arr []int, start, end int) {
if start >= end {
return
}
pivot := partition(arr, start, end)
QuickSortCore(arr,start, pivot-1)
QuickSortCore(arr, pivot+1, end)
}
func partition(arr []int, start, end int) int {
tmp := arr[end]
i, j := start, start // i 维护数组前半段比tmp小的指针,j 遍历整个数组用于跟tmp比较指针
for ;j<=(end-1); j++ {
if arr[j] < tmp {
arr[i], arr[j] = arr[j], arr[i]
i++
}
}
arr[i], arr[end] = arr[end], arr[i]
return i
}
func main() {
arr := []int{2,4,8,1,3,6,7,5,0, 10}
fmt.Printf("arr : %v\n", arr)
QuickSort(arr)
fmt.Printf("res : %v\n", arr)
}
复杂度分析
- 时间复杂度O(nlogn)。
- 原地排序。
- 非稳定排序算法:因为要比较交换位置,如:6,7,10,6,10,4,8,9,5 第一轮比较过后,首位的6被交换到10后面,故不稳定。
比较
归并排序与快排都运用了分治思想,递归实现,思路很相似,而二者处理思路正好想返。
- 归并排序,先分区,在合并同时排序到临时数组,最终copy到原数组(非原地排序)。
- 快速排序,寻找基准点后,先排序(粗略小于基准点在左侧,大于基准点在右侧),然后在分别对应的基准点两个区,在进行排序,分区,最终递归返回。
注意点
- 归并排序,习惯性取中间位置为分区点,分别左右分区,切分区点是归属于左分区,参与最后的递归中“归”时候得比较。
- 快速排序,习惯性取最后一个位置作为比较的基准点,然后通过巧妙的比较交换位置的方式,最终将小与基准点都放在左侧,大于基准点的放在右侧。然后分别对应左侧小与分区,和右侧大于分区分别递归(基准点位置为最终排序好的所在数组位置,不在参与任何运算)运算即可。