go container/heap源码解析

本文介绍了如何实现堆数据结构及其操作,包括接口定义、堆化、插入与删除等方法,以IntHeap为例详细展示了小顶堆的实现过程和应用场景。

//heap的接口类:使用时候需要实现它的方法
type Interface interface {
	sort.Interface
	Push(x any) // add x as element Len()
	Pop() any   // remove and return element Len() - 1.
}

//shifDown()
//堆化(heapify):如果一个节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动
func down(h Interface, i0, n int) bool {
	i := i0
	for {
		j1 := 2*i + 1          //获取i这个下标所在节点的left child
		if j1 >= n || j1 < 0 { //验证是否超过堆数组范围
			break
		}
		j := j1
		if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
			j = j2 // = 2*i + 2  // right child
		}
		//父节点与最小(大)子节点比较,如果子节点比夫节点小(大)则替换
		if !h.Less(j, i) {
			break
		}
		h.Swap(i, j)
		i = j
	}
	return i > i0
}

//shifUp()
//如果一个节点比它的父节点大(最大堆)或者小(最小堆),
//那么需要将它同父节点交换位置。这样是这个节点在数组的位置上升。
func up(h Interface, j int) {
	for {
		i := (j - 1) / 2 // floor((i - 1)/2):获取j的父节点
		//最小堆时,如果夫节点比子节点大,交换;
		//最大堆时,如果夫节点比子节点小,则交换
		if i == j || !h.Less(j, i) {
			break
		}
		h.Swap(i, j)
		j = i
	}
}

//初始化时,从上往下堆化处理
func Init(h Interface) {
	// heapify
	n := h.Len()
	for i := n/2 - 1; i >= 0; i-- {
		down(h, i, n)
	}
}

//向尾部插入节点
func Push(h Interface, x any) {
	h.Push(x)        //先执行定义的方法
	up(h, h.Len()-1) //从下而上的排序堆
}

//返回并删除根节点数据
func Pop(h Interface) any {
	n := h.Len() - 1
	h.Swap(0, n)  //将根节点与最后一个节点交换
	down(h, 0, n) //堆化处理
	return h.Pop()
}

//删除指定节点
func Remove(h Interface, i int) any {
	n := h.Len() - 1
	if n != i {
		h.Swap(i, n)        //将删除的元素和最后一个元素交换
		if !down(h, i, n) { //堆化来修复,但是并不是唯一的情况,可能还需要up来向上修复
			up(h, i)
		}
	}
	return h.Pop()
}

示例


type IntHeap []int

func main() {
	h := &IntHeap{2, 1, 5, 6, 4, 3, 7, 9, 8, 0} // 创建slice
	heap.Init(h)                                // 将数组切片进行堆化
	dump.P(h)                                   // [0 1 3 6 2 5 7 9 8 4] 由Less方法可控制小顶堆
	dump.P(heap.Pop(h))                         // 调用pop 0 返回移除的顶部最小元素
	heap.Push(h, 6)                             // 调用push [1 2 3 6 4 5 7 9 8] 添加一个元素进入堆中进行堆化
	dump.P("new", h)                            // [1 2 3 6 4 5 7 9 8 6]
	for len(*h) > 0 {                           // 持续推出顶部最小元素
		fmt.Printf("%d \n ", heap.Pop(h))
	}
}

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) Pop() any {
	old := *h
	n := len(old)
	dump.P("old", old) // [1 2 3 6 4 5 7 9 8 0] 将顶小堆元素与最后一个元素交换位置,在进行堆排序的结果

	x := old[n-1]
	*h = old[0 : n-1]
	dump.P(h) // [1 2 3 6 4 5 7 9 8]
	return x
}

func (h *IntHeap) Push(x any) { // 绑定push方法,插入新元素
	*h = append(*h, x.(int))
}

<think>我们使用Go标准库中的container/heap包来实现一个(优先队列)。是一种完全二叉树,通常用于实现优先队列。在Go中,heap包提供了操作的接口,我们需要实现这个接口来使用heap.Interface接口定义如下: ```go type Interface interface { sort.Interface Push(x interface{}) // 将x添加为元素Len() Pop() interface{} // 移除并返回元素Len()-1 } ``` 而sort.Interface包含: ```go type Interface interface { Len() int Less(i, j int) bool // 决定是最大还是最小 Swap(i, j int) } ``` 因此,我们需要定义一个类型(比如一个切片),并实现这五个方法:Len, Less, Swap, Push, Pop。 ### 示例:整数最小 下面是一个整数最小的示例。最小即根节点是最小值。 ```go package main import ( "container/heap" "fmt" ) // 定义一个类型,这里使用整数切片 type IntHeap []int // 实现sort.Interface接口 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] } // 实现heap.Interface的Push方法 func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) // 将x添加到中 } // 实现heap.Interface的Pop方法 func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] // 取出最后一个元素 *h = old[0 : n-1] // 修改切片长度 return x } func main() { h := &IntHeap{2, 1, 5} // 初始化一个 heap.Init(h) // 初始化,调整结构 heap.Push(h, 3) // 插入新元素 fmt.Printf("最小值: %d\n", (*h)[0]) // 最小的根是最小值,但不一定是唯一的最小值,但保证根是最小 for h.Len() > 0 { fmt.Printf("%d ", heap.Pop(h)) // 依次弹出最小值 } } ``` 输出: ``` 最小值: 1 1 2 3 5 ``` ### 示例:最大 如果要实现最大,只需修改Less方法: ```go func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // 最大,使用大于号 ``` ### 示例:使用排序 实际上,我们可以利用来进行排序(排序)。上面的示例中,Pop操作每次弹出最小值(对于最小)或最大值(对于最大),因此依次弹出就得到了有序序列。 ### 示例:自定义类型 假设我们有一个任务结构体,每个任务有优先级,我们按照优先级高低(优先级值越小越优先)来处理任务。 ```go package main import ( "container/heap" "fmt" ) type Task struct { priority int name string } // 定义一个任务 type TaskHeap []Task func (h TaskHeap) Len() int { return len(h) } func (h TaskHeap) Less(i, j int) bool { // 优先级的数值越小,优先级越高 return h[i].priority < h[j].priority } func (h TaskHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *TaskHeap) Push(x interface{}) { *h = append(*h, x.(Task)) } func (h *TaskHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } func main() { tasks := &TaskHeap{ {priority: 3, name: "Task3"}, {priority: 1, name: "Task1"}, {priority: 2, name: "Task2"}, } heap.Init(tasks) heap.Push(tasks, Task{priority: 4, name: "Task4"}) heap.Push(tasks, Task{priority: 0, name: "Task0"}) // 最高优先级 // 按优先级顺序处理任务 for tasks.Len() > 0 { task := heap.Pop(tasks).(Task) fmt.Printf("处理任务: %s (优先级: %d)\n", task.name, task.priority) } } ``` 输出: ``` 处理任务: Task0 (优先级: 0) 处理任务: Task1 (优先级: 1) 处理任务: Task2 (优先级: 2) 处理任务: Task3 (优先级: 3) 处理任务: Task4 (优先级: 4) ``` ### 注意事项 1. 在初始化时,必须调用`heap.Init(h)`来调整结构,否则可能不是合法的。 2. 使用`heap.Push`和`heap.Pop`(而不是自定义类型的Push和Pop方法)来操作,因为这两个函数会维护的性质。 3. 在自定义类型的中,Pop方法通常是从末尾弹出,但内部会通过调整来保证顶元素是最值。 通过以上示例,你应该可以掌握在Go中使用container/heap包的方法。根据实际需求,你可以定义不同的类型并实现接口。 ### 相关问题 1. 如何实现一个固定大小的(比如只保留最大的k个元素)? 2. 在中如何更新一个元素的值并重新调整? 3. 如何避免在操作中使用反射(interface{})来提升性能?</think># Go语言 container/heap 包使用详解 在Go语言中,`container/heap`包提供了数据结构的实现,可用于实现优先队列。是一种特殊的树形数据结构,通常用于高效获取集合中的最大或最小元素。 ## 的基本概念 是一种满足以下性质的完全二叉树: - **最小**:每个节点的值都小于或等于其子节点的值(根节点最小) - **最大**:每个节点的值都大于或等于其子节点的值(根节点最大) 在Go中,的插入和删除操作的时间复杂度为$O(\log n)$,获取最小/最大元素的时间复杂度为$O(1)$[^2]。 ## 实现接口 要使用`container/heap`包,需要实现以下接口: ```go type Interface interface { sort.Interface Push(x interface{}) // 添加元素 Pop() interface{} // 移除并返回元素 } ``` 其中`sort.Interface`包含: ```go Len() int Less(i, j int) bool // 决定最小或最大 Swap(i, j int) ``` ## 最小示例 ```go package main import ( "container/heap" "fmt" ) // 定义整数类型 type IntHeap []int // 实现sort.Interface 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] } // 实现heap.Interface的Push方法 func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) } // 实现heap.Interface的Pop方法 func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } func main() { h := &IntHeap{2, 1, 5} heap.Init(h) // 初始化 heap.Push(h, 3) // 插入新元素 fmt.Printf("最小值: %d\n", (*h)[0]) // 查看最小值 // 依次弹出最小值 for h.Len() > 0 { fmt.Printf("%d ", heap.Pop(h)) } // 输出: 1 2 3 5 } ``` ## 最大示例 只需修改Less方法即可实现最大: ```go func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] // 最大 } // 使用相同代码,输出将是: 5 3 2 1 ``` ## 自定义类型示例 ```go type Item struct { value string priority int } type PriorityQueue []*Item func (pq PriorityQueue) Len() int { return len(pq) } // 优先级高的先出(最大) func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*Item) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } func main() { pq := make(PriorityQueue, 0) heap.Init(&pq) heap.Push(&pq, &Item{value: "紧急任务", priority: 3}) heap.Push(&pq, &Item{value: "普通任务", priority: 1}) heap.Push(&pq, &Item{value: "高优先级", priority: 5}) for pq.Len() > 0 { item := heap.Pop(&pq).(*Item) fmt.Printf("%s (优先级: %d)\n", item.value, item.priority) } // 输出: // 高优先级 (优先级: 5) // 紧急任务 (优先级: 3) // 普通任务 (优先级: 1) } ``` ## 操作注意事项 1. **初始化**:使用前必须调用`heap.Init(h)`初始化结构 2. **操作函数**:使用`heap.Push()`和`heap.Pop()`而非直接调用类型的Push/Pop方法 3. **性能特点**: - 插入操作$O(\log n)$ - 删除操作$O(\log n)$ - 获取最小/最大值$O(1)$ 4. **适用场景**:优先队列、Dijkstra算法、排序等 ## 固定大小实现 ```go type FixedSizeHeap struct { heap []int size int maxSize int } // 实现heap.Interface方法... func (h *FixedSizeHeap) Push(x interface{}) { if h.size < h.maxSize { h.heap = append(h.heap, x.(int)) h.size++ } else if x.(int) > h.heap[0] { // 对于最大 h.heap[0] = x.(int) heap.Fix(h, 0) } } // 使用示例:保留最大的10个元素 h := &FixedSizeHeap{maxSize: 10} heap.Init(h) ``` ## 常见问题解答 **Q: 和普通队列有什么区别?** A: 普通队列是先进先出(FIFO),而是按照优先级出队,最高优先级的元素总是先出队。 **Q: 什么时候应该使用而不是平衡二叉搜索树?** A: 当只需要访问最小或最大元素而不需要完全有序遍历时,是更好的选择,因为它的实现更简单且常数因子更小。 **Q: 排序如何实现?** A: 排序分为两步: 1. 将数组构建为:$O(n)$ 2. 重复提取根节点并调整:$O(n \log n)$ 总时间复杂度$O(n \log n)$ ## 相关问题 1. 如何实现排序算法? 2. 在Dijkstra算法中如何使用优化性能? 3. 与平衡二叉搜索树(BST)在性能和应用场景上有何区别? 4. 如何实现一个支持元素更新的优先队列? 5. Gocontainer/heap包在并发场景下是否安全?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值