图论
大部分学习内容来自代码随想录,这里对其进行整理。
图的理论基础
图的存储
图的存储分为两种,邻接矩阵和邻接表
- 邻接矩阵构造图
邻接矩阵使用二维数组表示图结构。邻接矩阵是从节点的角度来表示图。有多少节点就申请多大的二维数组
/*------------邻接矩阵----------------*/
//假设有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,