golang sort.Sort () 排序演算法學習

wangyufeng發表於2020-07-01

堆(heap)

堆通常是一個可以被看做一棵樹的陣列物件。

  • 堆中某個節點的值總是不大於或不小於其父節點的值
  • 堆總是一棵完全二叉樹

將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。

快速排序

快速排序演算法透過多次比較和交換來實現排序,其排序流程如下:

(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接下來的處理過程不是很理解,請大佬多多指教。

參考文獻

快速排序演算法

go語言實現十大經典排序演算法—堆排序

堆排序及GOLANG程式碼實現

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章