图论
大部分学习内容来自代码随想录,这里对其进行整理。
图的理论基础
图的存储
图的存储分为两种,邻接矩阵和邻接表
- 邻接矩阵构造图
邻接矩阵使用二维数组表示图结构。邻接矩阵是从节点的角度来表示图。有多少节点就申请多大的二维数组
/*------------邻接矩阵----------------*/
//假设有10个节点,为了和下标对齐,申请n+1 * n+1这么大的二维数组
int n = 10;
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
//输入m个边,构造方式如下
int m = 5;
int s,t;
while(m--)
{
cin >> s >> t;
//使用邻接矩阵,1表示节点s指向节点t
graph[s][t] = 1;
}
- 邻接表构造图
邻接表用数组加链表的方式来表示。邻接表是从边的数量来表示图,有多少边才会申请对应大小的链表。
该图表示: - 节点1指向节点3,节点5
- 节点2指向节点4,节点3
- 节点3指向节点4
- 节点4指向节点1
- 节点5不指向任何节点
/*-----------邻接表-------------*/
//n个节点,申请n+1维数组
int n = 10;
vector<list<int>> graph(n + 1); //元素为链表的数组
//输入m个边,构造方式如下
int m = 5;
int s,t;
while(m--)
{
cin >> s >> t;
graph[s].push_back(t);//表示s->t是相连的
}
深度优先搜索
广度优先搜索
广搜是一圈一圈搜索的过程
广搜的适用场景
广搜的搜索方式适合于解决两个点之间的最短路径问题。广搜是一圈一圈进行搜索,一旦遇到终点,之前走过的节点即最短路径。
岛屿问题用深搜和广搜都可以。
广搜的过程
代码框架
针对上面的四方格地图
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; //四个方向
//grid是地图,即一个二维数组
//visited表示访问过的节点,不要重复访问
//x,y表示开始搜索节点的下标
void bfs(vector<vector<char>> &grid, vector<vector<bool>>& visited, int x, int y)
{
queue<pair<int ,int>>que; //定义队列
que.push({x,y});//当前搜索节点加入队列
visited[x][y] = 1;//访问节点标记
while(!que.empty()) //遍历队列里的元素
{
pair<int, int> cur = que.front();//取出队首元素
que.pop();//队首元素出队
int curx = cur.first;
int cury = cur.second;//当前节点坐标
for (int i = 0; i < 4; i++) //遍历四个方向
{
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; //坐标越界,直接跳过
if (!visited[nextx][nexty]) //节点未访问过
{
que.push({nextx, nexty}); //加入队列
visited[nextx][nexty] = true; //加入队列后立即标记
}
}
}
}
力扣相关题目
所有可能的路径
题目链接
这里用ACM模式,分别用邻接矩阵和邻接表的形式实现
- 邻接矩阵
#include <iostream>
#include <vector>
using namespace std;
//全局变量记录结果
vector<vector<int>> res;
vector<int> path;
//gragh是邻接矩阵,x是当前遍历的节点,n是最终节点
void dfs(const vector<vector<int>>& graph, int x, int n)
{
//终止条件
if (x == n) //终止条件,当前遍历的节点是最终节点
{
res.push_back(path); //存储结果
return; //结束当前递归调用!!!
}
//回溯搜索的遍历过程
for (int i = 1; i <= n; i++) //遍历x链接的所有节点
{
if (graph[x][i] == 1) //找到x链接的节点
{
path.push_back(i);//将链接节点加入路径
dfs(graph, i, n);//进入下一层递归
path.pop_back(); //回溯,撤销本节点
}
}
}
int main()
{
int n, m, s, t;
cin >> n >> m;
//节点编号从1到n,申请n+1维数组
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
while(m--)
{
cin >> s >> t;
//使用邻接矩阵,表示无向图
graph[s][t] = 1;
}
path.push_back(1);//无论什么路径从0节点出发
dfs(graph, 1, n); //开始遍历
//输出结果
if (res.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : res)
{
for (int i = 0; i < pa.size() - 1; i++)
{
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
}
对于这个代码,一开始不理解return,认为找到一条满足的路径,函数不就return,直接结束了么。记住这里的return只是结束当前的递归调用,但循环不会结束。
- 邻接表
#include <iostream>
#include <vector>
#include <list>
using namespace std;
//全局变量记录结果
vector<vector<int>> res;
vector<int> path;
void dfs(vector<list<int>> &graph, int x, int n)
{
if (x == n)
{
res.push_back(path);
return;
}
for (int i : graph[x]) //遍历graph[x],将graph[x]中的值赋值给i
{
path.push_back(i); //加入x链接的节点
dfs(graph, i, n);//递归下一层
path.pop_back();//回溯,撤销当前节点
}
}
int main(void)
{
int n, m, s, t;
cin >> n >> m;
vector<list<int>> graph(n + 1);
while(m--)
{
cin >> s >> t;
//使用邻接表,表示s—t是相连的
graph[s].push_back(t);
}
path.push_back(1);//无论什么路径都是从0几点出发
dfs(graph, 1, n); //开始遍历
//输出结果
if (res.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : res)
{
for (int i = 0; i < pa.size() - 1; i++)
{
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
return 0;
}
岛屿数量
深搜版本
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
void dfs(const vector<vector<int>> &grid, vector<vector<bool>>& visited, int x, int y)
{
for (int i = 0; i < 4; i++) //遍历当前节点周围的节点
{
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())//超出边界,跳过
continue;
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)//节点未访问过且是陆地
{
visited[nextx][nexty] = true; //标记为访问过的陆地
dfs(grid, visited, nextx, nexty); //继续访问
}
}
}
int main()
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));//初始化岛屿
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));//初始化访问数组
int res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1) //节点未访问过且是陆地
{
res++;
dfs(grid, visited, i, j); //将当前陆地链接的陆地都标记
}
}
}
cout << res << endl;
}
这里的终止条件隐含在调用dfs地方,遇到不合法方向,不会调用dfs。
广搜版本
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
void bfs(const vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
queue<pair<int, int>> que;
que.push({x,y});
visited[x][y] = true; //只要加入队列,立刻标记为已经访问的节点
while (!que.empty())
{
pair<int, int>cur = que.front(); //取出队首元素
que.pop();//队首元素出队
int curx = cur.first;
int cury = cur.second;
for (int i = 0; i < 4; i++) //遍历周围节点
{
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
continue;
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
que.push({nextx, nexty});
visited[nextx][nexty] = true;
}
}
}
}
int main()
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for(int i = 0; i < n; i++)
{
for (int j = 0; j < m ;j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
int res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
res++;
bfs(grid, visited, i, j);//将与其链接的陆地都标记上 true
}
}
}
cout << res << endl;
}
这里和二叉树的层遍历很像,都是队列实现
岛屿的最大面积
广搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int bfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
int res= 1; //!!! 需要初始化为1,起始节点也应该算作一个部分,bfs处理当前节点
queue<pair<int, int>> que;
que.push({x, y}); //当前遍历节点放入队列
visited[x][y] = true; //加入队列后立即标记
while (!que.empty())
{
pair<int, int> cur = que.front();
que.pop();
int curx = cur.first;
int cury = cur.second;
for (int i = 0; i < 4; i++)
{
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
res++;
que.push({nextx, nexty});
visited[nextx][nexty] = true;
}
}
}
return res;
}
int main()
{
int res = 0;
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
int area = bfs(grid, visited, i, j);
res = max(res, area);
}
}
}
cout << res << endl;
return 0;
}
bfs里处理当前节点,因此res初始化为1!!!
深搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int count; //定义全局变量,记录当前遍历节点的岛屿面积
//dfs处理当前节点
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
visited[x][y] = true;
count++;
for (int i = 0; i < 4; i++)
{
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
dfs(grid, visited, nextx, nexty); //递归搜索下一个节点
}
}
}
int main(void)
{
int res = 0;
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
count = 0;
dfs(grid, visited, i, j);
res = max(res, count);
}
}
}
cout << res << endl;
return 0;
}
dfs也处理当前节点,因此一开始就count++!!!
孤岛的总面积
深搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int count;
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
visited[x][y] = true;
//grid[x][y] = 0; 不需要修改网格值,因为遍历边缘岛屿时,会将遍历的节点标记
count++;
for(int i = 0; i < 4; i++)
{
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
dfs(grid, visited, nextx, nexty);
}
}
}
int main(void)
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
//靠近边界的岛屿的土地变成水
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (i == 0 || j == 0 || i == grid.size() - 1 || j == grid[0].size() - 1)
{
if (!visited[i][j] && grid[i][j] == 1)
{
dfs(grid, visited, i, j);
}
}
}
}
//找到孤岛和计算孤岛的总面积
int res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
count = 0;
dfs(grid, visited, i, j);
res += count;
}
}
}
cout << res << endl;
return 0;
}
广搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int count;
void bfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
queue<pair<int, int>> que;
que.push({x,y});
visited[x][y] = true;
count++;
while(!que.empty())
{
pair<int, int> cur = que.front();
que.pop();
int curx = cur.first;
int cury = cur.second;
for(int i = 0; i < 4; i++)
{
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
que.push({nextx, nexty});
visited[nextx][nexty] = true;
count++;
}
}
}
}
int main(void)
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for(int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (i == 0 || j == 0 || i == grid.size() - 1 || j == grid[0].size() - 1)
{
if (!visited[i][j] && grid[i][j] == 1)
{
bfs(grid, visited, i, j);
}
}
}
}
int res = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
count = 0;
bfs(grid, visited, i, j);
res += count;
}
}
}
cout << res <<endl;
return 0;
}
沉没孤岛
深搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
visited[x][y] = true;
for (int i = 0; i < 4; i++)
{
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
dfs(grid, visited, nextx, nexty);
}
}
}
int main(void)
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m ,0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (i == 0 || j == 0 || i == n - 1 || j == m - 1)
{
if(!visited[i][j] && grid[i][j] == 1)
{
dfs(grid, visited, i, j);
}
}
}
}
for (int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
grid[i][j] = 0;
}
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cout << grid[i][j];
}
cout << endl;
}
return 0;
}
广搜版
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
void bfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
queue<pair<int, int>> que;
que.push({x, y});
visited[x][y] = true;
while(!que.empty())
{
pair<int, int> cur = que.front();
que.pop();
int curx = cur.first;
int cury = cur.second;
for (int i = 0; i < 4; i++)
{
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1];
if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size())
{
continue;
}
if (!visited[nextx][nexty] && grid[nextx][nexty] == 1)
{
que.push({nextx, nexty});
visited[nextx][nexty] = true;
}
}
}
}
int main(void)
{
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m ,0));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> visited(n, vector<bool>(m, false));
for (int i = 0; i < n; i++)
{
for ( int j = 0; j < m; j++)
{
if (i == 0 || j == 0 || i == n - 1 || j == m - 1)
{
if (!visited[i][j] && grid[i][j] == 1)
{
bfs(grid, visited, i, j);
}
}
}
}
for (int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
if (!visited[i][j] && grid[i][j] == 1)
{
grid[i][j] = 0;
}
}
}
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
cout << grid[i][j];
}
cout << endl;
}
return 0;
}
水流问题
题目链接
用深搜解决,逆向思考!!!
#include <iostream>
#include <vector>
using namespace std;
int n,m;
int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1};
//逆向思考,从边界开始遍历
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y)
{
if (visited[x][y])
{
return;
}
visited[x][y] = true;
for(int i = 0; i < 4; i++)
{
int nextx = x + dir[i][0];
int nexty = y + dir[i][1];
if (nextx < 0 || nextx >= n || nexty < 0 || nexty >= m)
{
continue;
}
if (grid[x][y] > grid[nextx][nexty]) //向着高处遍历
{
continue;
}
dfs(grid, visited, nextx, nexty);
}
return;
}
int main(void)
{
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m, 0));
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
cin >> grid[i][j];
}
}
vector<vector<bool>> first_border(n, vector<bool>(m, false));
vector<vector<bool>> second_border(n, vector<bool>(m, false));
//遍历第一边界的左边界和第二边界的右边界
for ( int i = 0; i < n; i++)
{
dfs(grid, first_border, i, 0); //遍历左边界
dfs(grid, second_border, i, m -1);//遍历右边界
}
//遍历第一边界的上边界和第二边界的下边界
for(int j = 0; j < m; j++)
{
dfs(grid, first_border, 0, j); //遍历上边界
dfs(grid, second_border, n - 1, j);//遍历下边界
}
for (int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
if(first_border[i][j] && second_border[i][j])
{
cout << i << " " << j << endl;
}
}
}
return 0;
}
这一题没有想到逆向思考来做,从两个边界向内的高处遍历,只要坐标被两个边界都遍历过,即符合题意