图论总结: 图论学习部分可以参考邓俊辉老师的《数据结构》。邓老师的图部分适用范围更广,在拓扑排序,优先级遍历,深度遍历,广度遍历的深度相对来说讲得更深,引入了时间变量,并将各连同点之间连线进行了标注。
1.图的定义 图主要指相互联系的一组字符集,其中又可以依据相互联系划分为有向图,无向图,连通图等。存储方式分为邻接矩阵,邻接表等方式。其中邻接矩阵的空间复杂度较高,但是便于理解和快速访问,适合稠密图,邻接表空间复杂度较低,方便存储,适合稀疏图。
2.图的遍历方式 图的遍历方式主要分为两种: 深度优先搜索和广度优先搜索。
要遍历图,首先时要读入图,按照不同的存储方式进行图的读入: 邻接矩阵模式:
vector<vector<int>> graphin(){
int n,m,s,t;
cout<<"输入结点数量"<<endl;
cin>>n;//输入结点数量
cout<<"输入边的数量"<<endl;
cin>>m;//输入边的数量
cout<<"输入联通的各结点"<<endl;
vector<vector<int>> graph(n+1,vector<int>(n+1,0));//初始化邻接矩阵,申请比原结点数量大一的空间
//输入联通的结点
while(m--){
cin>>s>>t;
graph[s][t]=1;
}
}
邻接表模式
vector<list<int>> graphin(){
int n,m,s,t;
cout<<"输入结点数量"<<endl;
cin>>n;//输入结点数量
cout<<"输入边的数量"<<endl;
cin>>m;//输入边的数量
cout<<"输入联通的各结点"<<endl;
vector<list<int>> graph(n+1);//初始化邻接矩阵,注意,这里一般申请比结点大一的空间
//输入联通的结点
while(m--){
cin>>s>>t;
graph[s].push_back(t);
}
return graph;
}
2.1深度优先搜索 主要通过递归栈对图进行深度遍历搜索,函数不断对满足联通关系的结点执行入栈操作,直到达到递推返回条件才弹出返回,然后重新执行入栈操作,直到栈空。原理上时采用回溯递归算法。 具体算法如下: 采用邓俊辉老师《数据结构》算法
enum{undiscover,discover,visited}
void dfs(int s){
int v=s;
int clock=0;
do{
if(status[v]==undiscover)
DFS(s,clock);
}while(s!=(v=(v++%n)))//确保不漏不重
}
void DFS(int v,int clock){
//记录访问时间,之后时间增加
dtime=clock++;
//改变结点状态、
status(v)=discover;
//枚举所有邻居,根据邻居结点状况进行访问
for(int u=firstnbr(v);u>-1;u=nextnbr(v)){
switch(status(u)) {
case undiscover:
type(v,u)=tree;parent(u)=v;DFS(u,clock);break;
case discover:
type(v,u)=backward;break;
case visited:
type(v,u)=(dtime(v)<dtime(u))?forward:cross;break;
}
}
//访问完毕结点之后改变结点状态,做好标记
status(v)=visited;ftime(v)=++clock;
}
同理采用代码随想录里的算法,使用递归三部曲 1.确定递归返回参数和传参
//graph 需要访问的图
//v开始遍历的结点
//n想要方位的终点结点
void DFS(const vector<vector<int>>graph, int v,int n)
void DFS(const vector<vector<int>>graph, int v)
2.确定递归终止条件
if(v==n)
result.push_back(path);//存储路径
return
3.设计单层递归逻辑
for(int i=1;i<=n;i++){
if(graph[v][i]==1){
path.push_back(i);
DFS(garph,i);//递归遍历
path.pop_back();
}
}
完整代码如下:
void DFS(const vector<vector<int>> graph,int v,int n){
if(v==n){
result.push_back(path);
return;
}
for(int i=1;i<=n;i++){
if(graph[v][i]==1){
path.push_back(i);
DFS(graph,i);
path.pop_back();
}
}
}
98.所有可达路径解答
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 1节点到终点的路径
void dfs (const vector<vector<int>>& graph, int x, int n) {
// 当前遍历的节点x 到达节点n
if (x == n) { // 找到符合条件的一条路径
result.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;
// 使用邻接矩阵 表示无线图,1 表示 s 与 t 是相连的
graph[s][t] = 1;
}
path.push_back(1); // 无论什么路径已经是从0节点出发
dfs(graph, 1, n); // 开始遍历
// 输出结果
if (result.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : result) {
for (int i = 0; i < pa.size() - 1; i++) {
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
}
2.2 广度优先搜素 主要通过队列这种数据结构,FIFO模式,对满足该结点联通条件的其他结点不断进行入队操作,直到无联通结点时,联通结点出队,继续执行,直到队空。原理上采用的时迭代递推算法。
void Graph::bfs ( int s ) {
reset(); int clock = 0; int v = s;
do {
if ( UNDISCOVERED == status ( v ) ){
BFS ( v, clock );
} while ( s != ( v = ( ++v % n ) ) );
}
template void Graph::BFS ( int v, int& clock ) {
Queue Q;
status ( v ) = DISCOVERED; Q.enqueue ( v );
while ( !Q.empty() ) {
int v = Q.dequeue(); dTime ( v ) = ++clock;
for ( int u = firstNbr ( v ); -1 < u; u = nextNbr ( v, u ) ){
if ( UNDISCOVERED == status ( u ) ) {
status ( u ) = DISCOVERED; Q.enqueue ( u );
type ( v, u ) = TREE; parent ( u ) = v;
} else {
type ( v, u ) = CROSS;
status ( v ) = VISITED;
}
}
代码随想录中的设计:
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
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 result = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (!visited[i][j] && grid[i][j] == 1) {
result++; // 遇到没访问过的陆地,+1
bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true
}
}
}
cout << result << endl;
}