[Golang 修仙之路] 算法专题:十大排序

64 阅读3分钟

参考的:blog.csdn.net/qq_34411783…

还有DeepSeek

image.png

image.png

1. 简单三兄弟:插入、冒泡、选择

平均时间复杂度:O(N2)O(N^2)

空间复杂度:O(1)O(1)

只有选择不稳

1.1 插入排序

右侧数组已经有序,新元素插入到有序数组中对应位置。

动图去看文章置顶博客。

稳定吗?稳定!每次插入,遇到相等的,直接放在后面,就可以实现稳定。

代码:

func InsertSort(nums []int) {
    n := len(nums)
    for i := 1; i < n; i++ {
        cur := nums[i]
        j := i - 1
        for j >= 0 && nums[j] > cur {
            nums[j+1] = nums[j]
            j--
        }
        // 结束循环后,nums[j]一定是小于等于cur的,所以j+1
        nums[j+1] = cur
    }
}

1.2 冒泡

稳定吗?稳定,等于的时候不交换,就稳定。

func BubbleSort(a []int) {
    n := len(a)
    for i := 0; i < n - 1; i++ {
        swaped := false
        for j := 0; j + 1 < n - i; j++ {
            if a[j] > a[j+1] {
                a[j], a[j+1] = a[j+1], a[j]
                swaped = true
            }
        }
        if !swaped {
            break
        }
    }
}

1.3 选择

选择排序不稳定!记住这个【5 8 5 2 9】

image.png

func SelectSort(a []int) {
    n := len(a)
    for i := 0; i < n - 1; i++ {
        minIdx := i
        for j := i + 1; j < n; j++ {
            if a[j] < a[minIdx] {
                minIdx = j
            }
        }
        a[i], a[minIdx] = a[minIdx], a[i]
    }
}

2. 速度三兄弟:快排、堆排、归并

2.1 快排

func sortArray(nums []int) []int {
    quickSort(nums, 0, len(nums)-1)
    return nums
}

func quickSort(a []int, l, r int) {
    if l >= r {
        return
    }
    i, j, p := l, r, a[l]
    for i < j {
        for i < j && a[j] >= p {
            j--
        }
        for i < j && a[i] <= p {
            i++
        }
        a[i], a[j] = a[j], a[i]
    }
    a[i], a[l] = a[l], a[i]
    quickSort(a, l, i-1)
    quickSort(a, i+1, r)
}

2.2 堆排

思路

下沉操作

  • 必须: 左子树是大根堆, 右子树也是大根堆. 对root节点调用sink一次, 可以让以root为根节点的树也变成大根堆.
  • 具体做法: 根节点和左右孩子中较大的交换. 递归这个过程. 直到根节点是最大的.

构建大根堆

  • 叶子结点是大根堆.
  • 从最后一个非叶子结点开始, 从下往上构建.

利用大根堆排序

  • 每次把「堆顶」的节点与数组最后一个节点交换, 然后重新构造大根堆. 重复这一过程直到数组有序.
代码
func sortArray(nums []int) []int {
    heapSort(nums)
    return nums
}

func heapSort(arr []int) {
    end := len(arr)-1
    // 从最后一个非叶子节点开始, 构建大根堆
    for i := end/2; i >= 0; i-- {
        sink(arr, i, end)
    }
    // 把最大的放到数组最后, 然后重新构建大根堆
    for i := end; i >= 0; i-- {
        arr[0], arr[i] = arr[i], arr[0]
        end--
        sink(arr, 0, end)
    }
}

// 大根堆的下沉操作
func sink(arr []int, root, end int) {
    for {
        leftChild := root*2+1
        if leftChild > end {
            break
        }
        rightChild := leftChild+1
        larger := leftChild
        // 找到左右孩子中较大的
        if rightChild <= end && arr[rightChild] > arr[leftChild] {
            larger = rightChild
        }
        // root最大, 就结束. 因为构建是从下往上的
        if arr[root] > arr[larger] {
            break
        }
        // root与较大的交换
        arr[root], arr[larger] = arr[larger], arr[root]
        // 继续下沉
        root = larger
    }
}

2.3 归并

归并是我的老朋友了。

func sortArray(nums []int) []int {
    return proc(nums, 0, len(nums)-1)
}

// proc 我采用的是左闭右闭,实践证明,可行
func proc(nums []int, l, r int) []int {
    if l == r {
        return nums[l:l+1]
    }
    mid := (l + r) / 2
    left := proc(nums, l, mid)
    right := proc(nums, mid+1, r)
    return merge(left, right)

}

// merge 上来先写一个
func merge(a, b []int) []int {
    lenA, lenB := len(a), len(b)
    c := make([]int, lenA + lenB)
    i, j, k := 0, 0, 0
    for i < lenA && j < lenB {
        if a[i] < b[j] {
            c[k] = a[i]
            i++
        } else {
            c[k] = b[j]
            j++
        }
        k++
    }
    for i < lenA {
        c[k] = a[i]
        i++
        k++
    }
    for j < lenB {
        c[k] = b[j]
        j++
        k++
    }
    return c
}

3. 空间三兄弟:桶排序、计数排序、基数排序