1. 程式人生 > 其它 >Go 語言標準庫之 sort 包

Go 語言標準庫之 sort 包

sort 包中實現了 5 種基本的排序演算法:插入排序、希爾排序、堆排序、快速排序和歸併排序,和其他語言中一樣,這 5 種演算法都是不公開的,它們只在 sort 包內部使用。

sort.Interface 介面

實現了sort.interface的型別(一般為集合),都可以使用 sort 包中函式進行排序 ,sort.Interface介面定義如下:

type Interface interface {
    // Len 方法返回集合中的元素個數
    Len() int
    // Less 方法報告索引 i 的元素是否比索引 j 的元素小
    Less(i, j int) bool
    // Swap 方法交換索引 i 和 j 的兩個元素
    Swap(i, j int)
}

自定義型別排序

Sort/Stable 函式

// 對 data 進行排序,不保證穩定性,即相等元素的相對次序可能改變
// Sort 呼叫 1 次 data.Len 確定長度,呼叫 O(n*log(n)) 次 data.Less 和 data.Swap
func Sort(data Interface)

// 對 data 進行排序,保證排序的穩定性,即相等元素的相對次序不變
// Sort 呼叫 1 次 data.Len,O(n*log(n)) 次 data.Less 和 O(n*log(n)*log(n)) 次 data.Swap
func Stable(data Interface)

// 包裝一個 sort.Interface 介面並返回一個新的 sort.Interface 介面,對該介面排序可生成遞減序列
func Reverse(data Interface) Interface

// 判斷 data 是否已經被排序
func IsSorted(data Interface) bool

示例程式碼如下:

type Student struct {
    Name string
    Age  int
}

type StudentSlice []Student

// 實現 sort.Interface 介面
func (ss StudentSlice) Len() int {
    return len(ss)
}

func (ss StudentSlice) Less(i, j int) bool {
    return ss[i].Age < ss[j].Age
}

func (ss StudentSlice) Swap(i, j int) {
    ss[i], ss[j] = ss[j], ss[i]
}

func main() {
    ss := StudentSlice{
        {"Alice", 18},
        {"Bob", 20},
        {"Allen", 16},
        {"Michelle", 18},
        {"James", 24},
    }
    // 按照年齡升序排列
    sort.Sort(ss)
    fmt.Println(ss)                // [{Allen 16} {Alice 18} {Michelle 18} {Bob 20} {James 24}]
    fmt.Println(sort.IsSorted(ss)) // true

    // 按照年齡降序排列
    sort.Sort(sort.Reverse(ss))
    fmt.Println(ss)                // [{James 24} {Bob 20} {Alice 18} {Michelle 18} {Allen 16}]
    fmt.Println(sort.IsSorted(ss)) // false
}

Slice/SliceStable 函式

在前面Sort/Stable函式的排序中,自定義型別的切片都需要實現sort.Interface介面,在程式碼上不太簡潔。Go 提供Slice/SliceStable函式只需傳入一個待排序的切片,一個回撥函式即可進行排序。需要注意,除了下列三個函式,sort 包下其它所有函式的入參物件x interface{}都需要實現sort.Interface介面,原因後文會分析。

// 使用 less 函式定義的規則對切片 x 進行排序,不保證排序的穩定性,x 不是切片會報 panic
func Slice(x interface{}, less func(i, j int) bool)

// 使用 less 函式定義的規則對切片 x 進行排序,保證排序的穩定性,x 不是切片會報 panic
func SliceStable(x interface{}, less func(i, j int) bool)

// 判斷切片 x 根據 less 函式定義的規則是否是有序的,x 不是切片會報 panic
func SliceIsSorted(x interface{}, less func(i, j int) bool)

示例程式碼如下:

type Student struct {
    Name string
    Age  int
}

func main() {
    ss := []Student{
        {"Alice", 18},
        {"Bob", 20},
        {"Allen", 16},
        {"Michelle", 18},
        {"James", 24},
    }

    less := func(i, j int) bool {
        return ss[i].Age < ss[j].Age
    }
    // 按照年齡升序排列
    sort.Slice(ss, less)
    fmt.Println(ss)                           // [{Allen 16} {Alice 18} {Michelle 18} {Bob 20} {James 24}]
    fmt.Println(sort.SliceIsSorted(ss, less)) // true
}

基本資料型別排序

對於 int、float64 和 string 型別的切片,Go 語言進行了封裝,可以直接呼叫 sort 包函式進行排序。

int 型別排序

// 在 sort 包下,Go 語言定義了 IntSlice 型別實現 sort.Interface 介面,用於 []int 排序
type IntSlice []int

// 實現 Interface 介面
func (x IntSlice) Len() int           { return len(x) }
func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x IntSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// 等價呼叫 sort.Sort(x)
func (x IntSlice) Sort() { Sort(x) }

// 等價呼叫 sort.SearchInts(x, p)
func (x IntSlice) Search(p int) int { return SearchInts(x, p) }
// 對 x 進行升序排序
func Ints(x []int)

// 判斷 x 是否為升序
func IntsAreSorted(x []int)

// 在升序順序的 a 中搜索 x,返回 x 的索引
// 如果查詢不到,返回值是 x 應該插入 a 的位置(以保證 a 的遞增順序),返回值可以是 len(a)
func SearchInts(a []int, x int) int

示例程式碼如下:

func main() {
    // 升序方式一:
    intList1 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Ints(intList1)
    fmt.Println(intList1) // [0 0 1 1 2 2 2 4 8 9]
    // 升序方式二:
    intList2 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Sort(sort.IntSlice(intList2))
    fmt.Println(intList2) // [0 0 1 1 2 2 2 4 8 9]
    // 升序方法三:
    intList3 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    (sort.IntSlice(intList3)).Sort()
    fmt.Println(intList3) // [0 0 1 1 2 2 2 4 8 9]

    // 降序方式:
    intList4 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Sort(sort.Reverse(sort.IntSlice(intList4)))
    fmt.Println(intList4) // [9 8 4 2 2 2 1 1 0 0]

    intList5 := []int{0, 0, 1, 1, 2, 2, 2, 4, 8, 9}
    // 搜尋方式一:
    fmt.Println(sort.SearchInts(intList5, 2)) // 4
    // 搜尋方式二:
    fmt.Println(sort.IntSlice(intList5).Search(2)) // 4
}

float64 型別排序

// 在 sort 包下,Go 語言定義了 Float64Slice 型別實現 sort.Interface 介面,用於 []float64 排序
type Float64Slice []float64

// 實現 Interface 介面
func (x Float64Slice) Len() int { return len(x) }
func (x Float64Slice) Less(i, j int) bool { return x[i] < x[j] || (isNaN(x[i]) && !isNaN(x[j])) }
func (x Float64Slice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// 等價呼叫 sort.Sort(x)
func (x Float64Slice) Sort() { Sort(x) }

// 等價呼叫 sort.SearchFloat64s(x, p)
func (x Float64Slice) Search(p float64) int { return SearchFloat64s(x, p) }
// 對 x 進行升序排序
func Float64s(x []float64)

// 判斷 x 是否為升序
func Float64sAreSorted(x []float64)

// 在升序順序的 a 中搜索 x,返回 x 的索引
// 如果查詢不到,返回值是 x 應該插入 a 的位置(以保證 a 的遞增順序),返回值可以是 len(a)
func SearchFloat64s(a []float64, x float64) int

示例程式碼如下:

func main() {
    // 升序方式一:
    floatList1 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Float64s(floatList1)
    fmt.Println(floatList1) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]
    // 升序方式二:
    floatList2 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Sort(sort.Float64Slice(floatList2))
    fmt.Println(floatList2) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]
    // 升序方法三:
    floatList3 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    (sort.Float64Slice(floatList3)).Sort()
    fmt.Println(floatList3) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]

    // 降序方式:
    floatList4 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Sort(sort.Reverse(sort.Float64Slice(floatList4)))
    fmt.Println(floatList4) // [99.9 50.4 31.4 27.81828 12.3 10 5.9 4.2 3.14]

    floatList5 := []float64{3.14, 4.2, 5.9, 10, 12.3, 27.81828, 31.4, 50.4, 99.9}
    // 搜尋方式一:
    fmt.Println(sort.SearchFloat64s(floatList5, 12.3)) // 4
    // 搜尋方式二:
    fmt.Println(sort.Float64Slice(floatList5).Search(12.3)) // 4
}

string 型別排序

// 在 sort 包下,Go 語言定義了 StringSlice 型別實現 sort.Interface 介面,用於 []string 排序
type StringSlice []string

// 實現 Interface 介面
func (x StringSlice) Len() int           { return len(x) }
func (x StringSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x StringSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// 等價呼叫 sort.Sort(x)
func (x StringSlice) Sort() { Sort(x) } 

// 等價呼叫 sort.SearchStrings(x, p)
func (x StringSlice) Search(p string) int { return SearchStrings(x, p) }
// 對 x 進行升序排序
func Strings(x []string)

// 判斷 x 是否為升序
func StringsAreSorted(x []string)

// 在升序順序的 a 中搜索 x,返回 x 的索引
// 如果查詢不到,返回值是 x 應該插入 a 的位置(以保證 a 的遞增順序),返回值可以是 len(a)
func SearchStrings(a []string, x string) int

示例程式碼如下:

func main() {
    // 升序方式一:
    stringList1 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Strings(stringList1)
    fmt.Printf("%q\n", stringList1) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]
    // 升序方式二:
    stringList2 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Sort(sort.StringSlice(stringList2))
    fmt.Printf("%q\n", stringList2) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]
    // 升序方式三:
    stringList3 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    (sort.StringSlice(stringList3)).Sort()
    fmt.Printf("%q\n", stringList3) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]

    // 降序方式:
    stringList4 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Sort(sort.Reverse(sort.StringSlice(stringList4)))
    fmt.Printf("%q\n", stringList4) // ["y" "o" "o" "o" "n" "l" "l" "g" "g" "d" "b" "a"]

    stringList5 := []string{"a", "b", "d", "g", "g", "l", "l", "n", "o", "o", "o", "y"}
    // 搜尋方式一:
    fmt.Println(sort.SearchStrings(stringList5, "l")) // 5
    // 搜尋方式二:
    fmt.Println((sort.StringSlice(stringList5)).Search("l")) // 5
}

底層實現分析

sort.Sort 函式

// Sort 函式是一個不穩定方式排序,使用希爾快速、快速排序或者堆排序三種排序方式之一
func Sort(data Interface) {
    n := data.Len()
    // maxDepth(n) 返回 2*ceil(lg(n+1)),元素深度達到 2*ceil(lg(n+1)) 則選用堆排序
    quickSort(data, 0, n, maxDepth(n))
}
// quickSort 會根據與元素深度自動選擇使用希爾快速、快速排序或者堆排序三種排序方式之一
func quickSort(data Interface, a, b, maxDepth int) {
    for b-a > 12 {
        if maxDepth == 0 { // Use ShellSort for slices <= 12 elements
            // 使用堆排序
            heapSort(data, a, b)
            return
        }
        maxDepth--
        // 使用三向切分快速排序,通過 doPivot 進行快排的分割槽
        mlo, mhi := doPivot(data, a, b)
        // Avoiding recursion on the larger subproblem guarantees
        // a stack depth of at most 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)
        }
    }
    // 切片元素小於等於 12 個,使用希爾排序(希爾排序是插入排序的高效版本),間隔 d=6
    if b-a > 1 {
        // Do ShellSort pass with gap 6
        // It could be written in this simplified form cause b-a <= 12
        for i := a + 6; i < b; i++ {
            if data.Less(i, i-6) {
                data.Swap(i, i-6)
            }
        }
        insertionSort(data, a, b)
    }
}

// 堆排序
func heapSort(data Interface, a, b int) {
    first := a
    lo := 0
    hi := b - a

    // Build heap with greatest element at top.
    for i := (hi - 1) / 2; i >= 0; i-- {
        siftDown(data, i, hi, first)
    }

    // Pop elements, largest first, into end of data.
    for i := hi - 1; i >= 0; i-- {
        data.Swap(first, first+i)
        siftDown(data, lo, i, first)
    }
}

// siftDown implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
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
    }
}

// 插入排序
// insertionSort sorts data[a:b] using insertion sort.
func insertionSort(data Interface, a, b int) {
    for i := a + 1; i < b; i++ {
        for j := i; j > a && data.Less(j, j-1); j-- {
            data.Swap(j, j-1)
        }
    }
}

sort.Stable 函式

// Stable 是一個穩定方式的排序,使用歸併排序
func Stable(data Interface) {
    stable(data, data.Len())
}
// 這裡用到的歸併排序演算法是一種原址排序演算法:
// 首先,它把 slice 按照每 blockSize=20 個元素為一個 slice,進行插入排序
// 迴圈合併相鄰的兩個 block,每次迴圈 blockSize 擴大二倍,直到 blockSize > n 為止
func stable(data Interface, n int) {
    // 初始 blockSize 設定為 20
    blockSize := 20 // must be > 0
    a, b := 0, blockSize
    // 對每個塊(以及剩餘不足blockSize的一個塊)進行插入排序
    for b <= n {
        insertionSort(data, a, b)
        a = b
        b += blockSize
    }
    insertionSort(data, a, n)
    
    for blockSize < n {
        a, b = 0, 2*blockSize
        for b <= n {
            // 每兩個 blockSize 進行合併
            symMerge(data, a, a+blockSize, b)
            a = b
            b += 2 * blockSize
        }
        // 剩餘一個多 blockSize 進行合併
        if m := a + blockSize; m < n {
            symMerge(data, a, m, n)
        }
        blockSize *= 2
    }
}

func symMerge(data Interface, a, m, b int) {
    // Avoid unnecessary recursions of symMerge
    // by direct insertion of data[a] into data[m:b]
    // if data[a:m] only contains one element.
    if m-a == 1 {
        // Use binary search to find the lowest index i
        // such that data[i] >= data[a] for m <= i < b.
        // Exit the search loop with i == b in case no such index exists.
        i := m
        j := b
        for i < j {
            h := int(uint(i+j) >> 1)
            if data.Less(h, a) {
                i = h + 1
            } else {
                j = h
            }
        }
        // Swap values until data[a] reaches the position before i.
        for k := a; k < i-1; k++ {
            data.Swap(k, k+1)
        }
        return
    }

    // Avoid unnecessary recursions of symMerge
    // by direct insertion of data[m] into data[a:m]
    // if data[m:b] only contains one element.
    if b-m == 1 {
        // Use binary search to find the lowest index i
        // such that data[i] > data[m] for a <= i < m.
        // Exit the search loop with i == m in case no such index exists.
        i := a
        j := m
        for i < j {
            h := int(uint(i+j) >> 1)
            if !data.Less(m, h) {
                i = h + 1
            } else {
                j = h
            }
        }
        // Swap values until data[m] reaches the position i.
        for k := m; k > i; k-- {
            data.Swap(k, k-1)
        }
        return
    }

    mid := int(uint(a+b) >> 1)
    n := mid + m
    var start, r int
    if m > mid {
        start = n - b
        r = mid
    } else {
        start = a
        r = m
    }
    p := n - 1

    for start < r {
        c := int(uint(start+r) >> 1)
        if !data.Less(p-c, c) {
            start = c + 1
        } else {
            r = c
        }
    }

    end := n - start
    if start < m && m < end {
        rotate(data, start, m, end)
    }
    if a < start && start < mid {
        symMerge(data, a, start, mid)
    }
    if mid < end && end < b {
        symMerge(data, mid, end, b)
    }
}

sort.Slice 函式

// Slice 函式是一個不穩定方式排序,使用希爾快速、快速排序或者堆排序三種排序方式之一
// Slice sorts the slice x given the provided less function.
// It panics if x is not a slice.
//
// The sort is not guaranteed to be stable: equal elements
// may be reversed from their original order.
// For a stable sort, use SliceStable.
//
// The less function must satisfy the same requirements as
// the Interface type's Less method.
func Slice(x interface{}, less func(i, j int) bool) {
    rv := reflectValueOf(x)
    swap := reflectSwapper(x)
    length := rv.Len()
    // maxDepth(n) 返回 2*ceil(lg(n+1)),元素深度達到 2*ceil(lg(n+1)) 則選用堆排序
    quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
}

// lessSwap is a pair of Less and Swap function for use with the
// auto-generated func-optimized variant of sort.go in
// zfuncversion.go.
type lessSwap struct {
    Less func(i, j int) bool
    Swap func(i, j int)
}
// quickSort_func 會根據與元素深度自動選擇使用希爾快速、快速排序或者堆排序三種排序方式之一
// Auto-generated variant of sort.go:quickSort
func quickSort_func(data lessSwap, a, b, maxDepth int) {
    for b-a > 12 {
        if maxDepth == 0 {
            // 使用堆排序
            heapSort_func(data, a, b)
            return
        }
        maxDepth--
        // 使用三向切分快速排序,通過 doPivot_func 進行快排的分割槽
        mlo, mhi := doPivot_func(data, a, b)
        if mlo-a < b-mhi {
            quickSort_func(data, a, mlo, maxDepth)
            a = mhi
        } else {
            quickSort_func(data, mhi, b, maxDepth)
            b = mlo
        }
    }
    // 切片元素小於等於 12 個,使用希爾排序(希爾排序是插入排序的高效版本),間隔 d=6
    if b-a > 1 {
        for i := a + 6; i < b; i++ {
            if data.Less(i, i-6) {
                data.Swap(i, i-6)
            }
        }
        insertionSort_func(data, a, b)
    }
}

從上面可以看到,sort.Slice呼叫的排序演算法函式quickSort_func的入參是data lessSwap,而不是data Interface,呼叫的插入排序、堆排序、快速排序函式也和sort.Sort不同。sort.Slicesort.Sort的實現原理是相同的,區別在於sort.Sort通過傳入實現sort.Interface介面的物件獲得Len()/Swap()/Less()sort.Slice則是通過反射的方式獲取Len()Swap()Less()通過傳參獲得。因此,sort.Slice傳入的物件不需要實現sort.Interface介面。


sort.SliceStable 函式

// SliceStable 是一個穩定方式的排序,使用歸併排序
// SliceStable sorts the slice x using the provided less
// function, keeping equal elements in their original order.
// It panics if x is not a slice.
//
// The less function must satisfy the same requirements as
// the Interface type's Less method.
func SliceStable(x interface{}, less func(i, j int) bool) {
    rv := reflectValueOf(x)
    swap := reflectSwapper(x)
    stable_func(lessSwap{less, swap}, rv.Len())
}

// lessSwap is a pair of Less and Swap function for use with the
// auto-generated func-optimized variant of sort.go in
// zfuncversion.go.
type lessSwap struct {
    Less func(i, j int) bool
    Swap func(i, j int)
}
// 這裡用到的歸併排序演算法是一種原址排序演算法:
// 首先,它把 slice 按照每 blockSize=20 個元素為一個 slice,進行插入排序
// 迴圈合併相鄰的兩個 block,每次迴圈 blockSize 擴大二倍,直到 blockSize > n 為止
// Auto-generated variant of sort.go:stable
func stable_func(data lessSwap, n int) {
    blockSize := 20  // 初始 blockSize 設定為 20
    a, b := 0, blockSize
    // 對每個塊(以及剩餘不足blockSize的一個塊)進行插入排序
    for b <= n {
        insertionSort_func(data, a, b)
        a = b
        b += blockSize
    }
    insertionSort_func(data, a, n)
    for blockSize < n {
        a, b = 0, 2*blockSize
        for b <= n {
            // 每兩個 blockSize 進行合併
            symMerge_func(data, a, a+blockSize, b)
            a = b
            b += 2 * blockSize
        }
        // 剩餘一個多 blockSize 進行合併
        if m := a + blockSize; m < n {
            symMerge_func(data, a, m, n)
        }
        blockSize *= 2
    }
}

sort.Slice一樣,sort.SliceStable函式的入參是data lessSwap,不是data Interface,通過反射的方式獲取Len()Swap()Less()通過傳參獲得。


參考

  1. 常見排序演算法總結和 Go 標準庫排序原始碼分析
  2. go語言中sort包的實現方法與應用詳解