tarjan算法(C语言)

可能是由于算法基础不够,学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");
	} 
}

作为一个大一人,只能做到这一步啦

算法是真滴难,我的理解全在里面了

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力攻坚操作系统

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值