切片是Go語言中引入的用於在大多數場合替代數組的語法元素。切片是一種長度可變的同類型元素序列,它原則上不支持存儲不一樣類型的元素,固然了做爲打工人是很是清楚「原則上」的潛臺詞就是「某種狀況下容許」html
special := []interface{}{「hello go」, 2021, 4.15}
這種容許的狀況有機會咱們另外討論,這個不是本次的討論範圍,本文就事論事,還不至於深刻到原理。java
正所謂有序列的地方就有排序的需求。在各類排序算法都已經成熟的今天,咱們徹底能夠針對特定元素類型的切片手寫排序函數/方法,但多數狀況下不推薦這麼作,由於Go標準庫內置了sort
包能夠很好地幫助咱們實現原生類型元素切片以及自定義類型元素切片的排序任務,但話又說回來,工程項目中咱們大機率都是拿來主義的,也只有是在日常刷題練習中才會本身考慮實現相關的算法。python
Go 的排序思路和 C 和 C++ 有些差異。 C 默認是對數組進行排序, C++ 是對一個序列進行排序, Go 則更寬泛一些,待排序的能夠是任何對象, 雖然不少狀況下是一個slice
(分片, 相似於數組),或是包含 slice 的一個對象。c++
這個包實現了四種基本排序算法:插入排序、歸併排序、堆排序和快速排序。可是這四種排序方法是不公開的,它們只被用於sort
包內部使用。所以在對數據集合排序時沒必要考慮應當選擇哪種排序方法,只要實現了 sort.Interface
定義的三個方法:git
Len()
方法Less()
方法Swap()
方法完成以後能夠順利對數據集合進行排序【無時不刻在等待泛型的出現啊,重複寫真的煩:)】github
sort 包會根據實際數據自動選擇高效的排序算法。 除此以外,爲了方便對經常使用數據類型的操做,sort 包提供了對[]int
切片、[]float64
切片和[]string
切片完整支持,主要包括:golang
前面已經提到過,對數據集合(包括自定義數據類型的集合)排序須要實現 sort.Interface 接口的三個方法,咱們看如下該接口的定義:算法
type Interface interface { // 獲取數據集合元素個數 Len() int // 若是 i 索引的數據小於 j 索引的數據,返回 true,且不會調用下面的 Swap(),即數據升序排序。 Less(i, j int) bool // 交換 i 和 j 索引的兩個元素的位置 Swap(i, j int) }
數據集合實現了這三個方法後,便可調用該包的Sort()
方法進行排序。Sort()
方法定義以下:swift
func Sort(data Interface)
Sort() 方法使用的唯一參數就是待排序的數據集合。數組
此外該包還提供了一個方法能夠判斷數據集合是否已經排好順序,畢竟方法的內部實現依賴於咱們本身實現的 Len() 和 Less() 方法:
func IsSorted(data Interface) bool { n := data.Len() for i := n - 1; i > 0; i-- { if data.Less(i, i-1) { return false } } return true }
最後一個方法:Search()
func Search(n int, f func(int) bool) int
該方法會使用「二分查找」算法來找出能使f(x)(0<=x<n)
返回 ture 的最小值 i。 前提條件 : f(x)(0<=x<i)
均返回false
,f(x)(i<=x<n)
均返回ture
。 若是不存在 i 可使 f(i) 返回 ture, 則返回 n。
Search() 函數一個經常使用的使用方式是搜索元素 x 是否在已經升序排好的切片 s 中:
x := 11 s := []int{3, 6, 8, 11, 45} // 注意已經升序排序 pos := sort.Search(len(s), func(i int) bool { return s[i] >= x }) if pos < len(s) && s[pos] == x { fmt.Println(x, " 在 s 中的位置爲:", pos) } else { fmt.Println("s 不包含元素 ", x) }
截至目前Go 1.15版本,Go還不支持泛型。所以,爲了支持任意元素類型的切片的排序,標準庫sort包定義了一個Interface
接口和一個接受該接口類型參數的Sort
函數:
type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } func Sort(data Interface) { n := data.Len() quickSort(data, 0, n, maxDepth(n)) }
爲了應用這個排序函數Sort,咱們須要讓被排序的切片類型實現sort.Interface
接口,以整型切片爲例
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] } func main() { sl := IntSlice([]int{89, 14, 8, 9, 17, 56, 95, 3}) fmt.Println(sl) // [89 14 8 9 17 56 95 3] sort.Sort(sl) fmt.Println(sl) // [3 8 9 14 17 56 89 95] }
從sort.Sort
函數的實現來看,它使用的是快速排序quickSort
。咱們知道快速排序是在全部數量級爲O(nlogn)
的排序算法中其平均性能最好的算法,但在某些狀況下其性能卻並不是最佳,Go sort包中的quickSort
函數也沒有嚴格拘泥於僅使用快排算法,而是以快速排序爲主,並根據目標情況在特殊條件下選擇了其餘不一樣的排序算法,包括堆排序(heapSort)、插入排序(insertionSort)等。
sort.Sort函數不保證排序是穩定的,要想使用穩定排序,須要使用sort.Stable
函數。
咱們看到,直接使用sort.Sort函數對切片進行排序是比較繁瑣的。若是僅僅排序一個原生的整型切片都這麼繁瑣(要實現三個方法),那麼sort包是會被噴慘的。還好,對於以常見原生類型爲元素的切片,sort包提供了類「語法糖」的簡化函數,好比:sort.Ints
、sort.Float64s
和sort.Strings
等。上述整型切片的排序代碼能夠直接改形成下面這個樣子:
func main() { sl := []int{89, 14, 8, 9, 17, 56, 95, 3} fmt.Println(sl) // [89 14 8 9 17 56 95 3] sort.Ints(sl) fmt.Println(sl) // [3 8 9 14 17 56 89 95] }
原生類型有「語法糖」可用了,那麼對於自定義類型做爲元素的切片,是否是每次都得實現Interface接口的三個方法呢?Go團隊也想到了這個問題! 因此在Go 1.8版本中加入了sort.Slice
函數,咱們只需傳入一個比較函數實現便可:
type Lang struct { Name string Rank int } func main() { langs := []Lang{ {"rust", 2}, {"go", 1}, {"swift", 3}, } sort.Slice(langs, func(i, j int) bool { return langs[i].Rank < langs[j].Rank }) fmt.Printf("%v\n", langs) // [{go 1} {rust 2} {swift 3}] }
同理,若是要進行穩定排序,則用sort.SliceStable
替換上面的sort.Slice
。
本文主要是經過對go中切片的分析,因爲go中的排序不一樣於c、c++、python這些語言的排序習慣,又因爲其不支持泛型,且正處於野蠻生長期,咱們在學習應用的過程當中,也可貴的能夠體驗其發育帶來痛苦,正由於沒有體會相同的痛苦,就不能感同身受,成熟的語言如java、python用多了,一直用別人的輪子,實在體會不到輪子內部的精妙之處,咱們在學習的過程當中能夠本身實現相關的排序算法,見證社區的發展,反而能夠一步步推演內核的進化,進而舉一反三猜想其餘語言的設計思想,不勝榮幸。