tarjan算法

dfn数组维护每一个点第一次被搜索到的时间戳,当下一次搜索的时间戳比当前的时间戳小,说明出现了强连通分量。在同一个dfn树的子树中,dfn越小,则其越浅
low数组代表dfn树中,此点以及其后代指出去的边,能返回到的最浅的点的时间戳
当dfn和low的值相同时,代表以当前节点为根的子树是一个强连通分量

void tarjan(int u)
{
	low[u]=dfn[u]=++num;//每一次搜到一个新的点,序号加一
	st[++top]=u;//用栈维护强连通分量
	for(int i=fir[u];i;i=nex[i])//搜索当前点指出去的边
	{
		int v=to[i];
		if(!dfn[v])//如果v节点未访问过
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);//更新当前节点的最小时间戳
		}
		else if(!co[v])//如果还在栈内
		low[u]=min(low[u],dfn[v]);//更新最小的时间戳
	}
	if(low[u]==dfn[u])//后代不能找到更浅的点
	{
		co[u]=++col;//增加一个强连通分量
		++si[col];
		while(st[top]!=u)
		{
			++si[col];
			co[st[top]]=col;
			--top;
		}
		--top;
	}
}

建图的话,遍历原有的边,如果所对应的两个节点所属的强连通编号不同,那么需要添边,最后得到一个有向无环图。

割点和桥
割点:无向连通图中,去掉一个顶点及和它相邻的所有边,图中的联通分量数增加,则该顶点称为割点。
桥:无向联通图中,去掉一条边,图中的联通分量增加,则这条边成为桥。
有割点不一定有桥,有桥不一定存在割点。
桥一定是割点依附的边
边双联通:一个无向图中去掉任意一条边不会改变此图的联通性,即不存在桥。
点双联通:一个无向图中去掉日益一个节点不会改变此图的联通性,即不存在割点。

void tarjan(int u)
{
	low[u]=dfn[u]=++t;//新点初始化
	stck[++top]=u;//节点入栈
	for(int i=head[u];i!=-1;i=nxt[i])//遍历u指出去的边
	{
		if(vis[i])continue;
		vis[i]=vis[i^1]=1;//标记走过的点,无向图包含双向边,正向边与反向边
		int v=pnt[i];
		if(!dfn[v])
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);//更新low值
		}
		else//与有向图区别,不需要判断是否在栈内
		low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])//后代不能找到更浅的点
	{
		co[u]=++col;//增加一个强连通分量
		++si[col];
		while(st[top]!=u)
		{
			++si[col];
			co[st[top]]=col;
			--top;
		}
		--top;
	}
}

最小树形图:一个有向图,存在以某个点为根的,可以到达所有点的一个最小生成树,则它就是最小树形图。
过程:
1.选入边集——找到除root点之外,每一个点的所有入边中权值最小的,用数组in记录下这个最小值,用pre记录到达该点的前驱。(若图中存在独立的点,最小树形图是不存在的)
2.找有向环,并用id记录节点所属环的编号。
3.找到环后,缩点,更新权值。
4.以环数为下一次查找的点数重新建图,继续执行上述操作,直到没有环或判定出不存在最先树形图为止。

//求具有V个点,以root为根节点的图map的最小树形图
int zhuliu(int root, int V, int mp[MAXV + 7][MAXV + 7])
{
    bool vis[MAXV + 7];
    bool f[MAXV + 7];//缩点标记为true,否则仍然存在
    int pre[MAXV + 7];//点i的父节点为pre[i]
    int sum = 0;//最小树形图的权值
    int i, j, k;
    for(i = 0; i <= V; i++) f[i] = false, mp[i][i] = INF;
    pre[root] = root;
    while(true)
    {
        for(i = 1; i <= V; i++){//求最短弧集合E0
            if(f[i] || i == root) continue;
            pre[i] = i;
            for(j = 1; j <= V; j++)
                if(!f[j] && mp[j][i] < mp[pre[i]][i])
                    pre[i] = j;
            if(pre[i] == i) return -1;
        }
        for(i = 1; i <= V; i++)
        {//检查E0
            if(f[i] || i == root) continue;
            for(j = 1; j <= V; j++) vis[j] = false;
            vis[root] = true;
            j = i;//从当前点开始找环
            do
            {
                vis[j] = true;
                j = pre[j];
            }while(!vis[j]);
            if(j == root)continue;//没找到环
            i = j;//收缩G中的有向环
            do{//将整个环的取值保存,累计计入原图的最小树形图
                sum += mp[pre[j]][j];
                j = pre[j];
            }while(j != i);
            j = i;
            do{//对于环上的点有关的边,修改其权值
                for(k = 1; k <= V; k++)
                    if(!f[k] && mp[k][j] < INF && k != pre[j])
                        mp[k][j] -= mp[pre[j]][j];
                j = pre[j];
            }while(j != i);
            for(j = 1; j <= V; j++)
            {//缩点,将整个环缩成i号点,所有与环上的点有关的边转移到点i
                if(j == i) continue;
                for(k = pre[i]; k != i; k = pre[k]){
                    if(mp[k][j] < mp[i][j]) mp[i][j] = mp[k][j];
                    if(mp[j][k] < mp[j][i]) mp[j][i] = mp[j][k];
                }
            }
            for(j = pre[i]; j != i; j = pre[j]) f[j] = true;//标记环上其他点为被缩掉
            break;//当前环缩点结束,形成新的图G',跳出继续求G'的最小树形图
        }
        if(i > V){//如果所有的点都被检查且没有环存在,现在的最短弧集合E0就是最小树形图.累计计入sum,算法结束
            for(i = 1; i <= V; i++)
                if(!f[i] && i != root) sum += mp[pre[i]][i];
            break;
        }
    }
    return sum;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值