Golang使用Sort包排序小结

转载自:

https://segmentfault.com/a/1190000008062661

https://www.jianshu.com/p/6cdbba0e30b5

 

Go的sort包提供了封装好的排序方法

对基础类型排序可以调用sort.Ints(IntList)、sort.Float64s(FloatList)、sort.Strings(StringList)等方法,默认为从小到大的升序排序,若想要修改为降序排序,则对Less()方法进行修改即可,Less方法是Sort包排序比较的核心,它定义了排序究竟是使用什么方式进行比较,对于基础类型可以使用sort.Sort(sort.Reverse(sort.IntSlice(IntList)))这种封装好的方法进行排序,无需对sort的方法进行重载

对于结构体排序算法,我们需要先理解sort包n内部是如何实现的:

func Sort(data Interface) {
	n := data.Len()
	quickSort(data, 0, n, maxDepth(n))
}

这是Sort()的实现代码,可以看到其内部是使用快排实现的,除了快排以外,Sort包还提供了插入、堆排、归并的实现:

//插入排序
func insertionSort(data Interface, a, b int)
//堆排序
func heapSort(data Interface, a, b int) 
//快速排序
func quickSort(data Interface, a, b, maxDepth int)
//归并排序
func symMerge(data Interface, a, m, b int) 

以快排的代码为例,看看Sort包是如何实现排序的:

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)
	}
}

这里我们要注意的是data.Less()、data.Swap()这两个方法,这两个方法决定了整个排序算法的比较和交换的方式,Sort使用接口的形式定义了这两个方法,也就是说我们可以在外面对这两个方法进行重载来实现我们想要的排序方式:

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的这个快排其实不是简单的快排,它是已经被优化过的了,在排序对象长度大于12的时候,先进行局部的快排,在深度等于0时再进行堆排序,在长度小于12时先进行一轮步长为6的希尔排序,最后再进行插入排序。

下面是实现的具体代码:

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

type Persons []Person
// 获取此 slice 的长度
func (p Persons) Len() int { return len(p) }
// 根据元素的年龄降序排序 (此处按照自己的业务逻辑写) 
func (p Persons) Less(i, j int) bool {
    return p[i].Age > p[j].Age
}
// 交换数据
func (p Persons) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func main() {
    persons := Persons{
        {
            Name: "test1",
            Age:  20,
        },
        {
            Name: "test2",
            Age:  22,
        },
        {
            Name: "test3",
            Age:  21,
        },
    }

    fmt.Println("排序前")
    for _, person := range persons {
        fmt.Println(person.Name, ":", person.Age)
    }
    sort.Sort(persons)
    fmt.Println("排序后")
    for _, person := range persons {
        fmt.Println(person.Name, ":", person.Age)
    }
}

一般来说Len()和Swap()方法都是不变的,只有涉及比较的方法Less()改变比较多,假如我们想要Person既可以按照Name进行排序,又留有按照Age排序的接口,那么就可以用嵌套结构体来实现,以C++的理解来讲,就是把相同的Len()和Swap()当成基类,然后在派生类中实现Less()方法,因为Go没有继承和派生,但可以使用嵌套结构体来实现这种思路:

package main

import (
	"fmt"
	"sort"
)

type Person struct {
	Name string
	Age  int
}

type Persons []Person

// Len()方法和Swap()方法不用变化
// 获取此 slice 的长度
func (p Persons) Len() int { return len(p) }

// 交换数据
func (p Persons) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

// 嵌套结构体  将继承 Person 的所有属性和方法
// 所以相当于SortByName 也实现了 Len() 和 Swap() 方法
type SortByName struct{ Persons }

// 根据元素的姓名长度降序排序 (此处按照自己的业务逻辑写)
func (p SortByName) Less(i, j int) bool {
	return len(p.Persons[i].Name) > len(p.Persons[j].Name)
}

type SortByAge struct{ Persons }

// 根据元素的年龄降序排序 (此处按照自己的业务逻辑写)
func (p SortByAge) Less(i, j int) bool {
	return p.Persons[i].Age > p.Persons[j].Age
}

func main() {
	persons := Persons{
		{
			Name: "test123",
			Age:  20,
		},
		{
			Name: "test1",
			Age:  22,
		},
		{
			Name: "test12",
			Age:  21,
		},
	}

	fmt.Println("排序前")
	for _, person := range persons {
		fmt.Println(person.Name, ":", person.Age)
	}
	sort.Sort(SortByName{persons})
	fmt.Println("排序后")
	for _, person := range persons {
		fmt.Println(person.Name, ":", person.Age)
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值