Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场

Go Sort

作者头像
一行舟
发布于 2022-08-25 06:14:25
发布于 2022-08-25 06:14:25
67500
代码可运行
举报
文章被收录于专栏:一行舟一行舟
运行总次数:0
代码可运行

Hi,我是行舟,今天和大家一起学习 Go 语言的包:sort

sort 包中主要包含了排序和搜索的方法。

排序方法

Ints、Float64s、Strings

sort 包中定义了常见类型的排序方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Ints(x []int)    // 对 int 类型的切片排序
func Float64s(x []float64)    // 对 float64 类型的切片排序
func Strings(x []string)    // 对 string 类型的切片排序

Ints为例,我们要排序一个 int 类型的数组,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
arr := []int{1, 6, 3, 1}
sort.Ints(arr)
fmt.Println(arr) // [1 1 3 6]

Float64s 和 Strings 的使用方法与 Ints 相同。

默认的排序方法只支持递增排序。

其它类型

如果不属于 int、float64、string 三种类型的切片,可以使用 Sort 方法排序。

Sort 方法接收 sort.Interface 接口类型的参数。sort.Interface 定义了三个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Interface interface {
        // 获取集合中元素的长度
 Len() int
        // 下标i、j位置的元素是否要互换位置。
 Less(i, j int) bool
        // 交换下标i、j位置的元素。
 Swap(i, j int)
}

一个具体的例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import (
 "fmt"
 "sort"
)

//Student 学生,包含姓名和学号两个字段
type Student struct {
 name   string
 number int
}

//StudentByNumber 按照学生的学号排序
type StudentByNumber []Student

//Len 返回学生人数
func (s StudentByNumber) Len() int {
 return len(s)
}

//Less 比较下标为i、j的元素是否需要交换位置
func (s StudentByNumber) Less(i, j int) bool {
 return s[i].number < s[j].number
}

//Swap 交换下标i、j元素的位置
func (s StudentByNumber) Swap(i, j int) {
 s[i], s[j] = s[j], s[i]
}

func main() {
 arr := []Student{
  {name: "Tom", number: 002},
  {name: "Jerry", number: 006},
  {name: "Bob", number: 001},
  {name: "Albert", number: 004},
 }
 sort.Sort(StudentByNumber(arr))
 fmt.Println(arr) // [{Bob 1} {Tom 2} {Albert 4} {Jerry 6}]
}

逆序

通过 sort.Reverse 实现逆序。接着上面的例子,加入下两行代码把 arr 逆序:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sort.Sort(sort.Reverse(StudentByNumber(arr)))
fmt.Println(arr) // [{Jerry 6} {Albert 4} {Tom 2} {Bob 1}]

看源码得知,调用 Reverse 方法时,会调用实现类型的 Less 方法,通过互换i和j的位置达到逆序的效果。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Less returns the opposite of the embedded implementation's Less method.
func (r reverse) Less(i, j int) bool {
 return r.Interface.Less(j, i)
}

我个人感觉 Reverse 方法的实现使用起来不够“显而易见”,需要使用者摸索一下;但是也很好的复用了 Less 方法。

IntSlice、Float64Slice、StringSlice

sort 包中,除了对外提供Ints、Float64s、Strings方法外,还对外提供了对应的三种类型 IntSlice、Float64Slice、StringSlice

这三种类型都分别实现了 sort.Interface 接口。

其实Ints、Float64s、Strings方法都是对传入参数类型进行了强制转换:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Ints sorts a slice of ints in increasing order.
func Ints(x []int) { Sort(IntSlice(x)) }

当我们调用 Reverse 方法时,可以使用上面的三种类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sort.Sort(sort.Reverse(IntSlice(arr)))

Stable 方法

Sort 使用的是不稳定的排序算法,如果希望使用稳定的排序算法,需要调用 Stable 方法。

简单理解稳定的排序算法:对于两个相等的值,排序前后的相对位置相同,这样的排序算法称为稳定的排序算法。

Stable 的使用方式和 Sort 方法相同,就不再举例了。

Search 方法

Search 方法的作用是完成二分查找。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Search(n int, f func(int) bool) int

Search 方法接收两个参数:n int, f func(int) bool,n代表二分查找会从[0,n)开始。f方法返回值为true时,代表找到了满足条件的元素,Search函数返回此时的索引。当有多个相同元素时,返回最左侧元素的索引。当没有找到对应元素时,返回 n。

e.g

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import (
 "fmt"
 "sort"
)

func main() {

 a := []int{1, 3, 6, 6, 15, 21, 28, 36, 45, 55}

 i := sort.Search(len(a), func(i int) bool { return a[i] >= 6 })
 fmt.Println("i=", i) // i= 2 返回第一个匹配的元素下标

 j := sort.Search(len(a), func(i int) bool { return a[i] >= 100 })
 fmt.Println("j=", j) // j= 10 没有找到匹配元素
}

sort 包还提供了另外三个Search开头的方法:SearchFloat64s、SearchInts、SearchStrings。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func SearchInts(a []int, x int) int

以 SearchInts 为例,SearchInts接受参数 a []int, x int,在切片a中查找元素值为x的元素,当有多个相同元素时,返回最左侧元素的索引。当没有找到对应元素时,返回切片a的长度。不过调用此方法有个前提条件 a 必须是有序的。

源码分析

sort 包中排序的逻辑在 sort.go 文件中,查找逻辑在 search.go 文件中。我们主要分析 sort.go 文件的源码。

sort.go 文件中首先定义了 Interface:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// An implementation of Interface can be sorted by the routines in this package.
// The methods refer to elements of the underlying collection by integer index.
type Interface interface {
 // Len is the number of elements in the collection.
 Len() int

 // Less reports whether the element with index i
 // must sort before the element with index j.
 //
 // If both Less(i, j) and Less(j, i) are false,
 // then the elements at index i and j are considered equal.
 // Sort may place equal elements in any order in the final result,
 // while Stable preserves the original input order of equal elements.
 //
 // Less must describe a transitive ordering:
 //  - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
 //  - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
 //
 // Note that floating-point comparison (the < operator on float32 or float64 values)
 // is not a transitive ordering when not-a-number (NaN) values are involved.
 // See Float64Slice.Less for a correct implementation for floating-point values.
 Less(i, j int) bool

 // Swap swaps the elements with indexes i and j.
 Swap(i, j int)
}

所有需要排序的切片元素都需要实现此接口的三个方法。

Sort 方法

接着我们从 Sort(data Interface)方法开始梳理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Sort sorts data in ascending order as determined by the Less method.
// It makes one call to data.Len to determine n and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
 n := data.Len()
 quickSort(data, 0, n, maxDepth(n))
}

Sort 方法调用了 quickSort(data, 0, n, maxDepth(n)),乍一看用了快速排序,接着看 quickSort 的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func quickSort(data Interface, a, b, maxDepth int) {
 for b-a > 12 { // Use ShellSort for slices <= 12 elements
  if maxDepth == 0 {
   heapSort(data, a, b)
   return
  }
  maxDepth--
  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)
  }
 }
 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)
 }
}

通过代码可以到,当元素个数大于12和小于12时,分别采用了不同的排序算法。

「大于12时」:如果 maxDepth 参数递减为0,调用 heapSort(data, a, b)采用堆排序,在maxDepth大于零时,调用doPivot(data, a, b)方法,然后根据 mlo-a < b-mhi决定对长度较小的一段递归调用 quickSort。在maxDepth递减为0时,表示快速排序递归了很多轮还没有出结果,遇到了比较坏的情况,此时采用堆排序更高效。

doPivot(data, a, b)方法实现了快速排序的核心逻辑:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
 m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
 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)
 }
 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

 for ; a < c && data.Less(a, pivot); a++ {
 }
 b := a
 for {
  for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
  }
  for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
  }
  if b >= c {
   break
  }
  // data[b] > pivot; data[c-1] <= pivot
  data.Swap(b, c-1)
  b++
  c--
 }
 // If hi-c<3 then there are duplicates (by property of median of nine).
 // Let's be a bit more conservative, and set border to 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 {
  // Protect against a lot of duplicates
  // Add invariant:
  // data[a <= i < b] unexamined
  // data[b <= i < c] = pivot
  for {
   for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == 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
}

doPivot方法中medianOfThree方法是一种快速寻找中位数办法。

「小于12时」:以 gap 等于6作为增量做希尔排序,最后对data做插入排序。

Stable 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Stable(data Interface) {
 stable(data, data.Len())
}

func stable(data Interface, n int) {
 blockSize := 20 // must be > 0
 a, b := 0, 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 {
   symMerge(data, a, a+blockSize, b)
   a = b
   b += 2 * blockSize
  }
  if m := a + blockSize; m < n {
   symMerge(data, a, m, n)
  }
  blockSize *= 2
 }
}

稳定排序的实现比较简单,先以20个为一组进行插入排序,再进行归并排序。

总体来讲 Go 的排序算法会根据结果集的大小选择相对优秀的排序算法完成排序。

总结

本文我们主要介绍了 Go 语言 sort 包中排序和查询相关方法的使用,以及 sort.go 源码的逻辑剖析。如果大家对文章内容有任何疑问或建议,欢迎私信交流。

我把Go语言基础知识相关文章的Demo都放到了下面这个仓库中,方便大家互相交流学习。https://gitee.com/jialj/golang-learn (之前的 github 账号被封了,换到了gitee)

相关链接

https://golang.google.cn/pkg/sort/

https://www.johndcook.com/blog/2009/06/23/tukey-median-ninther/

https://learnku.com/articles/30404

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一行舟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Python 列表、字典、元组的一些小技巧
我们知道 Python 的内置 dictionary 数据类型是无序的,通过 key 来获取对应的 value。可是有时我们需要对 dictionary 中的 item 进行排序输出,可能根据 key,也可能根据 value 来排。到底有多少种方法可以实现对 dictionary 的内容进行排序输出呢?下面摘取了使用 sorted 函数实现对 dictionary 的内容进行排序输出一些精彩的解决办法。
章鱼猫先生
2021/10/15
1.2K0
Python 列表、字典、元组的一些小技巧
python3字典的排序
通一表述:字典有两个参数,key, value,下面所描述,键:key,值:value
全栈程序员站长
2022/09/13
4300
python 对字典"排序"总结
对字典进行排序?这其实是一个伪命题,搞清楚python字典的定义---字典本身默认以key的字符顺序输出显示---就像我们用的真实的字典一样,按照abcd字母的顺序排列,并且本质上各自没有先后关系,是一个哈希表的结构:
用户5745385
2019/07/04
5.6K0
python 对字典"排序"总结
python字典排序
利用Python 内置函数 sorted 对字典的键或者值进行排序,首先来了解下 sorted 函数
雷子
2024/03/25
1300
python字典排序
Python字典按键/值排序的几种方法
同理,如果我们只需要对sort_by_value稍微修改一下,就可以得到按值排序的结果:
宇宙之一粟
2020/10/26
5.8K0
Python 列表排序
本文最先发布在: https://www.itcoder.tech/posts/python-list-sort/
雪梦科技
2020/06/05
1.8K0
python中列表排序,字典排序,列表中的字典排序
key= lambda dict1:dict1[0] #dict1[0]表示按键,dict1[1]表示按值。
用户8346838
2021/03/10
9.7K0
python字典的合并排序添加查询
python 中映射类型里key和value是一种一对多的关系,通常被认为是一种可变的哈希表。字典对象是可变的,它是一个容器类型,能存储任意个数的Python对象,也可存储其他容器类型。
Tim在路上
2020/08/04
1K0
python dict的list排序
对于简单的list排序,直接调用内建函数就可以了,但是对于dict的list排序就没有那么直接了,不过,还是有很简洁的办法的,如:
py3study
2020/01/14
1.9K0
分享几个简单易懂的Python技巧,能够极大的提高工作效率哦!
大家好,我是俊欣,今天和大家来分享几个关于Python的小技巧,都是非常简单易懂的内容,希望大家看了之后能够有所收获。
用户6888863
2021/07/19
4180
Python 学习入门(11)—— 排序
Python的内置dictionary数据类型是无序的,通过key来获取对应的value。可是有时我们需要对dictionary中 的item进行排序输出,可能根据key,也可能根据value来排。
阳光岛主
2019/02/19
4120
Python 进阶编程之字典的高级用法
一个字典就是一个键对应一个单值的映射,而上面的列表中有相同键。如果你想要一个键映射多个值,那么就需要将这多个值放到另外的序列中,比如 list 或者 set 里面,像下面这样:
Crossin先生
2019/11/10
1.3K0
Python这10个字典操作你必须知道
不要使用 key in d.keys(), 这是一种画蛇添足的操作,因为d.keys()会返回一个新的列表对象,导致内存最大。
IT阅读排行榜
2019/05/14
4860
python list排序
python 列表list中内置了一个十分有用的排序函数sort,sorted,它可以用于列表的排序,以下是例子。
py3study
2020/01/07
1.2K0
Python字典不是不可以排序,是你方法没用对!
摘要:排序是个古老的话题,不过对于字典的排序,常常会让 小白手足无措。好像没有找到可以排序字典的函数呢!到底是按key排序,还是按value排序呢?字典到底可不可以按value排序呢?排完序后,还可以通过key检索吗?当然,还会抛出很多问题,而本文将完美地给出了这些问题的答案!
蒙娜丽宁
2021/02/19
1.1K0
Python-排序-01-字典排序
系统:Windows 7 语言版本:Anaconda3-4.3.0.1-Windows-x86_64 编辑器:pycharm-community-2016.3.2
zishendianxia
2019/10/23
8960
Python-排序-01-字典排序
python3的sorted和sort
python内置排序函数sorted,可以适用于所有可迭代的对象。而类型自有的sort函数只适用于类型本身。例如list.sort(),只适用于列表类型。另外,sort函数是在原来列表上直接进行排序,而sorted函数则是返回一个排序之后的列表。
zy010101
2022/05/05
3830
python中列表的排序方法操作总结分享
简单记一下python中List的sort方法(或者sorted内建函数)的用法。
我叫什么好
2022/01/08
8420
Python3学习笔记 | 七、Python的类型与运算-字典
字典在Python里是无序集合对象类型,字典的值都有独立的唯一的键(Key),用相应的键来取值。
TeamsSix
2019/09/24
7190
python 字典一些常见的魔法方法以及遇到的面试题
dict 类型不但在各种程序里广泛使用,它也是 Python 语言的基石。模块的命名空间、实例的属性和函数的关键字参数中都可以看到字典的身影。跟它有关的内置函数都在__builtins__.__dict__模块中。正是因为字典至关重要,Python 对它的实现做了高度优化,而散列表则是字典类型性能出众的根本原因。
用户4945346
2020/07/20
7610
相关推荐
Python 列表、字典、元组的一些小技巧
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验