DFS
题目12.3链接Depth First Search
DFS可以使用递归和栈来解。
以下两种方法都是将邻接表转化为邻接矩阵,在图的邻接矩阵上操作。
使用递归:
#include <iostream>
#include <cstring>
using namespace std;
const int Max = 105;
int G[Max][Max], color[Max], d[Max], f[Max];
int t, n;
//color -1未遍历 0已遍历,但仍在栈中 1出栈了,已经访问完其邻接表了
void dfs_visit(int u)
{
color[u] = 0;//已遍历
d[u] = ++t;//开始时刻
for(int j=0; j<n; j++)
{
if(!G[u][j]) continue;//不相邻
if(color[j]==-1)//递归访问未访问过的结点
dfs_visit(j);
}
color[u] = 1;//递归完后,出栈,表示其邻接表的点已访问完
f[u] = ++t;//结束时刻
}
void dfs()
{
memset(color, -1, sizeof(color));//初始化
t = 0;//记录时间
for(int i=0; i<n; i++)
{
if(color[i]==-1)//如果未遍历,则可以从它开始DFS
dfs_visit(i);
}
for(int i=0; i<n; i++)//输出结果
{
cout << i+1 << " " << d[i] << " " << f[i] << endl;
}
}
int main()
{
int u, k, v;
cin >> n;
for(int i=0; i<n; i++)
{
cin >> u >> k;
for(int j=0; j<k; j++)
{
cin >> v;
G[u-1][v-1] = 1;
}
}
dfs();
return 0;
}
使用栈:
#include <iostream>
#include <cstring>
#include <stack>
using namespace std;
const int Max = 105;
int G[Max][Max], color[Max], d[Max], f[Max], nt[Max];//nt[i]用来记录邻接矩阵第i行访问到了哪一列
int t, n;
int next(int u)//查询u的邻接点访问到了哪一个
{
for(int v=nt[u]; v<n; v++)
{
nt[u] = v+1;
if(G[u][v]) return v;
}
return -1;//表示邻接点访问完了
}
void dfs_visit(int r)
{
stack<int> S;
S.push(r);
color[r] = 0;
d[r] = ++t;
while(!S.empty())
{
int u = S.top();
int v = next(u);//寻找u的邻接点
if(v!=-1)//u还有邻接点
{
if(color[v]==-1)//未访问过
{
color[v] = 0;
d[v] = ++t;
S.push(v);
}
}
else
{
S.pop();
color[u] = 1;
f[u] = ++t;
}
}
}
void dfs()
{
memset(color, -1, sizeof(color));
memset(nt, 0, sizeof(nt));
t = 0;
for(int i=0; i<n; i++)
{
if(color[i]==-1)
dfs_visit(i);
}
for(int i=0; i<n; i++)
{
cout << i+1 << " " << d[i] << " " << f[i] << endl;
}
}
int main()
{
int u, k, v;
cin >> n;
for(int i=0; i<n; i++)
{
cin >> u >> k;
for(int j=0; j<k; j++)
{
cin >> v;
G[u-1][v-1] = 1;
}
}
dfs();
return 0;
}
BFS
题目12.4链接Breadth First Search
BFS主要使用队列来解,每次将所有为遍历的邻接点入队列。
这个方法还是采用了邻接矩阵,复杂度O(N^2)
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int Max = 105;
int G[Max][Max], color[Max], d[Max];//d存的是距离
int n;
//color -1未遍历 0已遍历
void bfs(int u)
{
memset(color, -1, sizeof(color));
memset(d, -1, sizeof(d));
queue<int> Q;
int v;
Q.push(u);//第一个节点
d[u] = 0;
color[u] = 0;
while(!Q.empty())
{
u = Q.front(); Q.pop();
for(int i=0; i<n; i++)
{
if(G[u][i] && color[i]==-1)//找到未遍历的邻接点
{
color[i] = 0;
Q.push(i);
d[i] = d[u]+1;
}
}
}
for(int i=0; i<n; i++)
{
cout << i+1 << " " << d[i] << endl;
}
}
int main()
{
int u, k, v;
cin >> n;
for(int i=0; i<n; i++)
{
cin >> u >> k;
for(int j=0; j<k; j++)
{
cin >> v;
G[u-1][v-1] = 1;
}
}
bfs(0);
return 0;
}
应用
DFS的应用
求图的关节点
题目15.3链接Articulation Points
求关节点使用了tarjan算法,相关讲解见tarjan算法求关节点和《挑战程序设计竞赛–数据结构与算法》p291。
变量设置如下:
变量 | 含义 |
---|---|
prenum[u] | 以图的任意一点为起点进行DFS,将各顶点u的访问(发现)顺序记录在prenum[u]中,即u在DFS过程中的深度 |
parent[u] | 通过DFS生成一棵树,将其中结点u的父节点记录在parent[u]中 |
lowest[u] | 对于各顶点u,计算通过u能够到达的最低深度数并记入lowest[u] |
(1)如果点u是dfs生成树的根,那么u至少有2个子女。理由:因为一删除u,它的子女所在的子树就断开了。
即若根u有两个及以上的子节点,则根u为关节点。
(2)如果点u不是dfs生成树的根,那么u的一个子女w,从w或w的子孙出发或回边到达u的祖先。如果能够到达,那么点u就不是关节点了。
即若prenum[u]<=lowest[w]
,则u为关节点。
对于prenum和parent均可以在DFS过程直接求出,因此只需考虑lowest的求解
lowest[u]=min{
prenum[u]
min(low[w]|w为点u的子孙)
min(prenum[x]|x和u构成回边)
};
(回边
:原图中除了dfs树中的边外,还有的边叫回边;如:1->2,1->3,2->3中,dfs时1->2->3,这里dfs树中的边为1->2,2->3,那么1->3就是回边)
代码如下:
#include <iostream>
#include <set>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int MAX = 100005;
int V, E, s, t, color[MAX], timer;
int prenum[MAX], parent[MAX], lowest[MAX];
vector<int> G[MAX];
//color 为0表示为访问,为1表示已访问
void dfs(int current, int prev)
{
prenum[current] = lowest[current] = timer;//初始赋值prenum[current] = lowest[current]
timer++;
color[current] = 1;
int next;
for(int i=0; i<G[current].size(); i++)//遍历其所有的邻接点
{
next = G[current][i];//邻接点
if(!color[next])//没有访问过,则可以建立生成树
{
parent[next] = current;//记录父节点
dfs(next, current);//递归DFS
lowest[current] = min(lowest[current], lowest[next]);
}
else if(next!=prev)//筛去其子节点是父节点的情况
{
lowest[current] = min(lowest[current], prenum[next]);
}
}
}
void art_points()
{
memset(color, 0, sizeof(color));
timer = 1;
dfs(0, -1);
int p, ans = 0;
set<int> S;//存储结果,利用set的自动排序功能
for(int i=1; i<V; i++)
{
p = parent[i];
if(p==0) ans++;//记录根节点的子节点个数
else if(prenum[p]<=lowest[i]) S.insert(p);
}
if(ans>1) S.insert(0);
set<int>::iterator it;
for(it=S.begin(); it!=S.end(); it++)
{
printf("%d\n", *it);
}
}
int main()
{
scanf("%d %d", &V, &E);
for(int i=0; i<E; i++)
{
scanf("%d %d", &s, &t);
G[s].push_back(t);
G[t].push_back(s);
}
art_points();
return 0;
}
BFS的应用
树的直径
题目15.4链接Diameter of a Tree
这道题采用了BFS的邻接表实现法,并定义了带权无向图的结构体。主要思路为
1-任选一节点s,求到s的最远结点x。
2-求到x最远的结点y。
3-报告结点x与结点y的距离,即树的直径。
因为是树,所以任意两点之间只有一条路径,也即其最远路径,可以使用BFS来记录所有点到点s的路径长度。
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
struct Edge
{
int t, w;
//Edge(){}
Edge(int t=-1, int w=-1): t(t), w(w){}
};
const int MAX = 100005;
const int INF = (1<<29);
int color[MAX], d[MAX];//d存的是距离
vector<Edge> G[MAX];
int n;
//color -1未遍历 0已遍历
void bfs(int u)
{
memset(color, -1, sizeof(color));
memset(d, -1, sizeof(d));
queue<int> Q;
int v;
Q.push(u);//第一个节点
d[u] = 0;
color[u] = 0;
while(!Q.empty())
{
u = Q.front(); Q.pop();
for(int i=0; i<G[u].size(); i++)
{
int v = G[u][i].t;
if(color[v]==-1)//找到未遍历的邻接点
{
color[v] = 0;
Q.push(v);
d[v] = d[u]+G[u][i].w;
}
}
}
}
void solve()
{
bfs(0);//任意一点
int Max = -1;
int x, y;
for(int i=0; i<n; i++)
{
if(d[i]>=0 && Max<d[i])
{
Max = d[i];
x = i;
}
}
bfs(x);
Max = -1;
for(int i=0; i<n; i++)
{
if(d[i]>=0 && Max<d[i])
{
Max = d[i];
y = i;
}
}
cout << d[y] << endl;
}
int main()
{
int u, k, v;
cin >> n;
if(n==1)
{
cout << 0 << endl;
return 0;
}
for(int i=0; i<n-1; i++)
{
cin >> u >> k >> v;
G[u].push_back(Edge(k, v));
G[k].push_back(Edge(u, v));
}
solve();
return 0;
}