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;
}