优先队列与广度搜索

什么是优先队列?

你可以把优先队列想象成“插队”的队伍。普通队列是“先来先服务”,谁先排队谁先走。但优先队列不一样,谁的“优先级”高,谁就能先走。

比如:

  • 医院急诊,病情重的先看。
  • 游戏里,分数高的玩家先上榜。

优先队列常用的实现方法是“堆”,比如二叉堆。

优先队列常见的操作:

  • 加入一个元素(push)
  • 取出优先级最高的元素(pop/top)

在C++里有std::priority_queue,Go语言里可以用container/heap包。

举个例子(Go语言):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import (
"container/heap"
"fmt"
)

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
}

func main() {
h := &IntHeap{}
heap.Init(h)
heap.Push(h, 3)
heap.Push(h, 1)
heap.Push(h, 5)
fmt.Println((*h)[0]) // 输出5,最大值先出来
heap.Pop(h)
fmt.Println((*h)[0]) // 输出3
}

什么是广度优先搜索(BFS)?

广度优先搜索(BFS)其实就是“按圈扩散”。

想象你在操场上,站在起点,每次都走一步,先走离你最近的,再走远一点的。

BFS用的是普通队列,保证大家一圈一圈地被访问。

BFS的基本步骤:

  1. 把起点放进队列,并记下来已经来过。
  2. 不断从队列里拿出一个点,看看它能到哪些新地方,把没去过的都加进队列。
  3. 队列空了,说明所有能到的地方都走过了。

举个例子(Go语言):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main
import (
"container/list"
"fmt"
)

func bfs(graph [][]int, start int) {
visited := make([]bool, len(graph))
q := list.New()
q.PushBack(start)
visited[start] = true
for q.Len() > 0 {
e := q.Front()
u := e.Value.(int)
fmt.Print(u, " ")
q.Remove(e)
for _, v := range graph[u] {
if !visited[v] {
visited[v] = true
q.PushBack(v)
}
}
}
}

func main() {
graph := [][]int{{1, 2}, {0, 3}, {0, 3}, {1, 2}}
bfs(graph, 0) // 输出0 1 2 3
}

两者的区别和联系

BFS用的是普通队列,适合“每一步花费都一样”的场景,比如迷宫最短路。

优先队列适合“每一步花费不一样”的场景,比如有的路长有的路短,这时候我们希望每次都先走“最省力”的那条路。

比如著名的Dijkstra算法,就是用优先队列来找最短路。

总结:

  • BFS用普通队列,适合每一步代价一样的情况。
  • 优先队列适合每一步代价不一样、需要优先处理“最优解”的情况。

为什么有时候要用优先队列来做广度搜索?

如果每一步的花费都一样(比如每走一步都花1元),用普通队列就行。

但如果有的路花1元,有的路花100元,我们当然想先走便宜的路。这时候就要用优先队列,每次都优先走“目前最省钱”的那条。

简单说:

  • 每一步代价一样,用普通队列。
  • 每一步代价不一样,用优先队列。

优先队列让搜索既能“按顺序扩展”,又能“按优先级扩展”,能解决更多实际问题。