图论理论基础
图的基本概念:二维坐标中,两点可以连成线,多个点连成的线就构成了图。当然图也可以就一个节点,甚至没有节点(空图)。
图的种类:有向图(图中边是有方向的)和无向图(图中边没有方向)。
加权有向图(图中边是有权值的),加权无向图同理。
度:无向图中有几条边连接该节点,该节点就有几度。
在有向图中每个节点有出度和入度。出度:从该节点出发的边的个数。入度:指向该节点边的个数。
连通性:在图中表示节点的连通情况,我们称之为连通性。
连通图:在无向图中,任何两个节点都是可以到达的,我们称之为连通图 。如果有节点不能到达其他节点,则为非连通图。
强连通图:在有向图中,任何两个节点是可以相互到达的,我们称之为 强连通图。
连通分量:在无向图中的极大连通子图称之为该图的一个连通分量。
强连通分量:在有向图中极大强连通子图称之为该图的强连通分量。
图的构造:一般使用邻接表、邻接矩阵 或者用类来表示。主要是 朴素存储、邻接表和邻接矩阵。
邻接矩阵:邻接矩阵 使用 二维数组来表示图结构。 邻接矩阵是从节点的角度来表示图,有多少节点就申请多大的二维数组。
例如: grid[2][5] = 6,表示 节点 2 连接 节点5 为有向图,节点2 指向 节点5,边的权值为6。
如果想表示无向图,即:grid[2][5] = 6,grid[5][2] = 6,表示节点2 与 节点5 相互连通,权值为6。
邻接表:邻接表 使用 数组 + 链表的方式来表示。 邻接表是从边的数量来表示图,有多少边 才会申请对应大小的链表。
图的遍历方式:深度优先搜索(dfs)、广度优先搜索(bfs)。
深度优先搜索理论基础
深搜与广搜的区别:
- dfs深搜是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯)。
- bfs广搜是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是广度,四面八方的搜索过程。
深搜三部曲
1.确认递归函数、参数。
2.确定终止条件。
3.处理目前搜索节点出发的路径。
vector<vector<int>> result; // 保存符合条件的所有路径
vector<int> path; // 起点到终点的路径
void dfs(图,目前搜索的节点){
if (终止条件) {
存放结果;
return;
}
for (选择:本节点所连接的其他节点) {
处理节点;
dfs(图,选择的节点); // 递归
回溯,撤销处理结果
}
}
所有可达路径 卡码网 98
写法一:邻接矩阵写法
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> res;
vector<int> path;
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++){
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(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;
}
}
写法二:邻接表写法
#include <iostream>
#include <vector>
#include <list>
using namespace std;
vector<vector<int>> res;
vector<int> path;
void dfs(const vector<list<int>> &graph,int x,int n){
if(x==n){
res.push_back(path);
return;
}
for(int i:graph[x]){
path.push_back(i);
dfs(graph,i,n);
path.pop_back();
}
}
int main()
{
int N,M,s,t;
cin>>N>>M;
vector<list<int>> graph(N+1);
while(M--){
cin>>s>>t;
graph[s].push_back(t);
}
path.push_back(1);
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;
}
}
总结
1.使用邻接矩阵和邻接表存放图。
2.ACM格式输入输出。
广度优先搜索理论基础
广搜(bfs)是一圈一圈的搜索过程,和深搜(dfs)是一条路跑到黑然后再回溯。
广搜(bfs)使用场景:解决两个点之间的最短路径问题。
也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行。
代码框架:
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; // 只要加入队列立刻标记,避免重复访问
}
}
}
}