可能是由于算法基础不够,学tarjan花了我好长时间,tarjan也是我目前学过的感觉最难的算法;
话不多说,直接上代码(分析都在代码里了)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_SIZE 50 //最大结点数
typedef struct arc{
int index;//存储结点在节点表中的位置
struct arc* next;//指向下一个边
}arc;//边结构
typedef struct{
int xiabiao;//存储结点在结点表中的下标
int data;
arc* firstarc;//指向从该点指出的第一条边
}vex;//顶点结构
typedef struct{
int vexsnum;
int arcsnum;
vex vexs[MAX_SIZE];//节点表
}Mgraph;//图
struct{
int top;
vex stack[MAX_SIZE];
}S;//栈
void make_empty()//清空栈
{
S.top=0;
}
bool is_empty()//判断栈是否为空
{
return S.top==0;
}
void push(vex v)//将元素v入栈
{
S.stack[(S.top)++]=v;
}
vex pop()//弹出栈顶元素
{
return S.stack[--(S.top)];
}
int ins[MAX_SIZE]={0};//判断结点是否在栈中,ins[i]为0说明结点i不在栈中
int dfn[MAX_SIZE]={0};//结点被访问的时间(时间戳),dfn[i]为0说明结点i没有被访问过
int low[MAX_SIZE]={0};//结点能够追溯回的最早被访问的顶点(即时间戳最小的顶点)
int time=0;//访问时间
void tarjan(Mgraph G,int i);//tarjan算法函数(从结点i开始深搜)
void creat_graph(Mgraph* G);//创建有向图
int find_location(Mgraph G,int data);//寻找data在结点表中的位置
int main()
{
Mgraph G;
creat_graph(&G);
int d;
int i;
printf("您想从哪个顶点开始:");
scanf("%d",&d);
i=find_location(G,d);
make_empty();
tarjan(G,i);
for(i=0;i<G.vexsnum;i++)
{
if(dfn[i]==0)
tarjan(G,i);
}
return 0;
}
void creat_graph(Mgraph* G)
{
printf("请输入有向图中结点数和边数:");
scanf("%d%d",&(G->vexsnum),&(G->arcsnum));
printf("请输入各顶点的值:");
int i;
for(i=0;i<G->vexsnum;i++)
{
scanf("%d",&(G->vexs[i].data));
G->vexs[i].firstarc=NULL;
G->vexs[i].xiabiao=i;
}
printf("请输入连接有向图两个顶点的边(边从顶点1指向顶点2):\n");
int m,n;//存储结点值
int x,y;//存储结点位置
for(i=0;i<G->arcsnum;i++)
{
scanf("%d%d",&m,&n);
x=find_location(*G,m);
y=find_location(*G,n);
arc* new_arc=(arc*)malloc(sizeof(arc));
new_arc->index=y;
new_arc->next=G->vexs[x].firstarc;
G->vexs[x].firstarc=new_arc;
}
printf("有向图建立完成!\n");
}
int find_location(Mgraph G,int data)
{
int i;
for(i=0;i<G.vexsnum&&G.vexs[i].data!=data;i++);
return i;
}
/*
tarjan算法依赖于深度优先搜索(DFS),深度优先搜索形成一颗深度优先搜索树,
而寻找强联通分量就是寻找结点所属的最大环(因为只有环里的元素才能保证任意两
点是可以相互到达的),将深度优先搜索树中省略的图中的边画出来,分为两种:
一:横叉边:边所连的两结点在图中无子孙、先祖关系,且横叉边一定不能构成环
二:返祖边:边所指向的点为该点的祖先,返祖边所连接的点一定能形成环
深搜点v的下一个点u时有三种情况:
一:若点u还未被访问,则令low[u]=dfn[u]=++time ;继续深搜
二:若点u已被访问,且点u在栈中(说明u为v的先祖结点),
则令low[v]=min{low[v],dfn[u]}
三:若点u已被访问且u不在栈中,则点u属于其他强联通分量,所以u与v不可能属
于同以强联通分量,不作操作,继续搜索u的其他临接点
*/
/*
tarjan算法证明:
为什么从u出发的DFS全部结束回到u时,若 dfn[u]=low[u],
此时将栈中u及其上方的节点 弹出,就找到了一个强连通分量?
强连通分量的特性:有向边,任意两个节点都可以相互到达。
此时所有节点分成以下几类:
1)还没被访问过的节点
2) 栈中比u早的节点(在u下方)
3) 栈中比u晚的节点(在u上方)
4) 栈中的u
5) 曾经入栈(访问过),又出了栈的节点
证明:
1):显然由u不可达。
2):由u不可达,因为DFN[u] = LOW[u]; u怎么转,能到达最早的点只有自己。
3):u可达此类节点,且这类节点可达u。
u可达此类节点是显然的。
证:此类节点可达u
若有此类节点x不可达u,首先不存在有向边(x->u),寻找x所能到达的最早的:
1.如果就是x,low[x]=dfn[x],则x应该已经被弹出栈了,这和x是第3类节点矛盾。
2.栈里面的节点y,则y必然比u晚,因为dfn[u]=low[u],u最早能到达的点为其本身,
y比u早,则是y。而且有 low[y] = dfn[y](若此条不成立,则x还能到达比y更早的节点,矛盾)。
而若 low[y] = dfn[y],则y应该已经被弹出栈了,y上方的x当然也已经不再栈中,
这和x是第3类节点矛盾。
4):略。
5):这类节点不可达u
1.早于u遍历到,早于u的出栈即证明不可达u。
若可达的话,它此时应该还在栈里面,u的下面 —导致矛盾。
2.晚于u遍历到,任取节点x,假设x可达的最早节点是y,则y一定晚于u,
即 x不可达u. 任取节点x。x之所以已经被弹出栈,一定是 因为最终low[x] = dfn[x],
或x位于某个y节点上方, 由y可达,且y满足条件:最终的 low[y] = dfn[y]。
因为y曾经出现在u的上方,所以y一定晚于u。因为 low[x]不可能小于等于dfn[u]
(否则low[y]就也会小于等于dfn[u],这和low[y]=dfn[y]矛盾),
所以x到达不了u及比u早的节点。
*/
void tarjan(Mgraph G,int i)
{
dfn[i]=low[i]=++time;
push(G.vexs[i]);//将点i入栈
ins[i]=1;
vex v;
arc* p=G.vexs[i].firstarc;
for(;p!=NULL;p=p->next)//遍历点i的每一个临接点
{
if(dfn[p->index]==0)//该点未被访问
{
tarjan(G,p->index);
//注意此处的回溯很关键
low[i]=(low[i]>low[p->index])?low[p->index]:low[i];//回溯时取i的后继的low和i本身的low中较小的那个
}
else if(dfn[p->index]&&ins[p->index])//如果该点已被访问过且该点在栈中
{
low[i]=(low[i]>dfn[p->index])?dfn[p->index]:low[i];
}
//如果该点已被访问且已出栈则不作任何处理
}
//判断该结点是否为强连通分量的顶点
if(dfn[i]==low[i])
{
printf("{ ");
while(!is_empty())
{
v=pop();
ins[v.xiabiao]=0;//结点已出站
printf("%d ",v.data);
if(v.xiabiao==i)
break;
}
printf("}\n");
}
}
作为一个大一人,只能做到这一步啦
算法是真滴难,我的理解全在里面了