关节点和重连通分量(第七章 P178 算法7.10,7.11)

 

如何确定关节点?

算法 7.107.11 的思路是这样的:首先在深度优先遍历图时,不仅标注某顶点是否被访问,还标注它的访问顺序。visited[] 不再只是 FALSE TRUE,而是 1~顶点数。

由于采用深度优先遍历,某结点的祖先被访问的顺序必先于该结点被访问的顺序。以上图为例,由第 1 个结点 A 深度优先遍历的顺序是:ALMJB、…… 增加 1 个辅助数组 low[], 对顶点 v,定义 low[v]=min(visited[v],low[w],visited[k])。其中 w k 分别是 v 的孩子和由回边相连的祖先。

由算法 7.11 可知,low[]是在递归调用返回之前求得的。所以,求得 low[]的顺序是:…… BML、…… 也就是说,孩子的 low[]是先于双亲的 low[]而获得的。这可由程序运行结果中的 lowOrder[] 看出(增加辅助数组 lowOrder[]的目的就是帮助分析求得 low[]的顺序)

如果顶点 v 有孩子 w,且有 low[w]visited[v],则顶点 v 必为关节点。下面分析几种可能存在的情况:

(1) 如果顶点 v 有通过回边相连的祖先 k,则 low[v]=visited[k](祖先顶点 k 被访问的顺序)。同时 k 也是 v 的双亲 u 的祖先或双亲,故有 low[v]=visited[k]visited[u](结点祖先或双亲必先于该结点被访问)。不满足判定关节点的公式,故 u 不是 v 的关节点。这种情况如上图中顶点 B low[]=顶点 A visited[]=1,其双亲 M visited[]=3,故 M不是 B 的关节点。

(2) 如果顶点 v 没有通过回边相连的祖先,但有孩子 w,而孩子顶点 w 有通过回边相连的祖先 k,则 low[w]=visited[k],而 k 也是 v 的双亲 u 的祖先,仍有 visited[k]≤ visited[u]。如顶点 K 没有通过回边相连的祖先,但有孩子 G,而 G 有通过回边相连的祖先 B。顶点 G low[]等于顶点 B visited[]=5,也等于顶点 K low[]。而 K 的双亲 H 的 visited[]=6,故 H 不是 K 的关节点。

(3) 如果顶点 v 既无孩子又无通过回边相连的祖先,则其双亲结点 u 是关节点。在这种情况下,low[v]=visited[v](顶点 v 被访问的顺序)。而 u 被访问的顺序必定小于 v 的,故有 low[v]=visited[v]visited[u]。所以 u v 的关节点。如顶点 E 就是既无孩子又无通过回边相连的祖先,则其双亲结点 D E 的关节点。

(4) 如果顶点 v 没有通过回边相连的祖先,虽有孩子顶点 w,但 w 也没有通过回边相连的祖先,则 v 的双亲结点 u 是关节点。在这种情况下,low[w]= visited[w](顶点 w 被访问的顺序)low[v]=visited[v](顶点 v 被访问的顺序)visited[u],故 u v 的关节点。如顶点 D 虽有孩子顶点 E,但 E 没有通过回边相连的祖先,则 low[D]=5=visited[B]。故 B 是 D 的关节点。

 

通过 low[w]visited[v]来判断连通图关节点的方法不能用于根结点。因为根结点的 visited[]=1,是最小值。判断根结点是否为关节点要看它有几棵子树,如果超过 1 棵,则根结点就是关节点。原因是,它的每棵子树上的结点都和其它子树的不相连。否则在深度优先遍历其它子树时,就会遍历到,也就不成为根结点的子树了。所以算法 7.10 在深度优先遍历时,不是直接从根结点遍历,而是从根结点的第 1 个邻接顶点开始遍历。当遍历完这个邻接顶点的生成子树,若还有顶点没被访问,则说明根结点是关节点。如上图所示,对根结点 A 的第 1 棵子树 L 遍历结束后,A 还有邻接点 F 没被访问到。说明除根结点 A 之外,L 子树上的任何一个结点都不和 F 邻接。这样,若根结点 A 被删除,原图就会被分割成 L 子树和 F 两部分。故根结点 A 是关节点。

 

 

typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */

#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
#include<limits.h> //常量INT_MAX和INT_MIN分别表示最大、最小整数

/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2 


#define MAX_NAME 2 /* 顶点字符串的最大长度+1 */
typedef int InfoType;
typedef char VertexType[MAX_NAME]; /* 字符串类型 */


/* ---------------------------------  图的邻接表存储表示    --------------------------------*/

#define MAX_VERTEX_NUM 20
typedef enum { DG, DN, AG, AN }GraphKind; /* {有向图,有向网,无向图,无向网} */
typedef struct ArcNode
{
	int adjvex; /* 该弧所指向的顶点的位置 */
	struct ArcNode *nextarc; /* 指向下一条弧的指针 */
	InfoType *info; /* 网的权值指针) */
}ArcNode; /* 表结点 */
typedef struct
{
	VertexType data; /* 顶点信息 */
	ArcNode *firstarc; /* 第一个表结点的地址,指向第一条依附该顶点的弧的指针 */
}VNode, AdjList[MAX_VERTEX_NUM]; /* 头结点 */
typedef struct
{
	AdjList vertices;
	int vexnum, arcnum; /* 图的当前顶点数和弧数 */
	int kind; /* 图的种类标志 */
}ALGraph;

/* ---------------------------------------------------------------------------------------------*/




/* ---------------------------  需要用的图的邻接表存储的基本操作 --------------------------*/

int LocateVex(ALGraph G, VertexType u)
{ /* 初始条件: 图G存在,u和G中顶点有相同特征 */
  /* 操作结果: 若G中存在顶点u,则返回该顶点在图中位置;否则返回-1 */
	int i;
	for (i = 0; i < G.vexnum; ++i)
		if (strcmp(u, G.vertices[i].data) == 0)
			return i;
	return -1;
}

Status CreateGraph(ALGraph *G)
{ /* 采用邻接表存储结构,构造没有相关信息的图G(用一个函数构造4种图) */
	int i, j, k;
	int w; /* 权值 */
	VertexType va, vb;
	ArcNode *p;
	printf("请输入图的类型(有向图:0,有向网:1,无向图:2,无向网:3): ");
	scanf("%d", &(*G).kind);
	printf("请输入图的顶点数,边数: ");
	scanf("%d,%d", &(*G).vexnum, &(*G).arcnum);
	printf("请输入%d个顶点的值(<%d个字符):\n", (*G).vexnum, MAX_NAME);
	for (i = 0; i < (*G).vexnum; ++i) /* 构造顶点向量 */
	{
		scanf("%s", (*G).vertices[i].data);
		(*G).vertices[i].firstarc = NULL;
	}
	if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
		printf("请顺序输入每条弧(边)的权值、弧尾和弧头(以空格作为间隔):\n");
	else /* 图 */
		printf("请顺序输入每条弧(边)的弧尾和弧头(以空格作为间隔):\n");
	for (k = 0; k < (*G).arcnum; ++k) /* 构造表结点链表 */
	{
		if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
			scanf("%d%s%s", &w, va, vb);
		else /* 图 */
			scanf("%s%s", va, vb);
		i = LocateVex(*G, va); /* 弧尾 */
		j = LocateVex(*G, vb); /* 弧头 */
		p = (ArcNode*)malloc(sizeof(ArcNode));
		p->adjvex = j;
		if ((*G).kind == 1 || (*G).kind == 3) /* 网 */
		{
			p->info = (int *)malloc(sizeof(int));
			*(p->info) = w;
		}
		else
			p->info = NULL; /* 图 */
		p->nextarc = (*G).vertices[i].firstarc; /* 插在表头 */
		(*G).vertices[i].firstarc = p;
		if ((*G).kind >= 2) /* 无向图或网,产生第二个表结点 */
		{
			p = (ArcNode*)malloc(sizeof(ArcNode));
			p->adjvex = i;
			if ((*G).kind == 3) /* 无向网 */
			{
				p->info = (int*)malloc(sizeof(int));
				*(p->info) = w;
			}
			else
				p->info = NULL; /* 无向图 */
			p->nextarc = (*G).vertices[j].firstarc; /* 插在表头 */
			(*G).vertices[j].firstarc = p;
		}
	}
	return OK;
}


Boolean visited[MAX_VERTEX_NUM]; /* 访问标志数组(全局量) */
void(*VisitFunc)(char* v); /* 函数变量(全局量) */



/* --------------------------------------------------------------------------------------------------*/



/* 实现算法7.10、7.11的程序 */

int count; /* 全局量count对访问计数 */
int low[MAX_VERTEX_NUM];

void DFSArticul(ALGraph G, int v0)
{ /* 从第v0个顶点出发深度优先遍历图G,查找并输出关节点。算法7.11 */
	int min, w;
	ArcNode *p;
	visited[v0] = min = ++count; /* v0是第count个访问的顶点 */
	for (p = G.vertices[v0].firstarc; p; p = p->nextarc) /* 对v0的每个邻接顶点检查 */
	{
		w = p->adjvex; /* w为v0的邻接顶点 */
		if (visited[w] == 0) /* w未曾访问,是v0的孩子 */
		{
			DFSArticul(G, w); /* 返回前求得low[w] */
			if (low[w] < min)
				min = low[w];
			if (low[w] >= visited[v0])
				printf("%d %s\n", v0, G.vertices[v0].data); /* 关节点 */
		}
		else if (visited[w] < min)
			min = visited[w]; /* w已访问,w是v0在生成树上的祖先 */
	}
	low[v0] = min;
}

void FindArticul(ALGraph G)
{ /* 连通图G以邻接表作存储结构,查找并输出G上全部关节点。算法7.10 */
  /* 全局量count对访问计数。 */
	int i, v;
	ArcNode *p;
	count = 1;
	low[0] = visited[0] = 1; /* 设定邻接表上0号顶点为生成树的根 */
	for (i = 1; i < G.vexnum; ++i)
		visited[i] = 0; /* 其余顶点尚未访问 */
	p = G.vertices[0].firstarc;
	v = p->adjvex;
	DFSArticul(G, v); /* 从第v顶点出发深度优先查找关节点 */
	if (count < G.vexnum) /* 生成树的根有至少两棵子树 */
	{
		printf("%d %s\n", 0, G.vertices[0].data); /* 根是关节点,输出 */
		while (p->nextarc)
		{
			p = p->nextarc;
			v = p->adjvex;
			if (visited[v] == 0)
				DFSArticul(G, v);
		}
	}
}

void main()
{
	int i;
	ALGraph g;
	printf("请选择无向图\n");
	CreateGraph(&g);
	printf("输出关节点:\n");
	FindArticul(g);
	printf(" i G.vertices[i].data visited[i] low[i]\n");
	for (i = 0; i < g.vexnum; ++i)
		printf("%2d %9s %14d %8d\n", i, g.vertices[i].data, visited[i], low[i]);
}

运行结果:

连通无向图:
 
 
根据输入产生的邻接表:
 
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值