Sort 包介绍
Go 语言标准库 sort 包中实现了几种基本的排序算法:插入排序、快速排序和堆排序,但是在使用 sort 包进行排序时无需具体考虑使用哪种排序方式,因为该方法会根据传入的排序的数据量来进行自动选择合适的排序算法。
func insertionSort(data Interface, a, b int) // 插入排序 func heapSort(data Interface, a, b int) // 堆排序 func quickSort(data Interface, a, b, maxDepth int) // 快速排序
sort.Interface 接口定义了以下三个方法,只要实现了这三个方法,就可以对数据集合进行排序,sort 包会根据实际数据自动选择高效的排序算法。
// sort.go type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports whether the element with // index i should sort before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) }
Sort 包自带的排序实现
在 Go 的 sort 包中,给我们实现了三种类型的排序,分别是 Int, string, float64 类型,我们来具体看一下 Int 类型的实现:
// sort.go // IntSlice attaches the methods of Interface to []int, sorting in increasing order. type IntSlice []int func (p IntSlice) Len() int { return len(p) } func (p IntSlice) Less(i, j int) bool { return p[i] < p[j] } func (p IntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Sort is a convenience method. func (p IntSlice) Sort() { Sort(p) }
可以看到 Go 已经给 int 数组实现了这三种方法,其它两种类型基本相同,所以我们在对上述三种类型的数据进行排序的时候,可以不必自己实现上述接口,直接调用得到排序结果。
Sort 包自定义排序方式
通过以上描述,我们知道只有上述三种类型是 Go 帮我们实现好的,那如果我们要对结构体进行排序,该如何实现呢?同理,我们只需要给自己定义的待排序的结构体类型实现上述接口就可以了。我们来看一下具体示例:
func main() { a := personSlice{ { Name: "AAA", Age: 55, }, { Name: "BBB", Age: 22, }, { Name: "CCC", Age: 0, }, { Name: "DDD", Age: 22, }, { Name: "EEE", Age: 11, }, } sort.Sort(a) fmt.Println("Sort: ", a) sort.Stable(a) // 稳定排序 fmt.Println("Stable: ", a) }
Sort 包如何选择最合适的排序方式
我们上面说用 sort 进行排序不用关心采用哪种排序方式,但是 sort 又是根据什么来选择排序方式的呢?在 sort 包自带的排序实现一节中,我们看到,上面的类型都是通过 sort.Sort() ,进一步调用 Sort(p) 来进行排序的,那我们来看一下 Sort(p) 所采用的排序策略:
// sort.go
// Sort sorts data. // 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 函数进行排序,其中参数 maxDepth(n) 为什么呢?我们看一下:
// sort.go // maxDepth returns a threshold at which quicksort should switch // to heapsort. It returns 2*ceil(lg(n+1)). func maxDepth(n int) int { var depth int for i := n; i > 0; i >>= 1 { depth++ } return depth * 2 }
注释告诉我们,这个函数返回一个阈值,用来区分是用快排还是用堆排序。那我们回去看一下 quickSort 是怎么根据这个阈值进行区分的:
// sort.go 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) } }
从上面的代码逻辑中可以看到,当元素个数(b-a) 大于 12 时,采用快排(算法中选取选取枢纽值进行了优化),当 maxDepth == 0 时采用堆排序(此处不是很理解);
当 元素个数(b-a) 大于 1 并且小于等于 12 时,首先进行一轮 step == 6 的希尔排序,然后进行插入排序。
Sort 包常用方法
func main() { a := []int{1, 3, 5, 7, 2} b := sort.IntSlice{2, 4, 6, 8} fmt.Println(b) //=====int===== c := sort.IntSlice{2, 1, 4, 3, 7} fmt.Println(c) sort.Ints(c) sort.IntsAreSorted(c) sort.IsSorted(c) sort.SearchInts(a, 5) //=====string==== d := sort.StringSlice{"aaa", "bbb", "ccc"} fmt.Println(d) sort.Strings(d) sort.StringsAreSorted(d) sort.IsSorted(d) sort.SearchStrings(d, "bbb") //=====Float64===== e := sort.Float64Slice{2.1, 2.6, 7.8, 0.5} fmt.Println(e) sort.Float64s(e) sort.Float64sAreSorted(e) sort.IsSorted(e) sort.SearchFloat64s(e, 7.8) //====common function=== /* search(n, func) 使用二分法进行查找指定切片[0:n],返回能使 f(i)=true 的最小的 i(0<=i<n) 的值 并且会假定,如果 f(i)=true, 则 f(i+1)=true 当在切片中无法找到 f(i)=true 的 i 时, 该方法返回 n,而不是返回 -1 notice: 上述的三种 search 查找方法中,其对应的 slice 必须按照升序进行排序,否则会出现奇怪的结果 */ f := []int{1, 2, 3} ff := sort.Search(len(f), func(i int) bool { return f[i] == 8 }) // ff=len(f)==3 fmt.Println(ff) /* Sort(data Interface) 会对 data 进行排序,为不稳定排序,可以根据不同形式决定 使用不同的排序方式(插入排序,堆排序,快排) */ /* Reverse(data Interface) Interface 实现对 data 的逆序排列,结果返回一个 Interface 需要再次调用 sort.Sort(data Interface) 对逆序的结果进行输出 */ g := sort.IntSlice{3, 2, 4} sort.Sort(sort.Reverse(g)) fmt.Println(g) // 4, 3, 2 }