八大排序算法的Golang实现

3,896 阅读7分钟
算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
冒泡排序O(n2)O(n)O(n2)O(1)稳定
选择排序O(n2)O(n2)O(n2)O(1)不稳定
插入排序O(n2)O(n)O(n2)O(1)稳定
希尔排序O(nlog2n)O(n1.3)O(n2)O(1)不稳定
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳定
快速排序O(nlog2n)O(nlog2n)O(n2)O(log2n)不稳定
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)稳定
桶排序O(n+k)O(n+k)O(n2)O(n+k)稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定

冒泡排序(bubbleSort)

讲解视频

1. 思想

  • 比较相邻元素,如果前者比后者大,则进行位置交换
  • 从第一个开始比较到未确定位置的最后一个,每经过一轮,则代表当前最大的数到达本次最后的位置

2.复杂度分析

时间复杂度

  • 平均:O(n2)
  • 最好:如果数组本来就是有序的,则经过一轮比较即可,共需要比较n-1次,时间复杂度是 O(n)
  • 最坏:如果是倒序,则需要比较 n-1 + n-2 + n-3 +...+1=n(n-1)/2次,为O(n2) 空间复杂度 使用常数个辅助单元O(1)

稳定性 稳定,因为 i>j时,A[i]=A[j],不会进行交换

3.代码实现

package main

import(
    "fmt"
)

func bubbleSort(array []int ){
    //最后一次交换的位置
    lastExchangeIndex := 0;
    //无序数组的边界
    sortBorder := len(array) - 1
    for i := 0; i < len(array)-1; i++ {
        //当前数组是否为有序的标记,初始值是 true
        isSorted := true
        for j := 0; j < sortBorder; j++ {
            //相等时不用交换,所以冒泡排序是稳定的
            if array[j]>array[j+1] {
                array[j],array[j+1]=array[j+1],array[j]
                //发生了交换,所以当前数组还不能确定是有序的
                isSorted = false
                //记录当前交换的位置
                lastExchangeIndex = j;
            }
        }
        sortBorder = lastExchangeIndex
        // 此轮没有发生元素的交换,证明是有序的
        if isSorted{
            break;
        }
    }
}
func main(){
    array:=[]int{5,8,6,3,9,2,1,7}
    bubbleSort(array)
    fmt.Println(array)
}

选择排序(selectionSort)

1.思想

每一次选出最小者直接交换到左侧,省出了多余的元素交换

2.复杂度分析

时间复杂度 平均:O(n2) 最坏:O(n2)

空间复杂度 O(1)

稳定性 不稳定。当数列包含多个值相等的元素时,选择排序有可能打乱它们的原有顺序。

3.代码实现

package main

import(
    "fmt"
)
func selectionSort(array []int){
    for i := 0; i < len(array)-1; i++ {
        //设置当前数组最小的值的索引
        minIndex := i
        for j := i+1; j < len(array); j++ {
            if array[j]<array[minIndex]{
                    //更新索引为当前最小值的索引
                    minIndex = j
            }
        }
        //把最小值放到本次循环的第一位
        array[i],array[minIndex]=array[minIndex],array[i]
    }
}
func main(){
    array:=[]int{5,8,6,3,9,2,1,7}
    selectionSort(array)
    fmt.Println(array)
}

插入排序(InsertionSort)

1.思想

维护一个有序区,把元素一个一个插入到有序区的适当位置,直到所有元素有序为止

2.复杂度分析

时间复杂度 O(n2)

空间复杂度 O(1)

稳定性 稳定

3.代码实现

package main

import(
    "fmt"
)
func InsertionSort(array []int){
    //设array[0] 为有序数组
    for i := 1; i < len(array); i++ {
        //插入数值
        insertValue:=array[i]
        j:=i-1
        for ;j>=0&&array[j]>insertValue; j-- {
            //如果前面的值大于插入值,依次把值向后移动
            array[j+1]=array[j]
        }
        //进行赋值
        array[j+1]=insertValue
    }
}
func main(){
    array:=[]int{5,8,6,3,9,2,1,7}
    InsertionSort(array)
    fmt.Println(array)
}

希尔排序

1.思想

设置希尔增量每次折半,逐步分组进行粗调,最后进行插入排序

2.复杂度分析

时间复杂度

  • 平均:O(nlogn)
  • 最坏:O(n2) + 检查的时间

空间复杂度 O(1)

稳定性 不稳定

3.代码实现

package main

import(
    "fmt"
)
func ShellSort(array []int){
    n:=len(array)
    for gap:=n/2;gap>0;gap=gap/2{
        //使用希尔增量的方式,即每次折半
        for i := gap; i < n; i++ {
            temp:=array[i]
            j:=i-gap
            for j >=0&&array[j]>temp{
                array[j+gap]=array[j]
                j=j-gap
            }
            array[j+gap] = temp
        }
    }
}
func main(){
    array:=[]int{5,3,9,12,6,1,7,2,13,4,11,8,10}
    ShellSort(array)
    fmt.Println(array)
}

快速排序(Quick Sort)

1. 思想 (分治)

快速排序是从冒泡元素演变而来。在快速排序中,元素的比较和交换是从两端向中间进行的,较大的元素一轮就能交换到后面的位置,而较小的元素一轮就能交换到前面的位置,元素每次移动的距离较远,所以比较次数和移动次数较少,速度较快。

  1. 在待排序的元素任取一个元素作为基准(通常选第一个元素,但最好的方法是从待排序元素中随机选取一个为基准),称为基准元素(pivot)
  2. 将待排序的元素进行分区,比基准元素大的元素放在它的右边,比基准元素小的放在它的左边
  3. 对左右两个分区重复以上步骤,直到所有的元素都是有序的

2.复杂度分析

时间复杂度

  • 平均:O(nlogn)
  • 最坏:O(n2) 空间复杂度
  • 平均:O(logn)
  • 最坏:O(n) 稳定性 不稳定,因为基准元素的比较和交换是跳跃进行的

3.代码实现

挖坑法 把基准元素的位置当做一个坑,然后按照右->左->右->左的指针顺序,依次进行比较和填坑。

package main

import(
    "fmt"
)
func quickSort(array []int,startIndex,endIndex int){
    //递归结束条件:startIndex >= endIndex 时
    if startIndex>endIndex{
        return
    }
    //得到基准元素的位置
    pivotIndex:=partition(array,startIndex,endIndex)
    //用分治发递归基准元素的前面和后面两个部分
    quickSort(array,startIndex,pivotIndex-1)
    quickSort(array,pivotIndex+1,endIndex)
}
func partition(array []int,startIndex,endIndex int)int{
    //取第一个元素作为基准元素
    pivot:=array[startIndex]
    left,right:=startIndex,endIndex
    //记录坑的位置,初始值为基准值的位置
    index:=startIndex

    //在左右指针重合 或者 交错的时候结束
    for right>=left{
        //right指针从右向左进行比较
        for right>=left {
            if array[right]<pivot{
                array[left]=array[right]
                index = right
                left++
                break
            }
            right--
        }
        //left指针从左向右进行比较
        for right>=left{
            if array[left]>pivot{
                array[right] = array[left]
                index = left
                right--
                break
            }
            left++
        }
    }
    array[index] = pivot
    return index
}
func main(){
    array:=[]int{4,7,6,5,3,2,8,1}
    quickSort(array,0,len(array)-1)
    fmt.Println(array)
}

指针交换法

package main

import(
    "fmt"
)
func quickSort(array []int,startIndex,endIndex int){
    //递归结束条件:startIndex >= endIndex 时
    if startIndex>endIndex{
        return
    }
    //得到基准元素的位置
    pivotIndex:=partition(array,startIndex,endIndex)
    //用分治发递归基准元素的前面和后面两个部分
    quickSort(array,startIndex,pivotIndex-1)
    quickSort(array,pivotIndex+1,endIndex)
}
func partition(array []int,startIndex,endIndex int)int{
    //取第一个元素作为基准元素
    pivot:=array[startIndex]
    left,right:=startIndex,endIndex

    //在左右指针重合 或者 交错的时候结束
    for right!=left{
        //right指针从右向左进行比较
        for left<right && array[right]>pivot{
            right--
        }
        //left指针从左向右进行比较
        for left<right && array[left]<=pivot{
            left++
        }
        //交换 left 和 right 指向的元素
        if left<right{
            array[left],array[right]=array[right],array[left]
        }
    }
    //pivot和指针重合点交换
    array[left],array[startIndex] = array[startIndex],array[left]
    return left
}
func main(){
    array:=[]int{4,7,6,5,3,2,8,1}
    quickSort(array,0,len(array)-1)
    fmt.Println(array)
}

归并排序

1.思想

最小分组比较,依次合并

  1. 创建一个大集合,长度为两个小集合之和
  2. 从左到右逐一比较两个小集合中的元素,把较小的优先放入大集合

2.复杂度分析

时间复杂度 O(nlogn) 空间复杂度 O(n)

3.代码实现

package main

import(
    "fmt"
)
func mergeSort(array []int,start,end int){
    if start<end{
        //0 1 2 3 4 5 6 7
        //5,8,6,3,9,2,1,7
        mid:=(start+end)/2
        //拆成两个小集合,分别进行递归
        mergeSort(array,start,mid)
        mergeSort(array,mid+1,end)
        //把两个有序的小集合,归并成一个大集合

        merge(array,start,mid,end)
    }
}
func merge(array []int,start,mid,end int){
    //开辟大集合
    tempArray:=make([]int,end-start+1)
    //设置指针
    p1 := start
    p2 := mid +1
    p :=0
    //比较两个小集合的元素,依次放入大集合
    for p1<=mid && p2<=end {
        if array[p1]<=array[p2]{
            tempArray[p]=array[p1]
            p1++
        }else{
            tempArray[p]=array[p2]
            p2++
        }
        p++
    }
    //如果左侧还有剩余,依次放入尾部
    for p1<=mid{
        tempArray[p]=array[p1]
        p++
        p1++
    }
    //如果右侧还有剩余,依次放入尾部
    for p2<=end {
        tempArray[p]=array[p2]
        p++
        p2++
    }
    //把大集合的元素复制回原数组
    for i := 0; i < len(tempArray); i++ {
        array[i+start]=tempArray[i]
    }
}
func main(){
    array:=[]int{5,8,6,3,9,2,1,7}
    mergeSort(array,0,len(array)-1)
    fmt.Println(array)
}

堆排序

讲解视频

1.思想

本质是完全二叉树,根节点为最大值或者最小值

2.复杂度分析

时间复杂度 O(nlogn)

空间复杂度 O(n)

3.代码实现

二叉堆的构建

package main

import(
	"fmt"
)
//插入(上浮)
func upAdjust(array []int){
	childIndex := len(array)-1
	parentIndex := (childIndex-1)/2
	//保存插入叶子节点值,用于最后的赋值
	temp:=array[childIndex]

	for childIndex>0 && temp < array[parentIndex]{
            //单项赋值,不用交换
            array[childIndex] = array[parentIndex]
            childIndex = parentIndex
            parentIndex = (childIndex-1)/2
	}
	array[childIndex] = temp
}

//删除(下沉)
func downAdjust(array []int,parentIndex,length int){
    //保存父节点的值,用于最后的赋值
    temp:= array[parentIndex]
    childIndex:=2*parentIndex+1
    for childIndex<length{
        //如果存在右孩子,并且右孩子小于左孩子的值,定位到右孩子
        if childIndex+1<length && array[childIndex+1]<array[childIndex] {
            childIndex++
        }
        //如果父节点小于任何一个孩子的值,直接退出
        if temp<=array[childIndex]{
            break
        }
        array[parentIndex] = array[childIndex]
        parentIndex = childIndex
        childIndex = 2*childIndex+1
    }
    array[parentIndex] = temp
}

//构建最小二叉堆
func buildHeap(array []int ){
    for i := (len(array)-2)/2; i >=0; i-- {
        downAdjust(array,i,len(array))
    }
}

func main(){
    array:=[]int{1,3,2,6,5,7,8,9,10,0}
    upAdjust(array)
    fmt.Println(array)

    array = []int{7,1,3,10,5,2,8,9,6}
    buildHeap(array)
    fmt.Println(array)
}

堆排序

//待补充

桶排序

1.思想

桶排序就是把数值按照范围进行划分,把数值依次放入一个个划分的范围内,称之为 ,然后在桶内进行排序,然后依次输出每个桶的值。

2.复杂度分析

时间复杂度 O(n+k)

空间复杂度 O(n+k)

稳定

3.代码实现

package main

import (
	"fmt"
	"sort"
)
func bucketSort(array []int){
    //1.找到最小值和最大值
    min,max:=1000000,0
    for _,v :=range array{
        if(v>max){
            max = v
        }
        if(v<min){
            min =v
        }
    }
    //2.初始化桶
    bucketNum := (max-min)/len(array)+1
    var bucketList [][]int
    for i := 0; i < bucketNum; i++ {
        bucketItem := make([]int,0)
        bucketList = append(bucketList,bucketItem)
    }
    //3.把元素放到桶内
    for _,v:=range array{
        number:= (v-min)/len(array)
        bucketList[number]=append(bucketList[number],v)
    }
    //4.桶内进行排序
    for _,v := range bucketList{
        sort.Ints(v)
    }
    ///5.依次输出
    index:=0
    for _,v:=range bucketList{
        for _,m:=range v{
            array[index] = m
            index++
        }
    }
}
func main(){
    array:=[]int{5,16,95,1,45,66,75,20,62,38,76,94,43}
    bucketSort(array)
    fmt.Println(array)
}