一、队列
1.队列的定义
队列是FIFO(先入先出) 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue),你只能移除第一个元素。
2.队列的实现
用动态数组实现的队列
**class MyQueue {
private:
vector<int> data;
int p_start; //指向队首
public:
MyQueue() {p_start = 0;}
bool enQueue(int x) {
data.push_back(x);
return true;
}
bool deQueue() {
if (isEmpty()) {
return false;
}
p_start++;
return true;
};
int Front() {
return data[p_start];
};
bool isEmpty() {
return p_start >= data.size();
}
};
**
这种实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。更有效的方法是使用循环队列。 可以使用固定大小的数组和两个指针来指示起始位置和结束位置。 目的是重用我们之前提到的被浪费的存储。
class MyCircularQueue {
private:
vector<int> data;
int head; //队首
int tail; //队尾
int size; //栈容量
//注意:队首和队尾都指向有效位置,初始化和空队列时,head=tail=-1,使它们不指向无效位置;
public:
MyCircularQueue(int k) {//初始化,栈空时head=tail=-1,不指向任何内容
data.resize(k); //data[-1]是不存在的
head = -1;
tail = -1;
size = k;
}
bool enQueue(int value) {
if (isFull()) {
return false;
}
if (isEmpty()) { //队列为空时插入元素,head=0,tail=0;
head = 0;
}
tail = (tail + 1) % size;
data[tail] = value;
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
bool deQueue() {
if (isEmpty()) {
return false;
}
if (head == tail) { //队列中只有一个元素,删除后队列为空,所以head=tail=-1
head = -1;
tail = -1;
return true;
}
head = (head + 1) % size;
return true;
}
int Front() {
if (isEmpty()) {
return -1;
}
return data[head];
}
int Rear() {
if (isEmpty()) {
return -1;
}
return data[tail];
}
bool isEmpty() {
return head == -1;
}
bool isFull() {
return ((tail + 1) % size) == head;
}
};
3.内置队列的使用
大多数流行语言都提供内置的队列库,,可以直接使用。
queue q; //初始化
q.push(1); //入队
q.empty(); //队空
q.pop(); //出队
q.front(); //队首
q.back(); //队尾
q.size(); //队大小
5.解决问题
leetcode 346.数据流中的移动平均值
leetcode 621.任务调度器
二、BFS
广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。与树的层序遍历类似,越是接近根结点的结点将越早地遍历。
首先将根结点排入队列。然后在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。值得注意的是,新添加的节点不会立即遍历,而是在下一轮中处理。结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)。这就是我们在 BFS 中使用队列的原因。
使用 BFS 的两个主要方案:遍历或找出最短路径。通常,这发生在树或图中,也可以用于更抽象的场景中。
//返回到目标的最短路径长度
int BFS(Node root, Node target) {
Queue<Node> queue; // 保存所有等待访问的结点
int step = 0; //距离开始的距离
add root to queue;
// BFS
while (queue is not empty) {
step = step + 1;
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; //没有查找到
}
1.如代码所示,在每一轮中,队列中的结点是等待处理的结点。
2.在每个更外一层的 while 循环之后,我们距离根结点更远一步。变量 step 指示从根结点到我们正在访问的当前结点的距离。
有时,确保我们永远不会访问一个结点两次很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。这是修改后的伪代码:
//返回到目标的最短路径长度
int BFS(Node root, Node target) {
Queue<Node> queue; //保存所有等待访问的结点
Set<Node> used; //保存访问过的结点
int step = 0; //距离开始的距离
add root to queue;
add root to used;
// BFS
while (queue is not empty) {
step = step + 1;
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; //没有结果
}
有两种情况不需要使用哈希集:
1.完全确定没有循环,例如,在树遍历中;
2.确实希望多次将结点添加到队列中。
图 二维数组
多源bfs:首先需要把多个源点都入队.
为了防止某个节点多次入队必须标记是否访问过,否则会超时。可以单独定义个访问标记数组在其入队之前就将其设置成已访问,或者直接在原数组中修改。
BFS求的是最短路径,但是求最远距离时不矛盾:
1.求最短路径时,只要找到目标和,返回。
2.求最长路径时,所有目标值都访问完,返回。
leetcode 130.被围绕的区域
leetcode 200.岛屿数量
leetcode 542.01 矩阵
leetcode 1162.地图分析
leetcode 79. 单词搜索
leetcode 994.腐烂的橘子
树 二叉树
单源bfs:首先把 root 节点入队
Tree 是有向的因此不需要标识是否访问过.
leetcode 102.二叉树的层次遍历
leetcode 101.对称二叉树
其它
leetcode 207.课程表
leetcode 279.完全平方数
leetcode 301.删除无效的括号
leetcode 752.打开转盘锁