首页 > 编程语言 >05_数据结构与算法

05_数据结构与算法

时间:2023-10-08 13:24:53浏览次数:46  
标签:sort 排序 return 05 int 算法 func interface 数据结构

Sort 排序算法

sort 包中实现了四种基本排序算法:插入排序、归并排序、堆排序、快速排序。但是它们不公开,只供sort包内部自己使用,所以在需要实现数据排序时不必考虑使用哪一种排序方法,只要实现了 sort.Interface 定义的三个方法: 获取数据集合长度Len()、比较两个元素大小Less()、交换两个元素位置Swap() 就可以完成对数据集合的自定义排序。sort 内部再根据实际数据自动选择高效的排序算法。

type Interface interface {
        // 获取数据集合元素个数
        Len() int
        // 如果 i 索引的数据小于 j 索引的数据,返回 true,且不会调用下面的 Swap(),即数据升序排序。
        Less(i, j int) bool
        // 交换 i 和 j 索引的两个元素的位置
        Swap(i, j int)
}

自定义的数据结构只要实现了以上接口的三个方法就可以实现自定义排序的功能,可以通过调用该包的 Sort()方法进行排序。

同时还提供了以下的一些方法:

func Sort(data Interface)                // 对数据集进行排序
func IsSorted(data Interface) bool       // 判断数据集合是否有序
func Reverse(data Interface) Interface   // 将数据按照Less()定义的排序方式逆序排序
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。

sort 的使用示例

// 示例1,使用sort来完成对学生结构体数组的自定义排序
type StuScore struct {
	name  string
	score int
}
type StuScores []StuScore

func (s StuScores) Len() int {
	return len(s)
}
func (s StuScores) Less(i, j int) bool {
	return s[i].score > s[j].score
}
func (s StuScores) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func TestStuScores() {
	stus := StuScores{
		{"alan", 95},
		{"hikerell", 91},
		{"acmfly", 96},
		{"leao", 90},
	}

	// 打印未排序的 stus 数据
	fmt.Println("Default:\n\t", stus)
	//StuScores 已经实现了 sort.Interface 接口 , 所以可以调用 Sort 函数进行排序
	sort.Sort(stus)
	// 判断是否已经排好顺序,将会打印 true
	fmt.Println("IS Sorted?\n\t", sort.IsSorted(stus))
	// 打印排序后的 stus 数据
	fmt.Println("Sorted:\n\t", stus)
	// Reverse 升序排序
	sort.Sort(sort.Reverse(stus))
	fmt.Println("升序\n\t", stus)
	// 测试 Search 函数,它会找到满足 bool为true的最小i的位置
	pos := sort.Search(len(stus), func(i int) bool {
		return stus[i].score >= 95
	})
	fmt.Println("pos: ", pos)
}

// 示例2,使用sort的Search方法完成猜数字游戏
func GuessingGame() {
	var s string
	fmt.Printf("Pick an integer from 0 to 100.\n")
	answer := sort.Search(100, func(i int) bool {
		fmt.Printf("Is your number <= %d? ", i)
		fmt.Scan(&s)
		return s != "" && s[0] == 'y'
	})
	fmt.Printf("Your number is %d.\n", answer)
}

sort包支持的内部数据类型排序

sort 包原生支持 []int、[]float64、[]string 三类内建数据类型切片的排序操作。

1. IntSlice 类型及 []int 排序

[]int 类型转化为 IntSlice 类型: sort.IntSlice(s)

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] }
//IntSlice 类型定义了 Sort() 方法,包装了 sort.Sort() 函数
func (p IntSlice) Sort() { Sort(p) }
//IntSlice 类型定义了 SearchInts() 方法,包装了 SearchInts() 函数
func (p IntSlice) Search(x int) int { return SearchInts(p, x) }

IntSlice 类型是 []int 切片类型实现 sort.Interface 接口后所产生的一种数据类型。并且提供了以下几种常用方法:

func Ints(a []int) { Sort(IntSlice(a)) }     // 对 []int 类型数据进行排序
func SearchInts(a []int, x int) int          // 对 []int 类型数据进行查找,使用前提是集合已经完成了排序
func IntsAreSorted(a []int) bool             // 判断是否完成排序
sort.Sort(sort.Reverse(sort.IntSlice(s)))    // 对 []int 类型数据进行反转排序

使用示例:

func TestSortInt() {
	s := []int{5, 2, 6, 3, 1, 4}
	res := sort.IsSorted(sort.IntSlice(s))
	fmt.Println(res)
	sort.Ints(s)
	res = sort.IsSorted(sort.IntSlice(s))
	fmt.Println(s)
	fmt.Println(res)
	pos := sort.SearchInts(s, 3)
	fmt.Println(pos)
}

2. Float64Slice 类型及[]float64 排序

[]Float64 类型转化为 Float64Slice :sort.Float64Slice(s)

type Float64Slice []float64

func (p Float64Slice) Len() int           { return len(p) }
func (p Float64Slice) Less(i, j int) bool { return p[i] < p[j] || isNaN(p[i]) && !isNaN(p[j]) }
func (p Float64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Float64Slice) Sort() { Sort(p) }
func (p Float64Slice) Search(x float64) int { return SearchFloat64s(p, x) }

[]Float64 类型常用的3种方法:

func Float64s(a []float64)                          // 排序,默认升序
func Float64sAreSorted(a []float64) bool            // 判断是否完成排序
func SearchFloat64s(a []float64, x float64) int     // 查找

使用示例:

func TestSortFloat() {
	s := []float64{1.11, 2.22, 3.33, 4.44, 5.55}
    res := sort.IsSorted(sort.Float64Slice(s))    // 使用 sort.IsSorted(xxx) 方法时内部需要传入实现了接口的类型
	fmt.Println(res)
	sort.Float64s(s)
	//res = sort.IsSorted(sort.Float64Slice(s))   这种方法也可以
	res = sort.Float64sAreSorted(s)
	fmt.Println(res)
	pos := sort.SearchFloat64s(s, float64(2.22))
	fmt.Println(pos)
	sort.Sort(sort.Reverse(sort.Float64Slice(s)))
	fmt.Println(s)
}

3. StringSlice 类型及[]string 排序

[]string 转化为 stringSlice:sort.stringSlice(s)

type StringSlice []string

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

[]string 常用的几种方法:

func Strings(a []string)
func StringsAreSorted(a []string) bool
func SearchStrings(a []string, x string) int

使用示例:

func TestSortString() {
	s := []string{"aaa", "bbb", "ccc", "ddd"}
	res := sort.IsSorted(sort.StringSlice(s))
	fmt.Println(res)
	sort.Strings(s)
	res = sort.StringsAreSorted(s)
	fmt.Println(res)
	pos := sort.SearchStrings(s, "aaa")
	fmt.Println(pos)
	sort.Sort(sort.Reverse(sort.StringSlice(s)))
	fmt.Println(s)
}

4. 其它数据类型如何排序

上面介绍了几种基本数据类型如何运用 sort 包完成排序功能,那么对于其它一般的数据类型我们该如何实现呢?

如果对于多个属性,排序可能不知道按照何种规则进行排序,所以 sort 包提供了以下几种方法来完成排序:

func Slice(slice interface{}, less func(i, j int) bool) 
func SliceStable(slice interface{}, less func(i, j int) bool) 
func SliceIsSorted(slice interface{}, less func(i, j int) bool) bool 
func Search(n int, f func(int) bool) int

上面三个函数都需要接收 []interface 作为参数,同时还需要一个匿名函数作为排序规则的制定,该匿名函数最后返回布尔

sort.Slice(xxx)

完成对 []interface 的自定义排序

func TestSortSB() {
	people := []struct {
		Name string
		Age  int
	}{
		{"Gopher", 7},
		{"Alice", 55},
		{"Vera", 24},
		{"Bob", 75},
	}
	// 实现sort实现接口后自定义排序
	sort.Slice(people, func(i, j int) bool {
		return people[i].Age < people[j].Age
	})
	fmt.Println(people)
}

sort.SliceStable(xxx)

完成对 []interface 的自定义稳定排序

people := []struct {
    Name string
    Age  int
}{
    {"Gopher", 7},
    {"Alice", 55},
    {"Vera", 24},
    {"Bob", 75},
}
// 稳定排序
sort.SliceStable(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
fmt.Println("Sort by age:", people)

sort.SliceIsSorted(xxx)

判断 []interface 是否为有序

people := []struct {
    Name string
    Age  int
}{
    {"Gopher", 7},
    {"Alice", 55},
    {"Vera", 24},
    {"Bob", 75},
}

sort.Slice(people, func(i, j int) bool { return people[i].Age > people[j].Age }) // 按年龄降序排序
fmt.Println("Sort by age:", people)
fmt.Println("Sorted:",sort.SliceIsSorted(people,func(i, j int) bool { return people[i].Age < people[j].Age }))

sort.Search(xxx)

在排序集合种进行数据查找

func TestIsSort() {
	people := []struct {
		Name string
		Age  int
	}{
		{"Gopher", 7},
		{"Alice", 55},
		{"Vera", 24},
		{"Bob", 75},
	}
	// 实现sort实现接口后自定义排序
	sort.Slice(people, func(i, j int) bool {
		return people[i].Age < people[j].Age
	})
	// 判断是否完成了指定规则的排序
	fmt.Println(sort.SliceIsSorted(people, func(i, j int) bool {
		return people[i].Age > people[j].Age
	}))
	// 实现查找
	pos := sort.Search(len(people), func(i int) bool {
		return people[i].Age >= 55
	})
	fmt.Println(pos)
}

堆排序参考:https://blog.csdn.net/qq_28063811/article/details/93034625/

go如何使用堆:https://www.jianshu.com/p/5dcfec42d3d9

heap源码分析:https://blog.csdn.net/m0_37965811/article/details/117911909

1. 简单使用

堆使用的数据结构是最小二叉树,即根节点比左子树和右子树的所有值都要小。它的内部也包括了 sort.Interface 结构,也就是说如果要使用go语言中的堆,需要实现 Len()、Less()、Swap()方法和 Push()、Pop()方法。

type Interface interface {
    sort.Interface
    Push(x interface{}) // add x as element Len()
    Pop() interface{}   // remove and return element Len() - 1.
}
type IntHeap []int

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

func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

堆提供的方法如下:

h := &IntHeap{3, 8, 6}  // 创建IntHeap类型的原始数据
func Init(h Interface)  // 对heap进行初始化,生成小根堆(或大根堆)
func Push(h Interface, x interface{})  // 往堆里面插入内容
func Pop(h Interface) interface{}  // 从堆顶pop出内容
func Remove(h Interface, i int) interface{}  // 从指定位置删除数据,并返回删除的数据
func Fix(h Interface, i int)  // 从i位置数据发生改变后,对堆再平衡,优先级队列使用到了该方法

2. heap源码解析

参考: https://blog.csdn.net/m0_37965811/article/details/117911909

heap 内部实现的 down 和 up 分别表示对堆中的某个元素向下保证最小(最大)堆和向上保证最小(最大)堆。最小还是最大由 Less() 函数决定;

当往堆中插入一个元素时,这个元素插入到最右子树的最后一个节点中,然后调用 up() 向上保证最小(最大)堆;

当从堆中推出一个元素时,先把该元素和右子树的最后一个节点交换,然后对 root 调用 down() 向下保证最小(最大)堆;

// 初始化堆结构
func Init(h Interface) {
	// heapify
	n := h.Len()  // 获取数据的长度
	for i := n/2 - 1; i >= 0; i-- {  // 从长度的一半开始,一直到第0个数据,每个位置都调用down方法,down方法实现的功能是保证从该位置往下保证形成堆
		down(h, i, n)
	}
}

// 取左右节点中最大(最小)值和当前节点交换,然后继续维护交换后的子节点
func down(h Interface, i0, n int) bool {
	i := i0  // 中间变量,第一次存储的是需要保证往下需要形成堆的节点位置
	for {  // 死循环
		j1 := 2*i + 1  // i节点的左子孩子
		if j1 >= n || j1 < 0 { // j1 < 0 after int overflow  // 保证其左子孩子没有越界
			break
		}
		j := j1 // left child  // 中间变量j先赋值为左子孩子,之后j将被赋值为左右子孩子中最小(大)的一个孩子的位置
		if j2 := j1 + 1; j2 < n && !h.Less(j1, j2) {
			j = j2 // = 2*i + 2  // right child
		}  // 这之后,j被赋值为两个孩子中的最小(大)孩子的位置(最小或最大由Less中定义的决定)
		if !h.Less(j, i) {
			break
		}  // 若j大于(小于)i,则终止循环
		h.Swap(i, j)  // 否则交换i和j位置的值
		i = j  // 令i=j,继续循环,保证j位置的子数是堆结构
	}
	return i > i0
}

// up同down原理类似,区别在于交换后继续处理的是父节点
func up(h Interface, j int) {
	for {  // 死循环
		i := (j - 1) / 2 // parent  // j节点的父节点
		if i == j || !h.Less(j, i) {  // 如果越界,或者满足堆的条件,则结束循环
			break
		}
		h.Swap(i, j)  // 否则将该节点和父节点交换
		j = i  // 对父节点继续进行检查直到根节点
	}
}

// 其余方法源码
func Push(h Interface, x interface{}) {
	h.Push(x)  // 将新插入进来的节点放到最后
	up(h, h.Len()-1)  // 确保新插进来的节点网上能保证堆结构
}
 
func Pop(h Interface) interface{} {
	n := h.Len() - 1  // 把最后一个节点和第一个节点进行交换,之后,从根节点开始重新保证堆结构,最后把最后那个节点数据丢出并返回
	h.Swap(0, n)
	down(h, 0, n)
	return h.Pop()
}
 
func Remove(h Interface, i int) interface{} {
	n := h.Len() - 1  pop只是remove的特殊情况,remove是把i位置的节点和最后一个节点进行交换,之后保证从i节点往下及往上都保证堆结构,最后把最后一个节点的数据丢出并返回
	if n != i {
		h.Swap(i, n)
		down(h, i, n)
		up(h, i)
	}
	return h.Pop()
}
 
func Fix(h Interface, i int) {
	if !down(h, i, h.Len()) {  // i节点的数值发生改变后,需要保证堆的再平衡,先调用down保证该节点下面的堆结构,如果有位置交换,则需要保证该节点往上的堆结构,否则就不需要往上保证堆结构,一个小小的优化
		up(h, i)
	}
}

3. heap实现优先队列

package dataStruct

import (
	"container/heap"
	"fmt"
)

// 队列中存储的节点
type Item struct {
	Value    string // 队列中的数据,可以是任意类型
	Priority int    // 优先级
	Index    int    // 在队列中的位置
}

type PriorityQueue []*Item

// 绑定 Len() 方法
func (pq PriorityQueue) Len() int {
	return len(pq)
}

// 绑定 Less() 方法---小根堆
func (pq PriorityQueue) Less(i, j int) bool {
	return pq[i].Priority < pq[j].Priority
}

// 绑定 Swap() 方法
func (pq PriorityQueue) Swap(i, j int) {
	pq[i], pq[j] = pq[j], pq[i]
	pq[i].Index, pq[j].Index = j, i
}

// 出队列
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	x := old[n-1]
	*pq = old[0 : n-1]
	x.Index = -1
	return x
}

// 入队列
func (pq *PriorityQueue) Push(x interface{}) {
	n := len(*pq)
	item := x.(*Item)
	item.Index = n
	*pq = append(*pq, item)
}

// 更新优先级及节点在队列中的位置
func (pq *PriorityQueue) update(item *Item, value string, priority int) {
	item.Value = value
	item.Priority = priority
	heap.Fix(pq, item.Index)
}

func TestPriorityQueue() {
	items := map[string]int{"二毛": 5, "张三": 3, "狗蛋": 9}
	i := 0
	pq := make(PriorityQueue, len(items)) // 创建优先队列并初始化
	for k, v := range items {
		pq[i] = &Item{
			Value:    k,
			Priority: v,
			Index:    i,
		}
		i++
	}
	heap.Init(&pq)
	item := &Item{
		Value:    "李四",
		Priority: 1,
	}
	heap.Push(&pq, item)
	pq.update(item, item.Value, 6)
	for len(pq) > 0 {
		item := heap.Pop(&pq).(*Item)
		fmt.Printf("%.2d:%s index:%.2d\n", item.Priority, item.Value, item.Index)
	}
}

链表

链表就是一个有 prev 和 next 指针的数组,Go语言中通过维护两个Type来定义链表结构

type Element struct {
    next, prev *Element  // 上一个元素和下一个元素
    list *List  // 元素所在链表
    Value interface{}  // 元素
}

type List struct {
    root Element  // 链表的根元素
    len  int      // 链表的长度
}

list 对应方法如下:

type Element
    func (e *Element) Next() *Element
    func (e *Element) Prev() *Element
type List
    func New() *List                 // 生成List结构
    func (l *List) Back() *Element   // 最后一个元素
    func (l *List) Front() *Element  // 第一个元素
    func (l *List) Init() *List  // 链表初始化
    func (l *List) InsertAfter(v interface{}, mark *Element) *Element // 在某个元素后插入
    func (l *List) InsertBefore(v interface{}, mark *Element) *Element  // 在某个元素前插入
    func (l *List) Len() int // 在链表长度
    func (l *List) MoveAfter(e, mark *Element)  // 把 e 元素移动到 mark 之后
    func (l *List) MoveBefore(e, mark *Element)  // 把 e 元素移动到 mark 之前
    func (l *List) MoveToBack(e *Element) // 把 e 元素移动到队列最后
    func (l *List) MoveToFront(e *Element) // 把 e 元素移动到队列最头部
    func (l *List) PushBack(v interface{}) *Element  // 在队列最后插入元素
    func (l *List) PushBackList(other *List)  // 在队列最后插入接上新队列
    func (l *List) PushFront(v interface{}) *Element  // 在队列头部插入元素
    func (l *List) PushFrontList(other *List) // 在队列头部插入接上新队列
    func (l *List) Remove(e *Element) interface{} // 删除某个元素

环不需要像 list 一样保持 list 和 element 两个结构,只需要保持一个结构。

type Ring struct {
    next, prev *Ring
    Value      interface{}
}

环提供如下方法:

type Ring
    func New(n int) *Ring  // 初始化环
    func (r *Ring) Do(f func(interface{}))  // 循环环进行操作
    func (r *Ring) Len() int // 环长度
    func (r *Ring) Link(s *Ring) *Ring // 连接两个环
    func (r *Ring) Move(n int) *Ring // 指针从当前元素开始向后移动或者向前(n 可以为负数)
    func (r *Ring) Next() *Ring // 当前元素的下个元素
    func (r *Ring) Prev() *Ring // 当前元素的上个元素
    func (r *Ring) Unlink(n int) *Ring // 从当前元素开始,删除 n 个元素

标签:sort,排序,return,05,int,算法,func,interface,数据结构
From: https://www.cnblogs.com/istitches/p/17748641.html

相关文章

  • 页帧的数据结构设计
    前言页帧page是物理内存管理的基本单位,structpage记录了任意时刻page的所有状态,因此每一个物理页帧都需一个对应的structpage结构体记录状态,对于内存多计算机系统来说需要的structpage本身就需要大量内存进行存储,因此该结构体中每增加一个变量带来的代价会很大,需要仔细控制该......
  • Mysql join算法深入浅出
    导语联表查询在日常的数据库设计中非常的常见,但是联表查询可能会带来性能问题,为了调优、避免设计出有性能问题的SQL,在explain命令中,会显示用的是哪个join算法,学习一下join过程是非常有必要的当执行下面这个SQLJoin,在不同的情况下会产生不一样的复杂度select*fromusertb1l......
  • 内存管理中的关键数据结构
    前言在谈Linux内存管理框架之前需要了解NUMA,NUMA是非一致性内存访问(Uon-UniformMemoryAccess)的缩写,与之相反的是一致性内存访问UMA。在多核的UMA架构的机器上,CPU视角下所有的内存都是均匀的,不同CPU访问同一块内存的延迟是相同;而在NUMA架构的机器上内存被划分为不同的区域,对CP......
  • C# Dx截图初始化报错“SharpDX.SharpDXException: HRESULT: [0x80070057], Module: [G
    最近发现Dx截图创建输出设备时output.QueryInterface<Output1>().DuplicateOutput报错:“SharpDX.SharpDXException:HRESULT:[0x80070057],Module:[General],ApiCode:[E_INVALIDARG/InvalidArguments],Message:参数错误。经过验证,如果一个进程多次创建输出设备(多次调用o......
  • 匈牙利算法简介与应用
     一、分配问题应用案例:1、男女相亲场景,10男10女为例,可让每人对每个异性进行意向度排序,若是男性优先则可以用男性意向度评分矩阵,女性优先同理,或者使用男女意向评分平均值作为意向度居正,然后用匈牙利算法求最大值,即可获得综合意向度得分最高的分配方法2、电销和催收用户分配场景,......
  • 10.8算法
     合并两个有序数组给你两个按非递减顺序排列的整数数组 nums1和nums2,另有两个整数m和n,分别表示nums1和nums2中的元素数目。请你合并nums2到nums1中,使合并后的数组同样按非递减顺序排列。注意:最终,合并后数组不应由函数返回,而是存储在数组nums1中。为了应......
  • 使用最短路径算法检查项目循环依赖
    最近项目组让我做一个自研的小工具,用来检查代码里的循环依赖,这里做下记录。思路由于工作是网络算路的,第一个想法就是通过路径计算来实现这个功能:把项目里test,resource等文件夹排除,剩下的每一个java文件可以算是对应一个类,把每个类看做是网络/路网里的节点,把类与类之间的依赖关......
  • Mysql 分布式序列算法
    接上文Mysql分库分表1.分布式序列简介在分布式系统下,怎么保证ID的生成满足以上需求?ShardingJDBC支持以上两种算法自动生成ID。这里,使用ShardingJDBC让主键ID以雪花算法进行生成,首先配置数据库,因为默认的注解id是int类型,装不下64位,需要进行修改:#在本地和远端服务器数据......
  • JavaSE基础05(方法,重载,调用,类和对象,构造器,封装,继承,方法重写,抽象类,接口,异常)
    面向对象以类的方式组织代码,以对象的组织封装数据;一个Java文件只能有一个public类,必须和文件名一样;java文件里也可以没有public类; 方法的定义方法的使用,修饰符返回值类型方法名(参数类型参数名){方法体return返回值};参数类型包括:基本数据类型和引用数据类......
  • 数据结构的思维导图(帮助梳理脉络)
    编辑......