图论理论基础
完整内容可参考:代码随想录|图论基础
这里关于图的基本概念就不说了,主要记录一下关于如何表示图,主要有两种方式,一种是邻接矩阵,一种是邻接表。邻接矩阵 使用 二维数组来表示图结构。 邻接矩阵是从节点的角度来表示图,有多少节点就申请多大的二维数组。
邻接矩阵的优点:
- 表达方式简单,易于理解
- 检查任意两个顶点间是否存在边的操作非常快
- 适合稠密图,在边数接近顶点数平方的图中,邻接矩阵是一种空间效率较高的表示方法。
缺点:
- 遇到稀疏图,会导致申请过大的二维数组造成空间浪费 且遍历 边 的时候需要遍历整个n * n矩阵,造成时间浪费
例如: grid[2][5] = 6,表示 节点 2 连接 节点5 为有向图,节点2 指向 节点5,边的权值为6。
邻接表使用数组+链表的方式来表示。邻接表是从边的数量来表示图。有多少边才会申请对应大小的链表。
邻接表的优点:
- 对于稀疏图的存储,只需要存储边,空间利用率高
- 遍历节点连接情况相对容易
缺点:
- 检查任意两个节点间是否存在边,效率相对低,需要 O(V)时间,V表示某节点连接其他节点的数量。
- 实现相对复杂,不易理解。
深搜理论基础
完整内容可参考**:代码随想录|深度优先搜索理论基础**
dfs关键就两点:
- 搜索方向,是认准一个方向搜,直到碰壁之后再换方向
- 换方向是撤销原路径,改为节点链接的下一个路径,回溯的过程。
dfs的代码框架:
cpp
void dfs(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
跟回溯板块差不多,深搜也有三部曲:确认递归函数、确认终止条件、处理目前搜索节点出发的路径。
98.可达路径
思路:主要难点在判断终止条件和递归思路,递归函数定义为:
void dfs (const vector<vector<int>>& graph, int x, int n)当x==n时,说明已经找到了一条路径,此时需要将path加入到结果数组。
每层的递归思路就是先判断当前节点是不是可连通的,如果可以就说明可以从这个节点接着往下搜索,用dfs函数递归继续搜索下去,最后记得回溯回去。这题是acm模式,所以是有打印数组的要求的,这里要注意最后一个数的后面是没有空格的,在写代码的时候需要特殊处理最后一个结果数组的元素。
我的代码:
cpp
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result;
vector<int> path;
void dfs(const vector<vector<int>> &graph,int x,int n) {
if(x==n) {
result.push_back(path);
return ;
}
for(int i=1;i<=n;i++) {
if(graph[x][i]==1) {
path.push_back(i);
dfs(graph,i,n);
path.pop_back();
}
}
}
int main() {
int n,m,s,t;
cin>>n>>m;
vector<vector<int>> graph(n+1,vector<int>(n+1,0));
while(m--) {
cin>>s>>t;
graph[s][t]=1;
}
path.push_back(1);
dfs(graph,1,n);
if(result.size()==0) cout<<-1<<endl;
for(auto pa:result) {
for(int i=0;i<pa.size()-1;i++) {
cout<<pa[i]<<" ";
}
cout<<pa[pa.size()-1]<<endl;
}
return 0;
}
广度优先搜索理论基础
完整版可参考:代码随想录|广度优先搜索理论基础
广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。当然,也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,**这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行。**对于广度优先搜索,用队列,还是用栈,甚至用数组,都是可以的。用队列的话,就是保证每一圈都是一个方向去转,例如统一顺时针或者逆时针。
因为队列是先进先出,加入元素和弹出元素的顺序是没有改变的。如果用栈的话,就是第一圈顺时针遍历,第二圈逆时针遍历,第三圈有顺时针遍历。因为栈是先进后出,加入元素和弹出元素的顺序改变了。而在这里顺序是无关紧要的。
下面给出广搜代码模板:
cpp
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] = 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]) { // 如果节点没被访问过
que.push({nextx, nexty}); // 队列添加该节点为下一轮要遍历的节点
visited[nextx][nexty] = true; // 只要加入队列立刻标记,避免重复访问
}
}
}
} //(by 代码随想录)
今日总结
今天的图论基础在学过回溯下比较好接受,不过图论也是一个大板块,好好理解今天的理论内容才能在后面做难题更的心顺手,特别要注意邻接矩阵和邻接表两种表示图的方式,链表的方式相对来说不好理解一点,但是看了示例代码其实感觉在写法上貌似没有很大差别。继续加油吧,再坚持十几天就结束了!