关节点
倘若在删去顶点v以及v相关联的各边后,将图的一个连通分量分割成两个或两个以上的分量,则称顶点v为该图的一个关节点
关节点的特性
由深度优先生成树可得出两类关节点的特性:
- 若生成树的根有两棵或两棵以上的子树,则此根顶点必为关节点
- 若生成树中某个非叶子顶点v,其某棵子树的根和子树中的其他结点均没有指向v的祖先的回边,则v为关节点
连通图及深度优先生成树
相关参数
- visited[v]为深度优先搜索遍历连通图时访问顶点v的次序号
- low[v]可以理解为顶点v可以往回找到的最远祖先序号
数据
13 17
A
B
C
D
E
F
G
H
I
J
K
L
M
A B
A C
A F
A L
B C
B D
B G
B H
B M
D E
G H
G I
G K
H K
J L
J M
L M
实现代码
#include<stdio.h>
#include<stdlib.h>
#define maxSize 100
typedef struct ArcNode
{
int adjvex;
int weight;
struct ArcNode *nextarc;
}ArcNode;
typedef struct VNode
{
char data;
ArcNode *firstarc;
}VNode;
typedef struct AGraph
{
VNode adjlist[maxSize];
int numNodes, numEdges;
}AGraph;
int FindVex(AGraph *g, char ch)
{
for(int i = 0;i < g->numNodes;i++)
if(g->adjlist[i].data == ch)
return i;
}
AGraph *create_graph()
{
int i, j, k;
ArcNode *pe;
AGraph *g;
g = (AGraph*)malloc(sizeof(AGraph));
FILE *fp = fopen("data.txt", "r");
fscanf(fp, "%d %d", &g->numNodes, &g->numEdges);
printf("图的顶点个数为%d,不重复边数为%d\n", g->numNodes, g->numEdges);
for(i = 0;i < g->numNodes;i++)
{
g->adjlist[i].firstarc = NULL;
}
for(i = 0;i < g->numNodes;i++)
fscanf(fp, "%s", &g->adjlist[i].data);
char x, y;
for(k = 0;k < g->numEdges;k++)
{
fscanf(fp, "%s %s", &x, &y);
//printf("已初始化边(%d, %d)", i, j);
int i = FindVex(g, x);
int j = FindVex(g, y);
pe = (ArcNode*)malloc(sizeof(ArcNode));
pe->adjvex = j;
pe->nextarc = g->adjlist[i].firstarc; //每一个边表分为 adjvex和nextarc两块 第一次添加相当于将边表的nextarc置空,然后邻接表的firstarc连上边表
g->adjlist[i].firstarc = pe; //第二次添加就相当于是头插法,将nextarc指向此时邻接表的firstarc,然后连上邻接表
//对于无向图
pe = (ArcNode*)malloc(sizeof(ArcNode));
pe->adjvex = i;
pe->nextarc = g->adjlist[j].firstarc;
g->adjlist[j].firstarc = pe;
}
fclose(fp);
return g;
}
//图的深度遍历
void DFS(AGraph *g, int v, int visit[])
{
visit[v] = 1;
printf("%c ", g->adjlist[v].data);
ArcNode *p = g->adjlist[v].firstarc;
while(p!=NULL)
{
if(visit[p->adjvex]==0)
DFS(g, p->adjvex, visit);
p=p->nextarc;
}
}
int visited[maxSize]={0};
int count = 0;
int low[maxSize] = {0};
void DFSArticul(AGraph *g, int v0)
{ //从第v0个顶点出发深度遍历优先遍历图g,查找并输出关节点
int min = ++count;
visited[v0] = min; //v0是第count个访问的顶点
ArcNode *p;
for(p = g->adjlist[v0].firstarc; p ; p = p->nextarc) //对v0的每个邻接顶点检查
{
int 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]) //low[w]大于等于visited[v0],说明邻接顶点w的指向的上一级仍然比v0大
printf("%d %c\n", v0, g->adjlist[v0].data); //即v0子树上存在一个结点,该结点均没有指向w的祖先的回边
}
else if(visited[w] < min) //w已访问,w是v0在生成树上的祖先
{
min = visited[w];
}
}
low[v0] = min;
}
void FindArticul(AGraph *g)
{ //连通图G以邻接表作存储结构,查找并输出G上全部关节点。
count = 1;
int i;
visited[0] = 1; //设定邻接表上0号顶点为生成树的根
for(i = 1;i < g->numNodes;i++)
visited[i] = 0;
ArcNode *p = g->adjlist[0].firstarc;
int v = p->adjvex;
DFSArticul(g, v); //从第v顶点出发深度优先查找关节点
if(count < g->numNodes) //生成树的根有至少两棵子树
{
printf("0 %c\n", g->adjlist[0].data); //根是关节点,输出
while(p->nextarc)
{
p = p->nextarc;
v = p->adjvex;
if(visited[v]==0)
DFSArticul(g, v);
}//while
}//if
}//FindArticul
int main()
{
AGraph *G;
int visit[maxSize] = {0};
G = create_graph();
printf("深度遍历结果: \n");
DFS(G, 0, visit);
printf("\n");
printf("\n关节点: \n");
FindArticul(G);
printf("\nG.adjlist[i].data\t");
for(int i = 0;i<G->numNodes;i++)
printf("%3c ", G->adjlist[i].data);
printf("\nvisited[i]\t\t");
for(int i = 0;i<G->numNodes;i++)
printf("%3d ", visited[i]);
printf("\nlow[i]\t\t\t");
for(int i = 0;i<G->numNodes;i++)
printf("%3d ", low[i]);
getchar();
return 0;
}