基于二向切分的内省排序(IntroSort)

505 阅读2分钟

在牛客网提交通过,对应题目

// 入口
func Sort(a []int) {
    l := len(a)
    if l < 2 {
        return
    }
    sort(a, 0, l-1, getMaxDepth(l))
}

// 控制递归最大深度
func getMaxDepth(n int) int {
    var depth int
    for i := n; i > 0; i >>= 1 {
        depth++
    }
    return 2*depth
}

// 递归调用
func sort(a []int, low, high, depth int) {
    // high-low+1 > 12
    if high-low > 11 {
        if depth == 0 {
            // 达到最大递归深度改为使用堆排序
            heapSort(a, low, high)
        } else {
            depth--
            pivotIndex := partition(a, low, high)
            sort(a, low, pivotIndex-1, depth)
            sort(a, pivotIndex+1, high, depth)
        }
    } else {
        // 小区间使用插入排序
        insertionSort(a, low, high)
    }
}

func heapSort(a []int, low, high int) {
    // 处理堆时使用的循环变量基于0计算
    // 读写数组a的元素时的索引要基于low计算
    // l := high-low+1
    // end := l-1
    end := high-low
    for i := (end-1)/2; i >= 0; i-- {
        heapify(a, i, end, low)
    }
    for i := end; i > 0; i-- {
        a[low], a[low+i] = a[low+i], a[low] // a[low]就是a[low+0]
        heapify(a, 0, i-1, low)
    }
}

// 向下调整
func heapify(a []int, b, e, low int) {
    // 处理堆时使用的循环变量基于b计算
    // 读写数组a的元素时的索引要基于low计算
    p := b
    pv := a[low+p]
    for {
        child := 2*p + 1
        if child > e {
            break
        }
        if child+1 <= e && a[low+child+1] > a[low+child] {
            child++
        }
        if pv >= a[low+child] {
            break
        }
        a[low+p] = a[low+child]
        p = child
    }
    a[low+p] = pv
}

func insertionSort(a []int, low, high int) {
    for i := low+1; i <= high; i++ {
        n := a[i]
        j := i-1
        for j >= low && a[j] > n {
            a[j+1] = a[j]
            j--
        }
        a[j+1] = n
    }
}

// 区间索引范围 [low, high]
// 二向切分 或者叫“挖坑填数”
// 返回值:pivot在切分完成后放在的索引位置
func partition(a []int, low, high int) int {
    selectPivot(a, low, high) // 将pivot放在low索引上
    pivot := a[low] // 第一个坑
    for low < high {
        for low < high && a[high] >= pivot {
            high--
        }
        a[low] = a[high]
        for low < high && a[low] <= pivot {
            low++
        }
        a[high] = a[low]
    }
    a[low] = pivot
    return low
}

// 将选出的枢轴元素放在low索引上
func selectPivot(a []int, low, high int) {
    l := high-low+1
    mid := low + (high-low)>>1
    // l <= 40,三数取中
    // l > 40,采用九数取中
    if l > 40 {
        seg := l/8
        medianOfThree(a, low, low+seg, low+2*seg)
        medianOfThree(a, mid, mid-seg, mid+seg)
        medianOfThree(a, high, high-seg, high-2*seg)
    }
    medianOfThree(a, low, mid, high)
}

// 将i1,i0,i2三个索引位置上的数的中位数交换到i1索引上
func medianOfThree(a []int, i1, i0, i2 int) {
    if a[i1] > a[i2] {
        a[i1], a[i2] = a[i2], a[i1]
    }
    if a[i0] > a[i2] {
        a[i0], a[i2] = a[i2], a[i0]
    }
    if a[i1] < a[i0] {
        a[i1], a[i0] = a[i0], a[i1]
    }
}