go排序sort包详解

sort包提供了排序切片和用户自定义数据集的函数。

目录

  • 接口——排序(接口)的三个要素
    • int类型
    • float类型
    • string类型
    • 基本类型 int 、 float64 和 string 的排序
    • 降序排序
  • 结构体类型的排序
    • 结构体排序方法 1——实现接口(最简单的一种)
    • 结构体排序方法 2——提供动态的Less方法
    • 结构体排序方法 3——扩展初始化函数
    • 结构体排序方法 4
    • 小结
  • 复杂结构排序
    • `[][]int`
    • `[]map[string]int [{"k":0},{"k1":1},{"k2":2]`
  • 其他方法
    • func Sort(data Interface)
    • func Stable(data Interface)
    • func IsSorted(data Interface) bool
    • func Reverse(data Interface) Interface
    • func Search(n int, f func(int) bool) int
  • 排序算法pdqsort
    • 选择插入排序,快排,堆排序对比
      • 插入排序
      • 快排
      • 堆排序
      • 三者对比
    • pdqsort算法

接口——排序(接口)的三个要素

go的排序操作需要该类型实现Interface接口,

type Interface interface {
    // Len方法返回集合中的元素个数
    Len() int
    // Less方法报告索引i的元素是否比索引j的元素小
    Less(i, j int) bool
    // Swap方法交换索引i和j的两个元素
    Swap(i, j int)
}

int类型

出厂已经实现了上述接口:

// IntSlice将接口方法附加到[]int,按递增顺序排序。
type IntSlice []int

func (x IntSlice) Len() int           { return len(x) }
func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x IntSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// Sort是一种方便的方法:x.Sort()调用Sort(x)。
func (x IntSlice) Sort() { Sort(x) }

// Search返回将SearchInts应用于receiver和x的结果。
func (p IntSlice) Search(x int) int { return SearchInts(p, x) }

int提供的方法:

Ints函数将a排序为递增顺序。
1. func Ints(a []int)

IntsAreSorted检查a是否已排序为递增顺序。
2. func IntsAreSorted(a []int) bool 

SearchInts在递增顺序的a中搜索x,返回x的索引。如果查找不到,返回值是x应该插入a的位置(以保证a的递增顺序)不会插入,返回值可以是len(a)3. func SearchInts(a []int, x int) int

float类型

出厂也已经实现了接口:

type Float64Slice []float64

func (x Float64Slice) Len() int { return len(x) }

// Less根据排序接口的要求,报告x[i]是否应在x[j]之前排序。
// 请注意,浮点比较本身不是传递关系
// 报告not-a-number(NaN)值的一致顺序。
// 此Less实现将NaN值置于任何其他值之前:
//
//	x[i] < x[j] || (math.IsNaN(x[i]) && !math.IsNaN(x[j]))
//
func (x Float64Slice) Less(i, j int) bool { return x[i] < x[j] || (isNaN(x[i]) && !isNaN(x[j])) }
func (x Float64Slice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// isNaN是从math包copy过来的,以避免对数学包的依赖。
func isNaN(f float64) bool {
	return f != f
}

func (x Float64Slice) Sort() { Sort(x) }

// Search等价于调用SearchFloat64s(p, x)
func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) }

float64提供的方法:

// 递增排序
1.func Float64s(a []float64)
// 判断是否有序
2.func Float64sAreSorted(a []float64) bool
// 返回查找到的x位置
3.func SearchFloat64s(a []float64, x float64) int

string类型

string实现的接口:

// StringSlice attaches the methods of Interface to []string, sorting in increasing order.
type StringSlice []string

func (x StringSlice) Len() int           { return len(x) }
func (x StringSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x StringSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// Sort is a convenience method: x.Sort() calls Sort(x).
func (x StringSlice) Sort() { Sort(x) }

// Search returns the result of applying SearchStrings to the receiver and x.
func (p StringSlice) Search(x string) int { return SearchStrings(p, x) }

string提供的方法:

// 递增排序
1. func Strings(a []string)
// 判断是否有序
2. func StringsAreSorted(a []string) bool
// 在[]string中查找string
3. func SearchStrings(a []string, x string) int

基本类型 int 、 float64 和 string 的排序

  1. go 分别提供了 sort.Ints() 、 sort.Float64s() 和 sort.Strings() 函数, 默认都是从小到大排序。(但没有 sort.Float32s() 函数)
  2. 可以借助以上函数快速对基本类型进行排序:
func main() {
    intList := [] int {2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
    float8List := [] float64 {4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    // no function : sort.Float32s
    // float4List := [] float32 {4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    stringList := [] string {"a", "c", "b", "d", "f", "i", "z", "x", "w", "y"}
    
    sort.Ints(intList)
    sort.Float64s(float8List)
    sort.Strings(stringList)
    
    fmt.Printf("%v\n%v\n%v\n", intList, float8List, stringList)
 
}

或者

ls := sort.Float64Slice{
    1.1,
    4.4,
    5.5,
    3.3,
    2.2,
}
fmt.Println(ls)  //[1.1 4.4 5.5 3.3 2.2]
sort.Float64s(ls)
fmt.Println(ls)   //[1.1 2.2 3.3 4.4 5.5]
-----------------------------------------------------
ls := sort.IntSlice{
    1,
    4,
    5,
    3,
    2,
}
fmt.Println(ls)  //[1 4 5 3 2]
sort.Ints(ls)
fmt.Println(ls)  //[1 2 3 4 5]
------------------------------------------------
//字符串排序,先比较高位,相同的再比较低位
ls := sort.StringSlice{
    "100",
    "42",
    "41",
    "3",
    "2",
}
fmt.Println(ls)  //[100 42 41 3 2]
sort.Strings(ls)
fmt.Println(ls)  //[100 2 3 41 42]
-------------------------------------------------
//汉字排序,依次比较byte大小
ls := sort.StringSlice{
    "啊",
    "博",
    "次",
    "得",
    "饿",
    "周",
}
fmt.Println(ls)  //[啊 博 次 得 饿 周]
sort.Strings(ls)
fmt.Println(ls)  //[博 周 啊 得 次 饿]

降序排序

  1. int 、 float64 和 string 都有默认的升序排序函数
  2. go 中对某个 Type 的对象 obj 排序, 可以使用 sort.Sort(obj) 即可,就是需要对 Type 类型绑定三个方法 : Len() 求长度、 Less(i,j) 比较第 i 和 第 j 个元素大小的函数、 Swap(i,j) 交换第 i 和第 j 个元素的函数。sort 包下的三个类型 IntSlice 、 Float64Slice 、 StringSlice 分别实现了这三个方法, 对应排序的是 [] int 、 [] float64 和 [] string 。如果期望逆序排序, 只需要将对应的 Less 函数简单修改一下即可。
  3. go 的 sort 包可以使用 sort.Reverse(slice) 来调换 slice.Interface.Less ,也就是比较函数,所以, int 、 float64 和 string 的逆序排序函数可以这么写:
func main() {
    intList := [] int {2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
    float8List := [] float64 {4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    stringList := [] string {"a", "c", "b", "d", "f", "i", "z", "x", "w", "y"}
    
    sort.Sort(sort.Reverse(sort.IntSlice(intList)))
    sort.Sort(sort.Reverse(sort.Float64Slice(float8List)))
    sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
    
    fmt.Printf("%v\n%v\n%v\n", intList, float8List, stringList)
}

结构体类型的排序

  1. 结构体类型的排序是通过使用 sort.Sort(slice) 实现的, 只要 slice 实现了 sort.Interface 的三个方法就可以。
  2. 虽然这么说,但是排序的方法却有那么好几种。首先一种就是模拟排序 [] int 构造对应的 IntSlice 类型,然后对 IntSlice 类型实现 Interface 的三个方法。

结构体排序方法 1——实现接口(最简单的一种)

type Person struct {
    Name string    // 姓名
    Age  int    // 年纪
}
 
// 按照 Person.Age 从大到小排序
type PersonSlice [] Person
 
func (a PersonSlice) Len() int {    // 重写 Len() 方法
    return len(a)
}
func (a PersonSlice) Swap(i, j int){     // 重写 Swap() 方法
    a[i], a[j] = a[j], a[i]
}
func (a PersonSlice) Less(i, j int) bool {    // 重写 Less() 方法, 从大到小排序
    return a[j].Age < a[i].Age 
}
 
func main() {
    people := [] Person{
        {"zhang san", 12},
        {"li si", 30},
        {"wang wu", 52},
        {"zhao liu", 26},
    }
 
    fmt.Println(people)
 
    sort.Sort(PersonSlice(people))    // 按照 Age 的逆序排序
    fmt.Println(people)
 
    sort.Sort(sort.Reverse(PersonSlice(people)))    // 按照 Age 的升序排序
    fmt.Println(people)
 
}

结构体排序方法 2——提供动态的Less方法

type Person struct {
    Name string    // 姓名
    Age  int    // 年纪
}
 
type PersonWrapper struct {
    people [] Person
    by func(p, q * Person) bool
}
 
func (pw PersonWrapper) Len() int {    // 重写 Len() 方法
    return len(pw.people)
}
func (pw PersonWrapper) Swap(i, j int){     // 重写 Swap() 方法
    pw.people[i], pw.people[j] = pw.people[j], pw.people[i]
}
func (pw PersonWrapper) Less(i, j int) bool {    // 重写 Less() 方法
    return pw.by(&pw.people[i], &pw.people[j])
}
 
func main() {
    people := [] Person{
        {"zhang san", 12},
        {"li si", 30},
        {"wang wu", 52},
        {"zhao liu", 26},
    }
 
    fmt.Println(people)
 
    sort.Sort(PersonWrapper{people, func (p, q *Person) bool {
        return q.Age < p.Age    // Age 递减排序
    }})
 
    fmt.Println(people)
    sort.Sort(PersonWrapper{people, func (p, q *Person) bool {
        return p.Name < q.Name    // Name 递增排序
    }})
 
    fmt.Println(people)
 
}

结构体排序方法 3——扩展初始化函数

type Person struct {
    Name string    // 姓名
    Age  int    // 年纪
}
 
type PersonWrapper struct {
    people [] Person
    by func(p, q * Person) bool
}
 
type SortBy func(p, q *Person) bool
 
func (pw PersonWrapper) Len() int {    // 重写 Len() 方法
    return len(pw.people)
}
func (pw PersonWrapper) Swap(i, j int){     // 重写 Swap() 方法
    pw.people[i], pw.people[j] = pw.people[j], pw.people[i]
}
func (pw PersonWrapper) Less(i, j int) bool {    // 重写 Less() 方法
    return pw.by(&pw.people[i], &pw.people[j])
}
 
 
func SortPerson(people [] Person, by SortBy){    // SortPerson 方法
    sort.Sort(PersonWrapper{people, by})
}
 
func main() {
    people := [] Person{
        {"zhang san", 12},
        {"li si", 30},
        {"wang wu", 52},
        {"zhao liu", 26},
    }
 
    fmt.Println(people)
 
    sort.Sort(PersonWrapper{people, func (p, q *Person) bool {
        return q.Age < p.Age    // Age 递减排序
    }})
 
    fmt.Println(people)
 
    SortPerson(people, func (p, q *Person) bool {
        return p.Name < q.Name    // Name 递增排序
    })
 
    fmt.Println(people)
 
}

结构体排序方法 4

type Person struct {
    Name        string
    Weight      int
}
 
type PersonSlice []Person
 
func (s PersonSlice) Len() int  { return len(s) }
func (s PersonSlice) Swap(i, j int)     { s[i], s[j] = s[j], s[i] }
 
type ByName struct{ PersonSlice }    // 将 PersonSlice 包装起来到 ByName 中
 
func (s ByName) Less(i, j int) bool     { return s.PersonSlice[i].Name < s.PersonSlice[j].Name }    // 将 Less 绑定到 ByName 上
 
 
type ByWeight struct{ PersonSlice }    // 将 PersonSlice 包装起来到 ByWeight 中
func (s ByWeight) Less(i, j int) bool   { return s.PersonSlice[i].Weight < s.PersonSlice[j].Weight }    // 将 Less 绑定到 ByWeight 上
 
func main() {
    s := []Person{
        {"apple", 12},
        {"pear", 20},
        {"banana", 50},
        {"orange", 87},
        {"hello", 34},
        {"world", 43},
    }
 
    sort.Sort(ByWeight{s})
    fmt.Println("People by weight:")
    printPeople(s)
 
    sort.Sort(ByName{s})
    fmt.Println("\nPeople by name:")
    printPeople(s)
 
}
 
func printPeople(s []Person) {
    for _, o := range s {
        fmt.Printf("%-8s (%v)\n", o.Name, o.Weight)
    }
}

小结

  1. 第一种排序对只根据一个字段的比较合适, 另外三个是针对可能根据多个字段排序的。
  2. 2、 3 没有太大的差别, 3 只是简单封装了一下。

复杂结构排序

[][]int

type testSlice [][]int

func (l testSlice) Len() int            { return len(l) }
func (l testSlice) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
func (l testSlice) Less(i, j int) bool { return l[i][1] < l[j][1] }

func main() {
    ls := testSlice{
        {1,4},
        {9,3},
        {7,5},
    }

    fmt.Println(ls)  //[[1 4] [9 3] [7 5]]
    sort.Sort(ls)
    fmt.Println(ls)  //[[9 3] [1 4] [7 5]]
}

[]map[string]int [{"k":0},{"k1":1},{"k2":2]

type testSlice []map[string]float64

func (l testSlice) Len() int            { return len(l) }
func (l testSlice) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
func (l testSlice) Less(i, j int) bool { return l[i]["a"] < l[j]["a"] } //按照"a"对应的值排序

func main() {
    ls := testSlice{
        {"a":4, "b":12},
        {"a":3, "b":11},
        {"a":5, "b":10},
    }


    fmt.Println(ls)  //[map[a:4 b:12] map[a:3 b:11] map[a:5 b:10]]
    sort.Sort(ls)
    fmt.Println(ls)  //[map[a:3 b:11] map[a:4 b:12] map[a:5 b:10]]
}

其他方法

func Sort(data Interface)

对实现接口的类型进行排序

tmp:=[]int{1,2,3,5,6}
sort.Sort(sort.IntSlice(tmp))

func Stable(data Interface)

Stable排序data,并保证排序的稳定性,相等元素的相对次序不变。

tmp:=[]int{1,2,3,5,6}
sort.Stable(sort.IntSlice(tmp))

func IsSorted(data Interface) bool

IsSorted报告data是否已经被排序。

func Reverse(data Interface) Interface

Reverse包装一个Interface接口并返回一个新的Interface接口,对该接口排序可生成递减序列。

s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Sort(sort.Reverse(sort.IntSlice(s)))
fmt.Println(s)
type doubleArray [][]int

func (d doubleArray)Len() int{ return len(d) }
func (d doubleArray) Less(i, j int) bool { return i<j }
func (d doubleArray) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
func main(){
	tmp:=[][]int{
		[]int{1},
		[]int{1,2},
		[]int{1,2,3},
	}
	res:=sort.Reverse(doubleArray(tmp))
	sort.Sort(res)
	fmt.Println(res)
	// &{[[1 2 3] [1 2] [1]]}
}

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

Search函数采用二分法搜索找到[0, n)区间内最小的满足f(i)==true的值i。

func main(){
	tmp:=[]int{1,2,6,3,5,6}
	res:=sort.Search(len(tmp), func(i int) bool {
		return tmp[i]==3
	})
	fmt.Println(res)
	// 3
}

排序算法pdqsort

pdqsort 是一种用于排序的 C++ 算法,旨在在许多情况下作为传统的 std::sort 算法的更快替代方案。它的全名是“Pattern-Defeating QuickSort”,由 Orson Peters 创建。pdqsort 的主要思想是最小化比较和分支预测错误的数量,这些都是排序算法中常见的瓶颈。

以下是 pdqsort 的一些关键特点和方面:

  1. Pattern Defeating 模式克服pdqsort 旨在克服实际数据集中可能出现的模式。传统的排序算法(如快速排序和归并排序)在某些输入模式下表现不佳,例如部分有序或逆序的数组。pdqsort 设计得更有效地处理这些模式。

  2. 自适应性pdqsort 是自适应的,意味着它会根据排序的数据自动调整其行为。它利用数据中已有的顺序来减少比较次数,从而减少排序时间。

  3. 内存效率:与一些其他自适应排序算法(如 Timsort)相比,pdqsort 使用较少的内存资源。在对大型数据集进行排序时,这可能非常有益。

  4. 高缓存友好性pdqsort 的设计考虑了缓存性能。它旨在最小化缓存未命中和分支预测错误,这可以显著影响排序算法的运行时间。

  5. 随机化的轴元素选择:与快速排序类似,pdqsort 也使用轴元素来将数组划分为较小的段。但是,pdqsort 中的轴元素选择是随机的,以减少对抗性输入所导致的性能下降。

  6. 插入排序:对于小的子数组,pdqsort 切换到插入排序,以进一步优化排序过程。由于插入排序的开销较低,对于小数组来说效率很高。

  7. 避免分支:算法设计旨在尽可能避免分支,这有助于减少分支预测错误,从而降低排序过程的速度。

  8. 稳定排序:默认情况下,pdqsort 不是稳定的,这意味着它不保证相等元素的顺序。然而,可以使用一个可选的标志使其保持稳定,但会稍微降低性能。

以下是 pdqsort 的简化流程:

  1. 如果数组大小小于某个阈值,切换到插入排序。
  2. 随机选择一个轴元素。
  3. 将数组划分为三个部分:小于轴元素的元素、等于轴元素的元素和大于轴元素的元素。
  4. 递归地对小于轴元素和大于轴元素的部分进行排序。
  5. 重复上述过程,直到整个数组排序完成。

选择插入排序,快排,堆排序对比

插入排序

插入排序是一种简单直观的排序算法,它逐步构建有序序列。这种算法的工作方式类似于我们整理一手扑克牌,一张一张地将牌插入到已经有序的牌堆中。插入排序的主要思想是,将未排序的元素逐个插入到已排序的部分中,以构建最终有序的数组。

以下是插入排序的详细步骤:

  1. 初始状态:将第一个元素视为已排序部分,其余元素视为未排序部分。

  2. 从未排序部分选择元素:从未排序的部分中依次选择一个元素。

  3. 插入元素到已排序部分:将选中的未排序元素插入到已排序部分的适当位置,使得已排序部分仍然保持有序。

  4. 重复步骤 2 和 3:重复选择未排序部分中的元素并将其插入到已排序部分,直到所有元素都被插入到已排序部分。

以下是插入排序的伪代码表示:

InsertionSort(arr):
    for i from 1 to length(arr) - 1:
        currentElement = arr[i]
        j = i - 1

        while j >= 0 and arr[j] > currentElement:
            arr[j + 1] = arr[j]
            j = j - 1
        
        arr[j + 1] = currentElement

插入排序的特点:

  • 稳定性:插入排序是稳定的,即相等元素的相对顺序不会发生改变。

  • 原地排序:插入排序只需要常数级别的额外空间来存储临时变量,因此可以在原数组上进行排序,不需要额外的内存空间。

  • 最佳情况时间复杂度:如果输入数组已经几乎有序,插入排序的时间复杂度可以接近O(n),其中n是元素的数量。

  • 最坏情况时间复杂度:如果输入数组完全逆序,插入排序的时间复杂度为O(n^2)。

插入排序在小型数据集上性能良好,而在大型数据集上通常会比其他高级排序算法(如归并排序或快速排序)慢,因为它的时间复杂度较高。然而,在某些情况下,插入排序的优点在于它具有相对低的常数因子,因此对于部分有序的数据集,插入排序可能比其他算法更快。

插入排序是一种简单但有用的排序算法,特别适用于小规模或基本有序的数据集。

在这里插入图片描述

时间复杂度:

最优:O(n)
平均:O(n^2)
最差:O(n^2)

快排

快速排序(QuickSort)是一种高效的分治排序算法,它以递归的方式将一个数组分成较小和较大的子数组,然后对这些子数组进行排序。快速排序的核心思想是选择一个轴元素(pivot),然后将数组分为两个子数组,一个包含所有小于轴元素的值,另一个包含所有大于轴元素的值,最后递归地对子数组进行排序。

以下是快速排序的详细步骤:

  1. 选择轴元素:从数组中选择一个轴元素。选择轴元素的方式可以有多种,常见的方法是随机选择、选择第一个元素或选择中间元素。

  2. 分区:将数组分为两个部分,一部分包含小于轴元素的值,另一部分包含大于轴元素的值。这个过程称为分区,通常通过移动元素来实现。

  3. 递归排序子数组:对两个子数组递归地应用快速排序算法,即对小于轴元素的子数组和大于轴元素的子数组进行排序。

  4. 合并:合并已排序的子数组,将小于轴元素的子数组、轴元素本身和大于轴元素的子数组合并成一个有序的数组。

以下是快速排序的伪代码表示:

QuickSort(arr, low, high):
    if low < high:
        pivotIndex = Partition(arr, low, high)
        QuickSort(arr, low, pivotIndex - 1)
        QuickSort(arr, pivotIndex + 1, high)

Partition(arr, low, high):
    pivot = arr[high]  // Choose the pivot as the last element
    i = low - 1

    for j from low to high - 1:
        if arr[j] <= pivot:
            i = i + 1
            Swap(arr[i], arr[j])
    
    Swap(arr[i + 1], arr[high])
    return i + 1

快速排序的特点:

  • 原地排序:快速排序是原地排序,它只需要常数级别的额外空间用于交换元素。

  • 不稳定性:在交换元素的过程中,可能会破坏相等元素的相对顺序,因此快速排序是不稳定的排序算法。

  • 平均时间复杂度:在平均情况下,快速排序的时间复杂度为O(n log n),其中n是元素数量。这使得它成为大多数实际应用场景中最快的排序算法之一。

  • 最坏情况时间复杂度:在最坏情况下,即数组已经有序或接近有序时,快速排序的时间复杂度为O(n^2),但通过合理的选择轴元素和随机化等方法,可以减少最坏情况的发生。

快速排序是一种高效的排序算法,特别适用于大规模数据集的排序。但需要注意的是,选择不当的轴元素或数据集的分布可能会影响性能。

时间复杂度:

最优:O(nlogn)
平均:O(nlogn)
最差:O(n^2)

堆排序

堆排序(Heap Sort)是一种基于堆数据结构的排序算法,它利用了堆的性质来进行排序。堆是一种特殊的树状数据结构,具有以下特点:在最大堆中,父节点的值总是大于或等于其子节点的值;在最小堆中,父节点的值总是小于或等于其子节点的值。堆排序的核心思想是通过构建堆,将最大(或最小)元素移到数组的末尾,然后从堆中移除它,再继续重复这个过程,直到所有元素都有序。

以下是堆排序的详细步骤:

  1. 构建堆:将待排序的数组构建成一个堆。可以根据需要选择构建最大堆或最小堆。

  2. 取出根节点:从堆中取出根节点(堆顶元素),即最大(或最小)元素。

  3. 重新调整堆:在取出根节点后,将堆的最后一个元素移到根节点的位置,然后通过向下调整(Heapify)的方式,使得堆继续满足堆的性质。

  4. 重复步骤 2 和 3:重复取出根节点、重新调整堆的步骤,直到堆中的所有元素都被取出,得到一个有序数组。

以下是堆排序的伪代码表示:

HeapSort(arr):
    BuildHeap(arr)  // 构建最大堆
    
    for i from length(arr) - 1 down to 1:
        Swap(arr[0], arr[i])  // 将最大元素移到末尾
        Heapify(arr, 0, i)    // 重新调整堆
    
BuildHeap(arr):
    n = length(arr)
    for i from n/2 - 1 down to 0:
        Heapify(arr, i, n)

Heapify(arr, index, heapSize):
    largest = index
    left = 2 * index + 1
    right = 2 * index + 2
    
    if left < heapSize and arr[left] > arr[largest]:
        largest = left
    
    if right < heapSize and arr[right] > arr[largest]:
        largest = right
    
    if largest != index:
        Swap(arr[index], arr[largest])
        Heapify(arr, largest, heapSize)

堆排序的特点:

  • 原地排序:堆排序是原地排序,它只需要常数级别的额外空间用于交换元素。

  • 不稳定性:由于在堆的调整过程中,可能破坏相等元素的相对顺序,因此堆排序是不稳定的排序算法。

  • 平均和最坏情况时间复杂度:堆排序的平均和最坏情况时间复杂度均为O(n log n),其中n是元素数量。

  • 适用性:堆排序在大规模数据集中表现良好,但在常数因子方面通常比快速排序稍慢。然而,堆排序的一个优点是它对于输入数据的分布不敏感,始终保持O(n log n)的时间复杂度。

堆排序是一种高效的排序算法,特别适用于需要稳定的时间复杂度,并且对额外内存空间要求较低的场景。

时间复杂度:

最优:O(nlogn)
平均:O(nlogn)
最差:O(nlogn)

三者对比

go排序sort包详解_第1张图片
根据序列元素排列情况划分:

  1. 完全随机的情况(random)
  2. 有序/逆序的情况(sorted/reverse)
  3. 元素重复度较高的情况(mod8)

在此基础上,还需要根据序列长度的划分(16/128/1024)

Benchmark-random:
go排序sort包详解_第2张图片
Benchmark-sorted
go排序sort包详解_第3张图片

  1. 所有短序列和元素有序情况下,插入排序性能最好
  2. 在大部分的情况下,快速排序有较好的综合性能
  3. 几乎在任何情况下,堆排序的表现都比较稳定

pdqsort算法

// pdqsort_func 对数据[a:b]进行排序。
// 该算法基于模式克服快速排序(pdqsort)
// pdqsort 论文:https://arxiv.org/pdf/2106.05123.pdf
// C++ 实现:https://github.com/orlp/pdqsort
// Rust 实现:https://docs.rs/pdqsort/latest/pdqsort/
// limit 在回退到堆排序之前,limit是允许的不良(非常不平衡)轴元素数量(在排序算法中,轴元素是用来划分数据集的元素,例如在快速排序中用来划分较大和较小元素的基准元素。)。
func pdqsort_func(data lessSwap, a, b, limit int) {
	// 插入排序极限:12
	const maxInsertion = 12

	var (
		wasBalanced    = true // whether the last partitioning was reasonably balanced
		wasPartitioned = true // whether the slice was already partitioned
	)

	for {
		length := b - a
		
		// 当长度不超12时采用插入排序
		if length <= maxInsertion {
			insertionSort_func(data, a, b)
			return
		}

		// 回退到堆排序
		if limit == 0 {
			heapSort_func(data, a, b)
			return
		}

		// If the last partitioning was imbalanced, we need to breaking patterns.
		if !wasBalanced {
			breakPatterns_func(data, a, b)
			limit--
		}

		pivot, hint := choosePivot_func(data, a, b)
		if hint == decreasingHint {
			reverseRange_func(data, a, b)
			// The chosen pivot was pivot-a elements after the start of the array.
			// After reversing it is pivot-a elements before the end of the array.
			// The idea came from Rust's implementation.
			pivot = (b - 1) - (pivot - a)
			hint = increasingHint
		}

		// The slice is likely already sorted.
		if wasBalanced && wasPartitioned && hint == increasingHint {
			if partialInsertionSort_func(data, a, b) {
				return
			}
		}

		// Probably the slice contains many duplicate elements, partition the slice into
		// elements equal to and elements greater than the pivot.
		if a > 0 && !data.Less(a-1, pivot) {
			mid := partitionEqual_func(data, a, b, pivot)
			a = mid
			continue
		}

		mid, alreadyPartitioned := partition_func(data, a, b, pivot)
		wasPartitioned = alreadyPartitioned

		leftLen, rightLen := mid-a, b-mid
		balanceThreshold := length / 8
		if leftLen < rightLen {
			wasBalanced = leftLen >= balanceThreshold
			pdqsort_func(data, a, mid, limit)
			a = mid + 1
		} else {
			wasBalanced = rightLen >= balanceThreshold
			pdqsort_func(data, mid+1, b, limit)
			b = mid
		}
	}
}

时间复杂度:

go排序sort包详解_第4张图片
其他排序算法:TimSort

你可能感兴趣的:(GO,golang,排序算法,c语言)