golang sort.Sort()排序源码学习

堆通常是一个可以被看做一棵树的数组对象。

  • 堆中某个节点的值总是不大于或不小于其父节点的值
  • 堆总是一棵完全二叉树

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

func Sort(data Interface)

排序思想:

  • 当元素数目小于等于12时进行插入排序
  • 当元素数目大于12同时阈值为0时进行堆排序
  • 当元素数目大于12同时阈值不为0时进行快速排序,返回初次排序的分界值后重新进行元素数目判断排序
type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less 比较索引为i的元素a[i]与索引为j的元素a[j],若.a[i]<a[j]返回true,否则返回false.
    Less(i, j int) bool
    // Swap 用索引i和j交换元素.
    Swap(i, j int)
}
  • 调用sort.Sort()进行排序
/**
Sort对数据进行排序
它对data.Len进行一次调用以确定n,对data.Less和data.Swap进行O(n*log(n))调用。 不能保证排序是稳定的。
*/
func Sort(data Interface) {
    n := data.Len()
    quickSort(data, 0, n, maxDepth(n))
}
  • 开始排序
func quickSort(data Interface, a, b, maxDepth int) {
    for b-a > 12 {
        //阈值为0时进行堆排序
        if maxDepth == 0 {
            heapSort(data, a, b)
            return
        }
        maxDepth--
        mlo, mhi := doPivot(data, a, b)
        // 避免对较大的子问题进行递归可确保堆栈深度最多为lg(b-a)。
        if mlo-a < b-mhi {
            quickSort(data, a, mlo, maxDepth)
            a = mhi // i.e., quickSort(data, mhi, b)
        } else {
            quickSort(data, mhi, b, maxDepth)
            b = mlo // i.e., quickSort(data, a, mlo)
        }
    }
    if b-a > 1 {

        /*
        用6做间隔点,它能使用这种简化方式编写是因为b-a<=12
        如果元素大于6,则先将6之后的元素a[j]与索引值j-6相对应的元素a[j-6]进行简单比较,当a[j-6]>a[j]时进行置换,即data.Less(i, i-6)为true
        然后进行插入排序
        */
        for i := a + 6; i < b; i++ {
            if data.Less(i, i-6) {
                data.Swap(i, i-6)
            }
        }
        //插入排序
        insertionSort(data, a, b)
    }
}
  • 当元素数目小于等于12时进行插入排序
// 插入排序
func insertionSort(data Interface, a, b int) {
    //从小到大排序, 从第一个元素开始,与它前面的元素相比较,如果比前面小则置换, 即data.Less(j,j-1)为true
    for i := a + 1; i < b; i++ { 
        for j := i; j > a && data.Less(j, j-1); j-- {
            data.Swap(j, j-1)
        }
    }
}
  • 当元素数目大于12同时maxDepth(阈值)为0时进行堆排序
//siftDown在data [lo,hi]上实现了heap属性。
//first是堆根所在的数组的偏移量。
func siftDown(data Interface, lo, hi, first int) {
    root := lo
    // 开始下沉
    for {
        // 定位左孩子节点位置
        child := 2*root + 1
        if child >= hi {
            break
        }
        // 如果右孩子节点比左孩子大,则定位到右孩子
        if child+1 < hi && data.Less(first+child, first+child+1) {
            child++
        }
        // 如果孩子节点小于或等于父节点,则下沉结束
        if !data.Less(first+root, first+child) {
            return
        }
        // 父节点进行下沉
        data.Swap(first+root, first+child)
        root = child
    }
}
//堆排序
func heapSort(data Interface, a, b int) {
    first := a
    lo := 0
    hi := b - a

    // 构建最大堆.
    for i := (hi - 1) / 2; i >= 0; i-- {
        siftDown(data, i, hi, first)
    }

    // 进行堆排序.
    for i := hi - 1; i >= 0; i-- {
        // 把堆顶元素与最后一个元素交换
        data.Swap(first, first+i)
        // 把打乱的堆进行调整恢复堆的特性
        siftDown(data, lo, i, first)
    }
}
  • 当元素数目大于12时进行快速排序
// meanOfThree将三个值data [m0],data [m1],data [m2]的中值移到data [m1]中。
func medianOfThree(data Interface, m1, m0, m2 int) {
    // 排序3个元素
    /*
    先将data[m0]与data[m1]比较排序,然后data[m1]与data[m2]比较排序,最后再进行一次data[m0]与data[m1]比较排序得到data[m0] <= data[m1] <= data[m2]
    */
    if data.Less(m1, m0) {
        data.Swap(m1, m0)
    }
    // 此时data[m0] <= data[m1]
    if data.Less(m2, m1) {
        data.Swap(m2, m1)
        // 此时data[m0] <= data[m2] && data[m1] < data[m2]
        if data.Less(m1, m0) {
            data.Swap(m1, m0)
        }
    }
    // now data[m0] <= data[m1] <= data[m2]
}

//快速排序
func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
    m := int(uint(lo+hi) >> 1) // 这样写可以避免整数溢出。
    if hi-lo > 40 {
        // Tukey's ``Ninther,'' median of three medians of three.
        s := (hi - lo) / 8
        medianOfThree(data, lo, lo+s, lo+2*s)
        medianOfThree(data, m, m-s, m+s)
        medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
    }
    // 使data[m0] <= data[lo] <= data[hi-1]
    medianOfThree(data, lo, m, hi-1)

    // Invariants are:
    //    data[lo] = pivot (set up by ChoosePivot)
    //    data[lo < i < a] < pivot
    //    data[a <= i < b] <= pivot
    //    data[b <= i < c] unexamined
    //    data[c <= i < hi-1] > pivot
    //    data[hi-1] >= pivot
    pivot := lo
    a, c := lo+1, hi-1
    // 从前往后找出比data[pivot]大的第一个数,获得此时的下标a,a>=c时结束
    for ; a < c && data.Less(a, pivot); a++ {
    }
    b := a
    for {
        // 从前往后找出比data[pivot]大的第一个数,获得此时的下标b,b>=c时结束
        for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
        }
        // 从后往前找出比data[pivot]小的第一个数,获得此时的下标c,b>=c时结束
        for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
        }
        // 当b>=c时结束循环
        if b >= c {
            break
        }
        // data[b] > pivot; data[c-1] <= pivot
        data.Swap(b, c-1)
        b++
        c--
    }
    // 如果hi-c <3,则存在重复项(按中位数9的属性)。
      // 让我们更加保守一些,将border设置为5。
    protect := hi-c < 5
    if !protect && hi-c < (hi-lo)/4 {
        // Lets test some points for equality to pivot
        dups := 0
        if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
            data.Swap(c, hi-1)
            c++
            dups++
        }
        if !data.Less(b-1, pivot) { // data[b-1] = pivot
            b--
            dups++
        }
        // m-lo = (hi-lo)/2 > 6
        // b-lo > (hi-lo)*3/4-1 > 8
        // ==> m < b ==> data[m] <= pivot
        if !data.Less(m, pivot) { // data[m] = pivot
            data.Swap(m, b-1)
            b--
            dups++
        }
        // if at least 2 points are equal to pivot, assume skewed distribution
        protect = dups > 1
    }
    if protect {
        /*
            防止大量重复
                添加不变式:
                    data[a <= i <b]未检查
                    data [b <= i <c] = pivot
        */
        for {
            //从后往前找出比data[pivot]小的第一个数,获得此时的下标b,a>=b时结束 data[b-1]<data[pivot]
            for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
            }
            //从前往后找出比data[pivot]大的第一个数,获得此时的下标b,a>=b时结束 data[a]>data[pivot]
            for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
            }
            if a >= b {
                break
            }
            // data[a] == pivot; data[b-1] < pivot
            data.Swap(a, b-1)
            a++
            b--
        }
    }
    // Swap pivot into middle
    data.Swap(pivot, b-1)
    return b - 1, c
}
  • 计算阈值
/**
maxDepth返回一个阈值,在该阈值处,快速排序应切换为堆排序。返回2*ceil(lg(n+1)).
*/
func maxDepth(n int) int {
    var depth int
    //通过右移后赋值计算n的二进制位数,即ceil(lg(n+1))
    for i := n; i > 0; i >>= 1 {
        depth++
    }
    return depth * 2
}

问题

​ 对快速排序部分protect := hi-c < 5接下来的处理过程不是很理解,请大佬多多指教。

参考文献

本作品采用《CC 协议》,转载必须注明作者和本文链接