使用Tarjan算法求解图的割点和桥。
1、割点
主要的算法结构就是DFS,一个点是割点,当且仅当以下两种情况:
(1)该节点是根节点,且有两棵以上的子树;
(2)该节点的任一子节点,没有到该节点祖先的反向边(就是说如果没有这个割点,那么这个子节点和那个祖先之间不连通);
1 void cutpoint_Tarjan(int u,int parent) 2 { 3 int son; //节点m的儿子节点 4 ENode *ptr=(ENode *)malloc(sizeof(ENode)); 5 6 dfn[u]=low[u]=depth++; //访问+标记+遍历 7 vis[u]=1; 8 ptr=ALG->vlist[u].firstedge; 9 while(ptr!=NULL) 10 { 11 son=ptr->key; 12 if(!vis[son]) 13 { 14 DFS(son,u); 15 low[u]=MIN(low[u],low[son]); 16 17 if(u==root) //不同之处//根节点[要定义初始访问节点,因为要考虑割点的2个判断条件] 18 cut[u]++; 19 else if(u!=root && dfn[u] <= low[son]) 20 cut[u]++; //m是割点 21 } 22 else if(son != parent) //有后向边 23 { 24 low[u]=MIN(low[u],dfn[son]); 25 } 26 ptr=ptr->next; 27 } 28 }
2、桥
Tarjan算法求割边(桥):
【1】使用(son!=parent && dfn[son]<dfn[u]);
1 void init_Tarjan(void) 2 { 3 depth=0; 4 for(int i=0;i<ALG->n;i++) 5 { 6 dfn[i]=low[i]=-1; 7 vis[i]=0; 8 } 9 10 num_bridge=0; 11 for(int j=0;j<ALG->e;j++) 12 { 13 bridge_Node[j].front=0; 14 bridge_Node[j].rear =0; 15 } 16 } 17 18 void Add_to_BNode(int front,int rear) //从坐标1开始存储 19 { 20 bridge_Node[num_bridge].front=front; 21 bridge_Node[num_bridge].rear =rear; 22 } 23 24 void bridgenode_Tarjan(int u,int parent) 25 { 26 int son; 27 ENode *ptr=(ENode*)malloc(sizeof(ENode)); 28 29 dfn[u]=low[u]=depth++; //访问+标记+遍历 30 vis[u]=1; 31 ptr=ALG->vlist[u].firstedge; 32 while(ptr!=NULL) 33 { 34 son=ptr->key; 35 if(son!=parent && dfn[son]<dfn[u]) //避免走重边,效果和id一样 36 { 37 if(!vis[son]) 38 { 39 bridge_node_Tarjan(son,u); 40 low[u]=MIN(low[u],low[son]); 41 if(low[son] > dfn[u]) //(u,son)是桥 42 { 43 num_bridge++; 44 Add_to_BNode(u,son); //存储桥 45 } 46 } 47 else if(son != parent) 48 { 49 low[u]=MIN(low[u],dfn[son]); 50 } 51 } 52 ptr=ptr->next; 53 } 54 }
【2】为每一条边标号 id记录每条边(一条无向边拆成的两条有向边id相同),每个点的父亲到它的边的标号;
1 //结点定义 /*****注意边表节点定义有所变化****/ 2 typedef struct edge_node{ 3 int key; //儿子节点[边的终点] 4 int id; //边的编号 5 struct edge_node *next; 6 }ENode; 7 void init_Tarjan(void) //Tarjan算法初始化 8 { 9 depth=0; 10 for(int i=0;i<ALG->n;i++) 11 { 12 vis[i]=0; 13 dfn[i]=low[i]=-1; 14 } 15 count_bridge=0; 16 for(int j=1;j<=ALG->e;j++) //取值于1-e 17 bridge[j]=0; 18 } 19 void bridge_Tarjan(int u,int id) //id是u的父亲边的编号 20 { 21 int son; //u的儿子节点 22 ENode *ptr=(ENode *)malloc(sizeof(ENode)); 23 24 dfn[u]=low[u]=depth++; //访问+标记+遍历 25 vis[u]=1; 26 ptr=ALG->vlist[u].firstedge; 27 while(ptr!=NULL) 28 { 29 if(ptr->id != id) //避免走重边,相当于cutpoint_Tarjan中的(son != parent) 30 { 31 son=ptr->key; 32 if(!vis[son]) 33 { 34 bridge_Tarjan(son,ptr->id); 35 low[u]=MIN(low[u],low[son]); 36 if(dfn[u] < low[son]) //注意不取等号,当DFN[u]==LOW[v]时,当u->v dfs递归,存在一条v->u的回边,使得LOW[v]=DFN[u];故不为桥 37 { 38 bridge[ptr->id]=1; //第id边是桥 39 printf("(%c,%c) ",ALG->vlist[u].vertex,ALG->vlist[son].vertex); //用于输出割边 40 } 41 } 42 else 43 { 44 low[u]=MIN(low[u],dfn[son]); 45 } 46 } 47 ptr=ptr->next; 48 } 49 }