1. 数据结构简介
1.1 队列
一句话来说便是“先入先出的数据结构”,队列是典型的 FIFO
数据结构,插入(insert
)操作也称作入队(enqueue
),新元素始终被添加在队列的末尾,删除(delete
)操作也被称为出队(dequeue
),你只能移除第一个元素
广度优先搜索算法(BFS)
广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径,越是接近根结点的结点将越早地遍历,如果在第k轮中将结点 x 添加到队列中,则根结点与 x 之间的最短路径的长度恰好是 k ,也就是说,第一次找到目标结点时,你已经处于最短路径中
- 模板
public int BFS(Node root, Node target) {
Queue<Node> queue = new LinkedList<>(); // 存储所有待处理的元素
Set<Node> used = new HashSet<>(); // 存储所有已经处理的元素
int step = 0; // 从头节点到目标节点的步数
// 初始化
queue.add(root);
used.add(root);
// 广度优先搜索主体
while (!queue.isEmpty()) {
step = step + 1;
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = queue.element();
if(cur == target){
return step;
}
for (Node n : cur.next) {
if (!used.contains(n)) {
queue.add(n);
used.add(n);
}
}
queue.remove();
}
}
return -1; // 自始至终没找到返回-1
}
1.2 栈
一句话来说便是“后入先出的数据结构”,与队列不同,栈是一个 LIFO
数据结构。通常,插入操作在栈中被称作入栈 push
,与队列类似,总是在堆栈的末尾添加一个新元素,但是,删除操作,退栈 pop
,将始终删除队列中相对于它的最后一个元素
深度优先搜索算法(DFS)
深度优先搜索算法会从头节点开始一直“向下”遍历,在我们到达最深的结点之后,仅会回溯并尝试另一条路径(回溯时将从栈中弹出最深的结点,即推入到栈中的最后一个结点)
注意事项
- 与 BFS 不同,更早访问的结点可能不是更靠近根节点的节点
- 使用 DFS 进行递归时,没有显式创建
Stack
,但实际上,我们使用的是系统提供的隐式栈,称为Call Stack
- 在大多数情况下,我们在能使用 BFS 时也可以使用 DFS
- 模板一:隐式栈
boolean DFS(Node cur, Node target, Set<Node> visited) {
if(target.equals(cur)){
return true;
}
for (n : cur.next) {
if (!visited.contains(cur)) {
visited.add(n);
return true if DFS(n, target, visited) == true;
}
}
return false;
}
- 模板二:显式栈
boolean DFS(int root, int target) {
Set<Node> visited;
Stack<Node> s;
s.add(root);
while (!s.isEmpty()) {
Node cur = s.peek()
if(target.equals(cur)){
return true;
}
for (Node n : cur.next)
if (!visited.contains(cur)) {
s.add(n);
visited.add(cur);
}
}
s.remove();
}
return false;
}
2.队列和栈在Java中的实现
2.1 队列 Queue<E>
简介
位于 java.util.Queue
包中,与 Stack
不同,此处的 Queue
仅仅是一个接口(与 List
、Set
同一级别),其常用的实现类有:
- 没有实现阻塞接口的
LinkedList
PriorityQueue
ConcurrentLinkedQueue
- 实现了阻塞接口的
ArrayBlockingQueue
一个由数组支持的有界队列LinkedBlockingQueue
一个由链接节点支持的可选有界队列PriorityBlockingQueue
一个由优先级堆支持的无界优先级队列DelayQueue
一个由优先级堆支持的、基于时间的调度队列SynchronousQueue
一个利用BlockingQueue
接口的简单聚集机制
常用方法(Interface
)
public boolean add(E e)
增加一个元索public boolean offer(E e)
添加一个元素并返回 truepublic E remove()
移除并返回队列头部的元素public E poll()
移除并返问队列头部的元素public E element()
返回队列头部的元素public E peek()
返回队列头部的元素
2.2 栈 Stack<E>
简介
位于 java.util.Stack
包中,是 Vector
的子类,故其大部分功能在 Vector
中已经实现,是一种对 Vector
的拓展
常用方法
public E push(E item)
把项压入堆栈顶部public synchronized E pop()
移除堆栈顶部的对象,并作为此函数的值返回该对象public synchronized E peek()
查看堆栈顶部的对象,但不从堆栈中移除它public boolean empty()
检测堆栈是否为空public synchronized int search(Object o)
返回对象在堆栈中的位置,1为起始位置
3. 例题剖析
题目:【墙与门】 https://leetcode-cn.com/problems/walls-and-gates/
解析: 由于需要房间到门的最短路径,我们可以使用 BFS 算法,每次向所选单位的四周移动,同时房间数量大于门的数量,如果以每个房间为单位进行查找,耗时较长,故此处可以以门为单位找房间,每个门都寻找一次所有的房间,将其值与当前门作对比并更新最小值,注意,若此处遇到障碍物不能继续走下去,下面的例子使用的是队列的方式,当然使用 DFS 的递归思路也可
class Solution {
class location {
int i;
int j;
public location(int i, int j) {
this.i = i;
this.j = j;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
location location = (location) o;
return i == location.i && j == location.j;
}
@Override
public int hashCode() {
return Objects.hash(i, j);
}
}
int INF = Integer.MAX_VALUE;
int lenx;
int leny;
public void wallsAndGates(int[][] rooms) {
leny = rooms.length;
lenx = rooms[0].length;
for (int i = 0; i < leny; i++) {
for (int j = 0; j < lenx; j++) {
if (rooms[i][j] == 0) {
BFS(rooms, i, j);
}
}
}
}
public void BFS(int[][] rooms, int x, int y) {
Queue<location> queue = new LinkedList<>();
Set<location> set = new HashSet<>();
int step = -1;
location l0 = new location(x, y);
queue.add(l0);
while (!queue.isEmpty()) {
step++;
int size = queue.size();
for (int k = 0; k < size; k++) {
location a = queue.element();
if (a != l0) {
if (rooms[a.i][a.j] == 0 || rooms[a.i][a.j] == -1) {
queue.remove();
continue;
} else {
rooms[a.i][a.j] = Math.min(step, rooms[a.i][a.j]);
}
}
// 此处可以化简为rooms[i][j] > 0
if ((a.i - 1 > -1) && (rooms[a.i - 1][a.j] != -1) && (rooms[a.i - 1][a.j] != 0)) {
location l = new location(a.i - 1, a.j);
if (!set.contains(l)) {
queue.add(l);
set.add(l);
}
}
if ((a.j - 1 > -1) && (rooms[a.i][a.j - 1] != -1) && (rooms[a.i][a.j - 1] != 0)) {
location l = new location(a.i, a.j - 1);
if (!set.contains(l)) {
queue.add(l);
set.add(l);
}
}
if ((a.i + 1 < leny) && (rooms[a.i + 1][a.j] != -1) && (rooms[a.i + 1][a.j] != 0)) {
location l = new location(a.i + 1, a.j);
if (!set.contains(l)) {
queue.add(l);
set.add(l);
}
}
if ((a.j + 1 < lenx) && (rooms[a.i][a.j + 1] != -1) && rooms[a.i][a.j + 1] != 0) {
location l = new location(a.i, a.j + 1);
if (!set.contains(l)) {
queue.add(l);
set.add(l);
}
}
queue.remove();
}
}
}
}
题目:【目标和】 https://leetcode-cn.com/problems/target-sum/
解析: 此题是经典的 DFS 算法题,思路较为简单,这里就不作剖析了
class Solution {
private int a = 0;
public int findTargetSumWays(int[] nums, int target) {
DFS(nums, 0, 0, target);
return a;
}
public void DFS(int[] nums, int i, int sum, int target) {
if(i==nums.length) {
if(sum==target){
a++;
}
return;
}
DFS(nums, i + 1, sum + nums[i], target);
DFS(nums, i + 1, sum - nums[i], target);
}
}
本文图片来自:https://leetcode-cn.com/