Happyunlimited 2019-12-13
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) }
在 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 数组实现了这三种方法,其它两种类型基本相同,所以我们在对上述三种类型的数据进行排序的时候,可以不必自己实现上述接口,直接调用得到排序结果。
通过以上描述,我们知道只有上述三种类型是 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(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 的希尔排序,然后进行插入排序。
如果要想让v2也进行排序,需要把k2和v2组装成新的类,作为k2,才能参与比较。// 1.1 告诉干活的人 输入流位置 读取hdfs中的文件。每一行解析成一个<k,v>。每一个键值对调用一次map函数