Write a program to find the strongly connected components in a digraph.
Format of functions:
void StronglyConnectedComponents( Graph G, void (*visit)(Vertex V) );
where Graph is defined as the following:
typedef struct VNode *PtrToVNode;
struct VNode {
Vertex Vert;
PtrToVNode Next;
};
typedef struct GNode *Graph;
struct GNode {
int NumOfVertices;
int NumOfEdges;
PtrToVNode *Array;
};
Here void (*visit)(Vertex V)
is a function parameter that is passed into StronglyConnectedComponents
to handle (print with a certain format) each vertex that is visited. The function StronglyConnectedComponents
is supposed to print a return after each component is found.
Sample program of judge:
#include <stdio.h>
#include <stdlib.h>
#define MaxVertices 10 /* maximum number of vertices */
typedef int Vertex; /* vertices are numbered from 0 to MaxVertices-1 */
typedef struct VNode *PtrToVNode;
struct VNode {
Vertex Vert;
PtrToVNode Next;
};
typedef struct GNode *Graph;
struct GNode {
int NumOfVertices;
int NumOfEdges;
PtrToVNode *Array;
};
Graph ReadG(); /* details omitted */
void PrintV( Vertex V )
{
printf("%d ", V);
}
void StronglyConnectedComponents( Graph G, void (*visit)(Vertex V) );
int main()
{
Graph G = ReadG();
StronglyConnectedComponents( G, PrintV );
return 0;
}
/* Your function will be put here */
Sample Input
(for the graph shown in the figure):
4 5
0 1
1 2
2 0
3 1
3 2
Sample Output:
3
1 2 0
Note: The output order does not matter. That is, a solution like
0 1 2
3
is also considered correct.
Tarjan(塔扬)算法是寻找图中强连通分支的经典算法。它建立在DFS基础上,与“寻找无向图的关节点”的算法有异曲同工之妙,都运用了dfn[MAXN]、low[MAXN]对各点进行标记。dfn[ ]是某点在深度优先搜索中的序列号,low[ ]则是某点所能接触到的最低序列号。
Tarjan算法还利用了栈来存储DFS过的点,出栈即代表点的输出。
思路的基础
理解此类标记时,可以运用类比手法。如下:
- 首先,对图进行DFS,可以把图结构表示为树结构。此时会有一些边被遗漏,把这些边以虚线加入到树中,将形成back edge. 可以肯定的是,所有的back edge一定出现在DFS树的某一支脉内部,而不会跨越树根。
- 任意从DFS树根至叶的支脉上,dfn序列号递增。这是他们的行政等级。序列号越小(越早被DFS到)行政等级越高。
- 若某点有back edge通往高级结点,则高级节点的行政等级为它的low值,即影响力值。(他行政等级不高,但他可以和省委书记通话)
- 若某点的子节点比该点具有更高级的low值,则该点的low值变为同样高的等级。(你儿子和省委书记搭上关系,可以与省委书记通话,你也就可以和省委书记通话了)
注:由3、4可以看出,back edge所连接两点及其之间的所有点的low值都相同,都是他们当中最高级的dfn值(行政等级)。
寻找关节点、强连通分支的具体方法
关节点的寻找和强连通分支的寻找都建立在这一概念的基础上
- 关节点:
① 若DFS树树根至少有两个子节点(==有两条支脉),则树根为关节点;
② 若节点 u 有子节点 v,存在至少一个 v 使得 low[v] ≥ dfn[u](或者说没有back edge能通往比u更高级的点),则该节点为关节点。
②即,该节点至少能屏蔽掉其旗下的一个节点的信息,使其不能向上传播,则说明该节点在信息向上传递这件事上的不可缺失性。
- 强连通分支:
back edge所连接两点及其之间的所有点会形成大回路,这就是一个强连通分支。
由于节点是放在栈内存储的,在大回路中第一个入栈的节点就是这个大回路中dfn最高级的那个,它满足 dfn[ ] == low[ ],因此不断出栈直到这样的一个点就可以输出一个强连通分支。
图示仅展示关节点的寻找过程
My answer:
#include<stdbool.h>
#define min(x,y) ((x)>(y)?(y):(x))
void DFS(int u, Graph G, void(*visit)(Vertex V));
void push(Vertex ver);
Vertex pop(void);
bool instack[MaxVertices]; //whether it is in stack
bool visited[MaxVertices]; //whether it is visited(printed)
int dfn[MaxVertices], low[MaxVertices];
int dfs_cnt = 0;
int stack[MaxVertices]; //for stack
int* top = stack - 1; //for stack
void StronglyConnectedComponents(Graph G, void(*visit)(Vertex V))
{
int i = 0;
for (i = 0; i < G->NumOfVertices; i++) {
instack[i] = false;
visited[i] = false;
dfn[i] = low[i] = -1;
}
for (i = 0; i < G->NumOfVertices; i++) {
if (!visited[i]) DFS(i, G, visit);
}
return;
}
void DFS(int u, Graph G, void(*visit)(Vertex V))
{
dfn[u] = low[u] = dfs_cnt++; //set the cur vertex
push(u);
instack[u] = true;
PtrToVNode p = G->Array[u]; //traverse all the edges of u
while (p) {
if (!instack[p->Vert] && !visited[p->Vert]) { //it is the 1st time to meet v
DFS(p->Vert, G, visit);
low[u] = min(low[u], low[p->Vert]);
}
else if (instack[p->Vert]) { //if v is in stack (cur edge is a back edge)
low[u] = min(low[u], dfn[p->Vert]);
}
p = p->Next;
}
Vertex poped_v = -1;
if (dfn[u] == low[u]) { //找到了某一强连通分支的各节点中在最下面的节点
while (poped_v != u) { //print a strongly component
visit(poped_v = pop());
instack[poped_v] = false; //pop and visit, simultaneously
visited[poped_v] = true;
}
printf("\n");
}
return;
}
void push(Vertex ver)
{
*(++top) = ver;
return;
}
Vertex pop(void)
{
return *(top--);
}
附录:本题题目中的读图函数
/
PtrToVNode insert(PtrToVNode p, int x)
{
p->Next = (PtrToVNode)malloc(sizeof(struct VNode));
p = p->Next;
p->Vert = x;
p->Next = NULL;
return p;
}
Graph ReadG()
{
int i, j, m, n, x, y;
scanf("%d%d", &m, &n);
Graph p = (Graph)malloc(sizeof(struct GNode));
PtrToVNode *rear = (PtrToVNode*)malloc(m * sizeof(struct VNode));
p->Array = (PtrToVNode*)malloc(m * sizeof(struct VNode));
p->NumOfVertices = m;
p->NumOfEdges = n;
for (i = 0; i < m; i++) {
rear[i] = p->Array[i] = (PtrToVNode)malloc(sizeof(struct VNode));
p->Array[i]->Next = NULL;
}
for (i = 0; i < n; i++) {
scanf("%d%d", &x, &y);
rear[x] = insert(rear[x], y);
}
for (i = 0; i < m; i++) {
PtrToVNode tmp = p->Array[i];
p->Array[i] = p->Array[i]->Next;
tmp->Next = NULL;
free(tmp);
}
return p;
}
/
参考资料:https://blog.youkuaiyun.com/Prediction__/article/details/100030166