1.基础知识
对于图来说比较重要的就是图的深度遍历和图的广度遍历,下面是这两种遍历的基本思想
1.深度遍历:
这里可以用深度搜索遍历的原因是,深度优先搜索使用的数据结构是栈。我们先将出度为 0 的节点放入,而出度为 0 的节点是比较高级的课程,基础的课程后入栈那么也就先出
S1:记录节点之间的映射关系
key:父节点;value:该父节点指向的相邻节点
比如:在课程中基础课程指向高级课程。有一个数据结构存储节点之间的关系,key :课程 ; value :该课程对应的高级课程
其中图中的「白色」「黄色」「绿色」节点分别表示「未搜索」「搜索中」「已完成」的状态

S2:逐个遍历节点
在遍历节点时不要按照什么规则,直接从 index 为 0 的节点开始遍历即可。从 A 开始遍历,一直遍历到 G 没有出度节点为止,就到达了最底层

S2:入栈+回溯
遍历到 G 了,G 是没有出度节点的,所以将 G 入栈,因为这是一个递归过程,G 是最深层的节点后再去回溯 F ,然后同理将 E 入栈。然后 F 入栈

S3:最后再返回到 A 节点

总结:
深度遍历的核心思想以一个节点为出发点,逐个遍历和该点相连的元素
2.广度遍历
S1:计算每个节点的入度和出度
入度为 0 的节点说明是基础节点,基础节点应该先被遍历然后先被输出,所以这里使用队列作为数据结构

S2:首先遍历入度为 0 的节点
B 节点的入度是 0 所以先将 B 放入队列中

S3:一个节点遍历完成后,更改其相邻节点的入度

S4:继续遍历队列,直到队列为空
继续遍历队列,每处理队列中的一个节点首先要将其进行输出,然后将其相邻接点的入度 -1

其实这里还有一个映射关系,就是 v 的值和 index 是映射的
2.1_207课程表
2.1.1 算法描述
首先这个题因为有相互的指向,所以是一个有向图
其次因为课程之间相互依靠,但是总有一个起始课程,所以这是一个无环图。一个有向图并且无环最终得到一个拓扑图。本题的主要目的是判断该图是否存在环
对于深度优先判断环:为每个节点增加判断的状态,如果一个节点重复是“已判断”状态代表出现了环
对于广度优先搜索判断环:对加入到栈中的元素进行判断,并记录判断元素的个数,如果一个元素判断过多次则最后判断个数 > 节点个数,说明存在环
2.1.2 代码实现
1.深度优先
class Solution {
public:
vector<vector<int>> edges; // key:课程 ;value 该课程对应的高级课程
vector<int> visited; // 表示每个节点的状态 0 未搜索 1 搜索中 2 搜索完成(出度为 0)
bool valid = true; // 判断图中是否有环
void dfs(int u){
visited[u] = 1; // 将状态置为 1
for(int v:edges[u]){ // 遍历 node 的出度节点
if(visited[v]==0){ // 子节点是未搜索状态
dfs(v); // 继续向下搜索
if(!valid) return;
}
else if(visited[v]==1){ // 在向下遍历时遇到了环
valid = false;
return;
}
visited[u] = 2; // 其他情况判断完成
}
}
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
edges.resize(numCourses); // 初始化大小
visited.resize(numCourses);
// 记录课程之间的关系
for(auto& info : prerequisites){
edges[info[1]].push_back(info[0]);
}
for(int i =0;i<numCourses&&valid;i++){
if(visited[i]==0){ // 基础课程时执行
dfs(i);
}
}
return valid;
}
};
2.广度优先
class Solution {
public:
vector<vector<int>> edges;
vector<int> indeg; // 记录每个节点的入度情况
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
edges.resize(numCourses);
indeg.resize(numCourses);
for(auto& info:prerequisites){
edges[info[1]].push_back(info[0]); // 映射
++indeg[info[0]]; // 入度情况
}
queue<int> q;
for(int i =0;i<numCourses;i++){
if(indeg[i]==0) q.push(i); // 入度为 0
}
int visited = 0;
while(!q.empty()){ // 逐个出队列元素
++visited;
int u = q.front();
q.pop();
for(int v:edges[u]){ // 开始遍历这个节点
--indeg[v];
if(indeg[v]==0){ // 入度为 0 就将该节点放入
q.push(v);
}
}
}
return visited==numCourses; // 每个节点只被遍历到一次代表无环
}
};
2.1.3 时空复杂度
时间复杂度:
图的深度和广度搜素的时间复杂度都是 O(v+e),也就是节点个数+变得个数
空间复杂度:
深度和广度都是 O(v+e) 因为他们要存储成邻接表的映射关系,与此同时还需要栈或者队列进行辅助,他们的空间复杂度是 O(v) ,所以最后的空间复杂度是 O(v+e)
2.2_200 岛屿数量
2.2.1 算法描述
深度优先搜索用递归实现。需要扫描整个网格。广度优先搜索需要借助队列
1.BFS
(1)递归顺序
该单元四周的单元先遍历哪个都一样。这里使用“先序遍历”,先对遍历过的该节点进行单元格置零操作再遍历该单元格的孩子节点
(2)递归的参数和返回值
这里使用 num_island 这个全局变量记录岛屿个数。递归函数中没有参数和返回值。因为在 dfs 的过程中岛屿的个数是不变的,所以在递归的过程中没有信息传递过程
(3)终止条件
当 if 中的条件没满足程序自然就不执行了。不一定需要 return ,相同的套路可见链表排序一题
(4)单层递归逻辑
本题只需要将遍历过的单元进行置 0 即可
1.深度搜索
使用 for 循环先从 [0,0] 的位置出发,将 [0,0] 位置上所有的“孩子”,上下左右四个点分别使用
数据结构 | 深度 | 广度 | 相关题目 |
---|---|---|---|
二叉树 | 先序:1.定义一个栈2.根节点进栈 3.根节点出栈(处理根)4.右左孩子入栈 中序:1.根节点不入栈2.while 循环分两步一直入栈左孩子 or 没有左孩子了->出栈一个元素(最底层元素)->处理该元素->右孩子不入栈!!->继续遍历该右孩子的左孩子 3.后续:1.定义一个栈,根节点入栈2.根节点出栈开始处理2.右左孩子入栈3.将 res 逆序 | 层次遍历: 1.定义一个队列,根节点入队 2.处理 root ,记录当前 size 3.使用 for 循环遍历队中剩余的节点,将那些节点的左右孩子入队 | 先:144 |
图 | 1.将题目中的数据进行映射,使用一个 vidsited 数组记录每个节点的访问情况 2.从头开始遍历 edges,cur 3.再以 cur 为起点递归遍历其 “孩子”,并对其孩子的遍历状态进行记录 | 1.将题目中的数据形成图的映射,并得到每个节点的入度 2.创建一个 queue 存放入度为 0 的元素 3.遍历 queue ,将队首元素弹出,遍历其”孩子” 4.孩子的处理:入度-1,如果入度为 0 则放入队列 | 207 |
二维数组 | 1.双重 for 循环以 [0,0] 为起始点,逐个遍历每个节点 2.在 dfs 中先对该节点进行处理 3.在 dfs 中递归遍历 cur 节点上下左右四个节点 | 1.for 循环以 [0,0] 为起始节点,逐个遍历每个节点 2.创建一个 queue ,将 cur 元素添加到 que 中,que 保存 cur 节点的坐标信息 3.队首元素出队,以 cur 为出发点遍历其上下左右四个元素。并将cur元素周围的单元格其入队,周围的单元格再把它周围的单元格入队 | 200 |
时间复杂度:O(MN) 就是二维数组的大小
2.2.2 代码实现
1.DFS
class Solution {
public:
int nums_lands = 0;
void dfs(vector<vector<char>>& grid,int i,int j){
// cout<<"i:"<<i<<"j:"<<j<<endl;
int nr = grid.size();
int nc = grid[0].size();
// 将其上下左右四个点设为 0
grid[i][j] = '0';
if(i>0&&grid[i-1][j]=='1') dfs(grid,i-1,j);
if(i<nr&&grid[i+1][j]=='1') dfs(grid,i+1,j);
if(j>0&&grid[i][j-1]=='1') dfs(grid,i,j-1);
if(j<nc&&grid[i][j+1]=='1') dfs(grid,i,j+1);
}
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
int nc = grid[0].size();
// DFS
for(int i = 0;i<nr;i++){
for(int j = 0;j<nc;j++){
if(grid[i][j]=='1'){
nums_lands++;
dfs(grid,i,j);
}
}
}
return nums_lands;
}
};
2.BFS
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if(!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for(int r = 0;r<nr;r++){
for(int c = 0;c<nc;c++){
if(grid[r][c]=='1'){
num_islands++;
grid[r][c] = '0'; // 已经访问过
queue<pair<int,int>> neighbors; // 使用队列进行广度优先搜索,保存其行列的值
neighbors.push({r,c}); // 将当前元素加入队列
// 循环迭代直到队列为空
while(!neighbors.empty()){
auto rc = neighbors.front();
neighbors.pop();
int row = rc.first,col = rc.second;
// 遍历上下左右四格,若值为1则加入队列
if(row-1>=0&&grid[row-1][col]=='1'){
neighbors.push({row-1,col});
grid[row-1][col] = '0';
}
if(row+1<nr&&grid[row+1][col]=='1'){
neighbors.push({row+1,col});
grid[row+1][col] = '0';
}
if(col-1>=0&&grid[row][col-1]=='1'){
neighbors.push({row,col-1});
grid[row][col-1] = '0';
}
if(col+1<nc&&grid[row][col+1]=='1'){
neighbors.push({row,col+1});
grid[row][col+1] = '0';
}
}
}
}
}
return num_islands;
}
};
2.2.3 空间复杂度:
BFS:O(MN),整个二维数组全是陆地
DFS:O(min(M,N)) , 最坏情况全部为陆地
总结:
深度遍历:
1.创建一个栈,将第一个元素放入
2.将栈顶元素 cur 出栈,处理 cur 元素,递归的遍历 cur 元素的孩子节点,并将其入栈
广度遍历:
1.创建一个队列,按照顺序将 cur 元素入队列
2.将 cur 弹出,将 cur 的孩子单元格入队