如何确定关节点?
算法 7.10、7.11 的思路是这样的:首先在深度优先遍历图时,不仅标注某顶点是否被访问,还标注它的访问顺序。visited[] 不再只是 FALSE 和 TRUE,而是 1~顶点数。
由于采用深度优先遍历,某结点的祖先被访问的顺序必先于该结点被访问的顺序。以上图为例,由第 1 个结点 A 深度优先遍历的顺序是:A、L、M、J、B、…… 增加 1 个辅助数组 low[], 对顶点 v,定义 low[v]=min(visited[v],low[w],visited[k])。其中 w 和 k 分别是 v 的孩子和由回边相连的祖先。
由算法 7.11 可知,low[]是在递归调用返回之前求得的。所以,求得 low[]的顺序是:…… B、M、L、…… 也就是说,孩子的 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]);
}
运行结果: