图-PTA-寻找强连通分支&寻找关节点 思路的相似性

这篇博客介绍了如何利用Tarjan算法查找有向图中的强连通分量。文章详细解释了算法的原理,包括深度优先搜索(DFS)的应用,dfn和low数组的含义,以及如何利用栈来识别和输出强连通分支。通过示例程序,展示了算法的实现过程,并给出了输入和输出示例,帮助读者理解算法的实际操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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过的点,出栈即代表点的输出。

思路的基础

理解此类标记时,可以运用类比手法。如下:

  1. 首先,对图进行DFS,可以把图结构表示为树结构。此时会有一些边被遗漏,把这些边以虚线加入到树中,将形成back edge. 可以肯定的是,所有的back edge一定出现在DFS树的某一支脉内部,而不会跨越树根。
  2. 任意从DFS树根至叶的支脉上,dfn序列号递增。这是他们的行政等级。序列号越小(越早被DFS到)行政等级越高。
  3. 若某点有back edge通往高级结点,则高级节点的行政等级为它的low值,即影响力值。(他行政等级不高,但他可以和省委书记通话)
  4. 若某点的子节点比该点具有更高级的low值,则该点的low值变为同样高的等级。(你儿子和省委书记搭上关系,可以与省委书记通话,你也就可以和省委书记通话了)
    注:由3、4可以看出,back edge所连接两点及其之间的所有点的low值都相同,都是他们当中最高级的dfn值(行政等级)。

寻找关节点、强连通分支的具体方法

关节点的寻找和强连通分支的寻找都建立在这一概念的基础上

  1. 关节点:
    ① 若DFS树树根至少有两个子节点(==有两条支脉),则树根为关节点;
    ② 若节点 u 有子节点 v,存在至少一个 v 使得 low[v] ≥ dfn[u](或者说没有back edge能通往比u更高级的点),则该节点为关节点。

②即,该节点至少能屏蔽掉其旗下的一个节点的信息,使其不能向上传播,则说明该节点在信息向上传递这件事上的不可缺失性。

  1. 强连通分支:
    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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值