go的sort函数,其中用到了插入排序,希尔排序,快速排序,那么就让我们来了解下常见的排序爸
package main
import "fmt"
// 冒泡排序O(n2)
// 每次遍历,依次对相邻的元素比较,不满足就交换
func bubbleSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
flag := false
for j := 0; j < length-i-1; j++ {
if arr[j] > arr[j+1] { // 不写等于,所以是稳定排序算法
arr[j], arr[j+1] = arr[j+1], arr[j]
flag = true
}
}
if flag == false {
return
}
}
}
// 插入排序O(n2)
// 初始已排序区间只有一个元素,取未排序区间元素,在已排序区间中找到合适的位置并插入
func insertSort(arr []int) {
length := len(arr)
for i := 1; i < length; i++ {
cur := arr[i]
j := i - 1
for ; j >= 0; j-- {
if cur < arr[j] { // 同理,不写等号,所以是稳定排序
arr[j+1] = arr[j]
} else {
break
}
}
arr[j+1] = cur // 插入数据
}
}
func insertSortByStep(arr []int, step int) {
length := len(arr)
for i := step; i < length; i += step {
cur := arr[i]
j := i - step
for ; j >= 0; j -= step {
if cur < arr[j] { // 同理,不写等号,所以是稳定排序
arr[j+step] = arr[j]
} else {
break
}
}
arr[j+step] = cur // 插入数据
}
}
// 希尔排序
/*
希尔排序时间复杂度是 O(n^(1.3-2)),空间复杂度为常数阶 O(1)。希尔排序没有时间复杂度为 O(n(logn)) 的快速排序算法快 ,因此对中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O(n^2 ) 复杂度的算法快得多。
希尔排序目的为了加快速度改进了插入排序,交换不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
在此我们选择增量 gap=length/2,缩小增量以 gap = gap/2 的方式,用序列 {n/2,(n/2)/2...1} 来表示。
举个简单例子,希尔排序一个 12 个元素的数列:[5 9 1 6 8 14 6 49 25 4 6 3],增量 d 的取值依次为:6,3,1:
取 d = 6 对 [5 x x x x x 6 x x x x x] 进行直接插入排序
没有变化。
取 d = 3 对 [5 x x 6 x x 6 x x 4 x x] 进行直接插入排序
排完序后:[4 x x 5 x x 6 x x 6 x x]。
取 d = 1 对 [4 9 1 5 8 14 6 49 25 6 6 3] 进行直接插入排序
因为 d=1 完全就是直接插入排序了。
*/
func shellSort(arr []int) {
for gap := len(arr) / 2; gap >= 1; gap = gap / 2 {
insertSortByStep(arr, gap)
}
}
// 选择排序O(n2)
// 每次从未排序的区间内找到min元素,插入到已排序区间的末尾
func selectSort(arr []int) {
length := len(arr)
for i := 0; i < length; i++ {
minPos := i
for j := i; j < length; j++ {
if arr[j] < arr[minPos] {
minPos = j
}
}
// 可以发现,每次找到的min都要交换位置,所以是不稳定排序
arr[i], arr[minPos] = arr[minPos], arr[i]
}
}
// 归并排序O(nlogn) 非稳定算法
// 分治思想,将数据从中间分解为前后两部分,然后对前后两部分分别排序,最后合并在一起
// 递推公式:merge_sort(p,r) = merge(merge_sort(p,q),merge_sort(q+1,r)) q=(r-p)/2 +p
// 终止条件:p>=r,不再分解
func mergeSort(arr []int) []int {
if len(arr) < 2 {
return arr
}
mid := len(arr) / 2
left := mergeSort(arr[0:mid])
right := mergeSort(arr[mid:])
return merge(left, right)
}
func merge(left, right []int) []int {
res := []int{}
i, j := 0, 0
for ; i < len(left) && j < len(right); {
if left[i] <= right[j] {
res = append(res, left[i])
i++
} else {
res = append(res, right[j])
j++
}
}
//left 先放进结果集,就是稳定排序
res = append(res, left[i:]...)
res = append(res, right[j:]...)
return res
}
// 快速排序O(nlogn) 非稳定算法
// 原理:如果要排序数组p到r的数据,则任择p到r的任意一个数据作为pivot(取最后一个元素),小于pivot的放在左边,大于的放在右边,pivot放在中间
// 经过这部后,p到r的数据被分成了3部分,假设pivot的下表为q,p到q-1的数据都小于pivot,q+1到r的数据都大于pivot,而后一直递归,知道待排序区间大小为1
// 递推公式:quick_sort(p,r) = partition(p,r) + quick_sort(p,q-1) + quick_sort(q+1,r) ,终止条件:p>=r
func quickSort(arr []int) {
quick(arr, 0, len(arr)-1)
}
func quick(arr []int, p, r int) {
if p >= r {
return
}
q := partition(arr, p, r)
quick(arr, p, q-1)
quick(arr, q+1, r)
}
// 当然也可以申请两个临时数组,一个放小于的数据,一个放大于的数据,最后合并,但是浪费内存,快排就没有了优势
func partition(arr []int, p, r int) (q int) {
i := p
pivot := arr[r]
for ; p < r; p++ {
if arr[p] < pivot {
arr[i], arr[p] = arr[p], arr[i]
i++
}
}
arr[r], arr[i] = arr[i], arr[r]
return i
}
/*
Q:为什么插入排序比冒泡排序实用性高
A:虽然时间和空间复杂度都一样,但是插入排序每次只是赋值,而冒泡需要交换元素,可以理解为是3次赋值语句
Q:为什么快速排序比归并排序实用性高
A:因为归并排序不是原地排序,需要额外的空间O(n)
*/
func main() {
arr := []int{1, 4, 2, 7, 4, 5, 3, 9,9283,0,-2}
//bubbleSort(arr)
//insertSort(arr)
//insertSortByStep(arr,1)
//shellSort(arr)
//selectSort(arr)
//res := mergeSort(arr)
//fmt.Println(res)
quickSort(arr)
fmt.Println(arr)
}