目录
概述
对元素的处理有时候需要限定处理的顺序,这时候传统的数组和链表就无法满足了。
两种不同的处理顺序:先入先出和后入先出
两个相应的线性数据结构:队列和栈
队列:先入先出的数据结构
简介
- 在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。
- 队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。
实现
- 动态数组+起始索引标记,实现简单,当开始标记移动时,会造成空间的浪费
- 使用循环队列的方式实现
循环队列
使用固定大小的数组和两个指针来指示起始位置和结束位置,重用之前被浪费的存储
基本操作
queue q;
入队:q.push(3);
出队:q.pop();
获取第一个元素:q.front();
获取最后一个元素:q.back();
获取尺寸:q.size();
是否为空:q.empty();
广度优先搜索BFS
广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径,另一个是遍历。
-
结点的处理顺序
第一轮:根结点
第二轮:根结点旁边的结点
第三轮:根结点两步的结点
…
与树的层序遍历类似,越是接近根结点的结点将越早地遍历。
如果在第 k 轮中将结点 X 添加到队列中,则根结点与 X 之间的最短路径的长度恰好是 k。也就是说,第一次找到目标结点时,你已经处于最短路径中。 -
队列的入队和出队顺序
1.首先将根结点排入队列。在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。值得注意的是,新添加的节点不会立即遍历,而是在下一轮中处理。
2.结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)。这就是我们在 BFS 中使用队列的原因。 -
在特定问题中执行 BFS 之前确定结点和边缘非常重要。通常,结点将是实际结点或是状态,而边缘将是实际边缘或可能的转换。
模板1
/* Return the length of the shortest path between root and target node. */
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
add next to queue;
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
- 在每一轮中,队列中的结点是等待处理的结点。
- 在每个更外一层的 while 循环之后,我们距离根结点更远一步。变量 step 指示从根结点到我们正在访问的当前结点的距离。
模板2
有时,确保我们永远不会访问一个结点两次很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。
/* Return the length of the shortest path between root and target node.*/
int BFS(Node root, Node target) {
Queue<Node> queue; // store all nodes which are waiting to be processed
Set<Node> used; // store all the used nodes
int step = 0; // number of steps neeeded from root to current node
// initialize
add root to queue;
add root to used;
// BFS
while (queue is not empty) {
step = step + 1;
// iterate the nodes which are already in the queue
int size = queue.size();
for (int i = 0; i < size; ++i) {
Node cur = the first node in queue;
return step if cur is target;
for (Node next : the neighbors of cur) {
if (next is not in used) {
add next to queue;
add next to used;
}
}
remove the first node from queue;
}
}
return -1; // there is no path from root to target
}
有两种情况你不需要使用哈希集:
- 你完全确定没有循环,例如,在树遍历中;
- 你确实希望多次将结点添加到队列中。
练习
1. 岛屿数量BFS
题目链接: 200. 岛屿数量
解题思路:
- BFS策略,访问过的元素记得标记防止无限相互回溯。
代码:
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int row = grid.size();
int col = grid[0].size();
vector<int> map(col*row, 0);
int numscount = 0;
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(grid[i][j] == '1' && map[i*col+j] == 0){
numscount++;
islandBFS(i, j, map, grid, numscount);
}
}
}
return numscount;
}
void islandBFS(int geti, int getj, vector<int>& map, vector<vector<char>>& grid, int num){
int row = grid.size();
int col = grid[0].size();
queue<int> mapindex;
map[geti*col+getj] = num;
mapindex.push(geti*col+getj);
while(!mapindex.empty()){
int i = mapindex.front() / col;
int j = mapindex.front() % col;
mapindex.pop();
if(i-1 >= 0 && grid[i-1][j] == '1' && map[(i-1)*col+j] == 0){
mapindex.push((i-1)*col+j);
map[(i-1)*col+j] = num;
}
if(i+1 < row && grid[i+1][j] == '1' && map[(i+1)*col+j] == 0){
mapindex.push((i+1)*col+j);
map[(i+1)*col+j] = num;
}
if(j-1 >= 0 && grid[i][j-1] == '1' && map[i*col+j-1] == 0){
mapindex.push(i*col+j-1);
map[i*col+j-1] = num;
}
if(j+1 < col && grid[i][j+1] == '1' && map[i*col+j+1] == 0){
mapindex.push(i*col+j+1);
map[i*col+j+1] = num;
}
}
}
};
2. 打开转盘锁
题目链接: 752. 打开转盘锁
解题思路:
- 广度优先遍历的总体思想
- 具体实施很多细节可以学习,判断一个字符串是否在一个字符串数组中,可以转化为unordered_set进行处理,提供count(string s)函数可以判断是否存在,另外在构建时利用迭代器可以一步复制原来的字符串数组到set当中。
- 字符char和整形int的相互转换,int到char可以 char c = i + ‘0’, char到int可以 int i = c - ‘0’,需要了解char的编码规则
代码:
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
unordered_set<string> deadendset;
deadendset.insert(deadends.begin(), deadends.end());
if(deadendset.count("0000")) return -1;
queue<string> sq;
sq.push("0000");
int res = 0;
while(!sq.empty()){
int size = sq.size(); //帮助我们统计离出发点的层数,这里的size控制的for循环必不可少
for(int t = 0; t < size; ++t){
string temp = sq.front();
sq.pop();
if(temp == target) return res;
for(int i = 0; i < 4; ++i){
for(int j = -1; j < 2; j += 2){
string newtemp = temp;
int vali = (temp[i]-'0' + j + 10) % 10; // +10保证减法操作也可以统一在这个式子中
newtemp[i] = vali + '0';
if(!deadendset.count(newtemp)){
sq.push(newtemp);
deadendset.insert(newtemp);
};
}
}
}
res