golang 堆中返回的值为什么是最后一个,不是0位
这涉及到 Go 中 sort.Slice
和堆(container/heap
) 实现的关键区别。虽然它们都使用 Less
函数,但行为完全不同:
1. sort.Slice
的 Less
函数(排序场景)
1 | sort.Slice(people, func(i, j int) bool { |
- 作用:直接定义最终排序顺序
- 结果:当
people[i].Age < people[j].Age
时,i
会排在j
前面 → 升序排列 - 内存布局:切片物理位置就是有序的,索引0是最小值,最后索引是最大值
2. 堆的 Less
函数(堆场景)
1 | type IntHeap []int |
- 作用:定义堆的父子节点关系(不是最终排序顺序!)
- 堆性质:在最小堆中,任意父节点 ≤ 子节点
- 内存布局:堆是半排序的树状结构(数组实现):
- 索引0位置是根节点(最小值)
- 最后一个元素位置不固定(可能大也可能小)
关键区别:Pop()
的实现
堆的 Pop()
操作有特殊处理:
1 | func (h *IntHeap) Pop() interface{} { |
为什么取最后一个元素? 因为在标准库 heap.Pop()
中已经做了前置操作:
1 | // 标准库 heap.Pop() 的伪代码: |
堆操作流程(最小堆为例)
初始化堆:
[3, 1, 5]
→ 堆化后:[1, 3, 5]
- 索引0=1(最小值)
- 最后索引=5
heap.Pop()
内部过程:- 交换根节点和最后节点:
[5, 3, 1]
- 重新堆化前n-1个元素:
[3, 5]
→ 堆化后[3, 5]
- 调用用户
Pop()
返回最后元素1
(最小值)
- 交换根节点和最后节点:
结果:
- 堆变为
[3, 5]
- 弹出值
1
(最小值)
- 堆变为
为什么最后位置不是最大值?
- 堆的物理存储不是全排序的,只保证父≤子
- 最后一个元素是最后一片叶子节点,可能很小也可能很大
- 通过
Swap(0, n-1)
操作,实际弹出的是原根节点(最小值)
堆的视觉表示
初始数组:[3, 1, 5]
堆化后内存布局:[1, 3, 5]
树状结构:
1 | 1 (根节点=最小值) |
Pop 时交换根和最后叶子:
1 | 5 (临时根) |
总结
特性 | sort.Slice | container/heap |
---|---|---|
Less 作用 | 定义最终排序顺序 | 定义父子节点关系 |
内存布局 | 完全有序 | 半有序(树状结构) |
索引0位置 | 最小值(升序时) | 根节点(最小值) |
最后索引位置 | 最大值(升序时) | 不固定(最后一个叶子节点) |
Pop() 行为 | 无此操作 | 返回交换后的最后一个元素(原根节点) |
因此,在堆实现中:
h[i] < h[j]
定义的是最小堆(根节点最小)Pop()
返回最小值,因为标准库在调用用户Pop()
前已交换了根节点和最后节点
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Lei Qi's Blog!
评论