3.1 sort —— 排序算法

    • 对基本数据类型切片的排序支持
    • 基本数据元素查找
    • 判断基本数据类型切片是否已经排好序
    • 对排好序的数据集合逆序

    前面已经提到过,对数据集合(包括自定义数据类型的集合)排序需要实现 sort.Interface 接口的三个方法,我们看以下该接口的定义:

    数据集合实现了这三个方法后,即可调用该包的 Sort() 方法进行排序。 Sort() 方法定义如下:

    Sort() 方法惟一的参数就是待排序的数据集合。

    该包还提供了一个方法可以判断数据集合是否已经排好顺序,该方法的内部实现依赖于我们自己实现的 Len() 和 Less() 方法:

    1. func IsSorted(data Interface) bool {
    2. n := data.Len()
    3. for i := n - 1; i > 0; i-- {
    4. if data.Less(i, i-1) {
    5. return false
    6. }
    7. }
    8. return true
    9. }

    下面是一个使用 sort 包对学生成绩排序的示例:

    1. package main
    2. import (
    3. "fmt"
    4. "sort"
    5. )
    6. // 学生成绩结构体
    7. type StuScore struct {
    8. name string // 姓名
    9. score int // 成绩
    10. }
    11. type StuScores []StuScore
    12. //Len()
    13. func (s StuScores) Len() int {
    14. return len(s)
    15. }
    16. //Less(): 成绩将有低到高排序
    17. func (s StuScores) Less(i, j int) bool {
    18. return s[i].score < s[j].score
    19. }
    20. //Swap()
    21. func (s StuScores) Swap(i, j int) {
    22. s[i], s[j] = s[j], s[i]
    23. }
    24. func main() {
    25. stus := StuScores{
    26. {"alan", 95},
    27. {"hikerell", 91},
    28. {"acmfly", 96},
    29. {"leao", 90},
    30. }
    31. // 打印未排序的 stus 数据
    32. fmt.Println("Default:\n\t",stus)
    33. //StuScores 已经实现了 sort.Interface 接口 , 所以可以调用 Sort 函数进行排序
    34. sort.Sort(stus)
    35. // 判断是否已经排好顺序,将会打印 true
    36. fmt.Println("IS Sorted?\n\t", sort.IsSorted(stus))
    37. // 打印排序后的 stus 数据
    38. fmt.Println("Sorted:\n\t",stus)
    39. }

    该示例程序的自定义类型 StuScores 实现了 sort.Interface 接口,所以可以将其对象作为 sort.Sort() 和 sort.IsSorted() 的参数传入。运行结果:

    1. Default:
    2. IS Sorted?
    3. true
    4. Sorted:
    5. [{leao 90} {hikerell 91} {alan 95} {acmfly 96}]

    该示例实现的是升序排序,如果要得到降序排序结果,其实只要修改 Less() 函数:

    1. //Less(): 成绩降序排序 , 只将小于号修改为大于号
    2. func (s StuScores) Less(i, j int) bool {
    3. return s[i].score > s[j].score
    4. }

    此外,sort包提供了 Reverse() 方法,可以允许将数据按 Less() 定义的排序方式逆序排序,而不必修改 Less() 代码。方法定义如下:

    1. func Reverse(data Interface) Interface

    我们可以看到 Reverse() 返回的一个 sort.Interface 接口类型,整个 Reverse() 的内部实现比较有趣:

    1. type reverse struct {
    2. Interface
    3. }
    4. //reverse 结构类型的 Less() 方法拥有嵌入的 Less() 方法相反的行为
    5. //Len() 和 Swap() 方法则会保持嵌入类型的方法行为
    6. func (r reverse) Less(i, j int) bool {
    7. return r.Interface.Less(j, i)
    8. }
    9. // 返回新的实现 Interface 接口的数据类型
    10. func Reverse(data Interface) Interface {
    11. return &reverse{data}
    12. }

    了解内部原理后,可以在学生成绩排序示例中使用 Reverse() 来实现成绩升序排序:

    1. sort.Sort(sort.Reverse(stus))
    2. fmt.Println(stus)

    最后一个方法:Search()

    1. func Search(n int, f func(int) bool) int

    Search() 函数一个常用的使用方式是搜索元素 x 是否在已经升序排好的切片 s 中:

    官方文档还给出了一个猜数字的小程序:

    1. func GuessingGame() {
    2. var s string
    3. fmt.Printf("Pick an integer from 0 to 100.\n")
    4. answer := sort.Search(100, func(i int) bool {
    5. fmt.Printf("Is your number <= %d? ", i)
    6. fmt.Scanf("%s", &s)
    7. return s != "" && s[0] == 'y'
    8. })
    9. fmt.Printf("Your number is %d.\n", answer)
    10. }

    前面已经提到,sort包原生支持[]int、[]float64 和[]string 三种内建数据类型切片的排序操作,即不必我们自己实现相关的 Len()、Less() 和 Swap() 方法。

    1. IntSlice 类型及[]int 排序

    sort包定义了一个 IntSlice 类型,并且实现了 sort.Interface 接口:

    1. type IntSlice []int
    2. func (p IntSlice) Len() int { return len(p) }
    3. func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] }
    4. func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    5. //IntSlice 类型定义了 Sort() 方法,包装了 sort.Sort() 函数
    6. func (p IntSlice) Sort() { Sort(p) }
    7. //IntSlice 类型定义了 SearchInts() 方法,包装了 SearchInts() 函数
    8. func (p IntSlice) Search(x int) int { return SearchInts(p, x) }

    并且提供的 sort.Ints() 方法使用了该 IntSlice 类型:

    1. func Ints(a []int) { Sort(IntSlice(a)) }

    所以,对[]int 切片排序更常使用 sort.Ints(),而不是直接使用 IntSlice 类型:

    1. s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
    2. sort.Ints(s)
    3. fmt.Println(s) // 将会输出[1 2 3 4 5 6]

    如果要使用降序排序,显然要用前面提到的 Reverse() 方法:

    1. s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
    2. sort.Sort(sort.Reverse(sort.IntSlice(s)))
    3. fmt.Println(s) // 将会输出[6 5 4 3 2 1]

    如果要查找整数 x 在切片 a 中的位置,相对于前面提到的 Search() 方法,sort包提供了 SearchInts():

    1. func SearchInts(a []int, x int) int

    注意,SearchInts() 的使用条件为:切片 a 已经升序排序 以下是一个错误使用的例子:

    1. s := []int{5, 2, 6, 3, 1, 4} // 未排序的切片数据
    2. fmt.Println(sort.SearchInts(s, 2)) // 将会输出 0 而不是 1

    2. Float64Slice 类型及[]float64 排序

    实现与 Ints 类似,只看一下其内部实现:

    1. type Float64Slice []float64
    2. func (p Float64Slice) Len() int { return len(p) }
    3. func (p Float64Slice) Less(i, j int) bool { return p[i] < p[j] || isNaN(p[i]) && !isNaN(p[j]) }
    4. func (p Float64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    5. func (p Float64Slice) Sort() { Sort(p) }
    6. func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) }

    与 Sort()、IsSorted()、Search() 相对应的三个方法:

    1. func Float64s(a []float64)
    2. func Float64sAreSorted(a []float64) bool
    3. func SearchFloat64s(a []float64, x float64) int

    要说明一下的是,在上面 Float64Slice 类型定义的 Less 方法中,有一个内部函数 isNaN()。 isNaN() 与math包中 IsNaN() 实现完全相同,sort包之所以不使用 math.IsNaN(),完全是基于包依赖性的考虑,应当看到,sort包的实现不依赖与其他任何包。

    3. StringSlice 类型及[]string 排序

    两个 string 对象之间的大小比较是基于“字典序”的。

    实现与 Ints 类似,只看一下其内部实现:

    与 Sort()、IsSorted()、Search() 相对应的三个方法:

    1. func Strings(a []string)
    2. func SearchStrings(a []string, x string) int

    通过前面的内容我们可以知道,只要实现了 sort.Interface 接口,即可通过 sort 包内的函数完成排序,查找等操作。并且 sort 包已经帮我们把[]int,[]float64,[]string 三种类型都实现了该接口,我们可以方便的调用。但是这种用法对于其它数据类型的 slice 不友好,可能我们需要为大量的 struct 定义一个单独的 []struct 类型,再为其实现 sort.Interface 接口,类似这样:

    1. type Person struct {
    2. Name string
    3. Age int
    4. }
    5. func (p Persons) Len() int {
    6. panic("implement me")
    7. }
    8. func (p Persons) Less(i, j int) bool {
    9. panic("implement me")
    10. }
    11. func (p Persons) Swap(i, j int) {
    12. panic("implement me")
    13. }

    思考一个问题:为什么 sort 包可以完成 []int 的排序,而不能完成 []struct 的排序?

    1. func Slice(slice interface{}, less func(i, j int) bool)
    2. func SliceStable(slice interface{}, less func(i, j int) bool)
    3. func SliceIsSorted(slice interface{}, less func(i, j int) bool) bool
    4. func Search(n int, f func(int) bool) int

    通过函数签名可以看到,排序相关的三个函数都接收 []interface,并且需要传入一个比较函数,用于为程序比较两个变量的大小,因为函数签名和作用域的原因,这个函数只能是 匿名函数

    1. sort.Slice

    该函数完成 []interface 的排序,举个栗子:

    1. people := []struct {
    2. Name string
    3. Age int
    4. }{
    5. {"Gopher", 7},
    6. {"Alice", 55},
    7. {"Vera", 24},
    8. {"Bob", 75},
    9. }
    10. sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age }) // 按年龄升序排序
    11. fmt.Println("Sort by age:", people)

    输出结果:

    1. By age: [{Gopher 7} {Vera 24} {Alice 55} {Bob 75}]

    2. sort.SliceStable

    该函数完成 []interface 的稳定排序,举个栗子:

    1. people := []struct {
    2. Name string
    3. Age int
    4. }{
    5. {"Gopher", 7},
    6. {"Alice", 55},
    7. {"Vera", 24},
    8. {"Bob", 75},
    9. }
    10. sort.SliceStable(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
    11. fmt.Println("Sort by age:", people)

    输出结果:

    1. By age: [{Bob 75} {Alice 55} {Vera 24} {Gopher 7}]

    3. sort.SliceIsSorted

    该函数判断 []interface 是否为有序,举个栗子:

    1. people := []struct {
    2. Name string
    3. Age int
    4. }{
    5. {"Gopher", 7},
    6. {"Alice", 55},
    7. {"Vera", 24},
    8. {"Bob", 75},
    9. }
    10. sort.Slice(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
    11. fmt.Println("Sort by age:", people)
    12. fmt.Println("Sorted:",sort.SliceIsSorted(people,func(i, j int) bool { return people[i].Age < people[j].Age }))

    输出结果:

    1. Sort by age: [{Bob 75} {Alice 55} {Vera 24} {Gopher 7}]
    2. Sorted: false

    sort 包没有为 []interface 提供反序函数,但是从 1 和 2 可以看出,我们传入的比较函数已经决定了排序结果是升序还是降序。

    判断 slice 是否为有序,同样取决于我们传入的比较函数,从 3 可以看出,虽然 slice 已经按年龄降序排序,但我们在判断 slice 是否为有序时给的比较函数是判断其是否为升序有序,所以最终得到的结果为 false。

    4. sort.Search

    该函数判断 []interface 是否存在指定元素,举个栗子:

    • 升序 slice

    sort 包为 []int,[]float64,[]string 提供的 Search 函数其实也是调用的该函数,因为该函数是使用的二分查找法,所以要求 slice 为升序排序状态。并且判断条件必须为 >=,这也是官方库提供的三个查找相关函数的的写法。

    举个栗子:

    1. found 21 at index 3 in [2 3 4 21 56 100 200 234]
    • 降序 slice

    如果 slice 是降序状态,而我们又不想将其变为升序,只需将判断条件由 >= 变更为 即可。此处就不给代码了,有兴趣的同学可以自己去尝试一下。推荐采用升序排列及相应的判断条件,与官方函数保持风格一致。

    导航

    • 下一节:index/suffixarray — 后缀数组实现子字符串查询