洛谷3916-图的遍历-python-(tarjan强连通分量算法+缩点+图的重建+dfs算法)

本文介绍了如何利用Tarjan算法求解图的强连通分量,并通过缩点优化时间复杂度。首先详细解释了Tarjan算法的实现过程,包括dfn、low、vis数组的作用以及如何找到强连通分量。接着,文章阐述了如何根据强连通分量重新构建图并进行DFS搜索,以找出每个点可达的最大值。最后,给出了完整的代码实现,展示了一个具体的解题过程。

题目要求我们求出从每一个点出发所能到达的点的最大值
思路1:对每一个点采用dfs搜索,求出最大点的编号。这样从理论上来说是可行的,但是时间复杂度是O(n^2),肯定是会超时。

思路2:对原图进行缩点,每一个强联通分量都看成一个点,连接这几个点,然后进行dfs,求出每一个点能够到达的最大值作为这一个强连通分量的最大值,最后就是输出答案了。

这里采用思路2来进行解题:
①如何求出一张图的强连通分量
tarjan算法

def tarjan(v):
    global time,num
    vis[v]=1
    stack.append(v)
    dfn[v]=time
    low[v]=time
    time+=1

    #访问v的后继节点 深度优先搜索 同时更新low
    for i in range(len(point[v])):
        s=point[v][i]
        if dfn[side[s][1]]==0:
            tarjan(side[s][1])
            low[v]=min(low[v],low[side[s][1]])
        elif vis[side[s][1]]==1:
            low[v]=min(low[v],low[side[s][1]])

    #出栈
    if dfn[v]==low[v]:
        num+=1
        while stack[-1]!=v:
            value=stack.pop()
            vis[value]=0
            belong[value]=num
            maxp[num]=max(maxp[num],value)
        value=stack.pop()
        vis[value]=0
        belong[value]=num
        maxp[num]=max(maxp[num],value)

简单分析一下tarjan算法,我们引入两个数组dfn[ ]和low[ ],dfn记录访问第一时间点,low记录能回溯的最早时间点,再引入数组vis来记录栈中点的情况。首先我们访问第一个点,对它能到达的点进行遍历,如果当前点未被访问,则该点入栈,如果该点被访问了,看该点是否在栈中。直到某一点再无后继结点。我们在tarjan的过程中,还要更新点的low数组,让其尽可能的小。如果当前的点的dfn值与low值相同,我们进行出栈操作,知道栈顶元素不为当前元素。出栈的元素组成一个强连通分量。我们再引入belong数组与maxp数组分别记录当前点属于哪一个强连通分量,以及当前强连通分量中最大的点的值,强连通分量的序号从一开始编号,num用来存储图中一共有几个强连通分量。
通过这样的操作,我们得出了每一个点所属于的强连通分量的序号,以及对应序号的强连通分量中序号最大的点的值

②重新构图
我们将每一个强连通分量都看成一个点,也就是我们所谓的缩点,然后我们遍历一开始的每一条边,对于一条边的两个顶点,如果属于两个不同的强连通分量,即belong值不同,我们就用一条边将这两个强连通分量连接在一个,有几个强连通分量,最后我们重构的图就有几个点。

#对于原图的每一条边 如果边的两个顶点在不同的强连通分量上
#则我们连接这两个强连通分量
def rebuild():
    global m,num
    newside=[0]*m
    sides=0
    newmap=[[] for i in range(num+1)]
    for i in range(m):
        if belong[side[i][0]]!=belong[side[i][1]]:
            newside[sides]=[belong[side[i][0]],belong[side[i][1]]]
            newmap[belong[side[i][0]]].append(sides)
            sides+=1
    return newside,newmap

newside记录边,newmap记录点连接的边,节省存储空间。图一共有num个顶点。由于每一个点到现在为止都有了自己所属的强连通分量的序号,而maxp数组记录某一个强连通分量中的最大值的序号,接下来我们需要做的就是看某个强连通分量中最大值能否变得更大,我们就对我们的新建的强连通分量的图进行dfs搜索即可,记录最大值即可
③dfs搜索记录最大值

def dfs(v,newside,newmap):
    #强连通分量的序号从小到大 是从深到浅
    if ans[v]:
        return
    ans[v]=maxp[v]
    for i in range(len(newmap[v])):
        side=newmap[v][i]
        dfs(newside[side][1],newside,newmap)
        ans[v]=max(ans[v],ans[newside[side][1]])

完整代码

global n,m
global side,point
global dfn,low,vis,time
global stack
global num,belong,maxp
global ans

def tarjan(v):
    global time,num
    vis[v]=1
    stack.append(v)
    dfn[v]=time
    low[v]=time
    time+=1

    #访问v的后继节点 深度优先搜索 同时更新low
    for i in range(len(point[v])):
        s=point[v][i]
        if dfn[side[s][1]]==0:
            tarjan(side[s][1])
            low[v]=min(low[v],low[side[s][1]])
        elif vis[side[s][1]]==1:
            low[v]=min(low[v],low[side[s][1]])

    #出栈
    if dfn[v]==low[v]:
        num+=1
        while stack[-1]!=v:
            value=stack.pop()
            vis[value]=0
            belong[value]=num
            maxp[num]=max(maxp[num],value)
        value=stack.pop()
        vis[value]=0
        belong[value]=num
        maxp[num]=max(maxp[num],value)
        
#对于原图的每一条边 如果边的两个顶点在不同的强连通分量上
#则我们连接这两个强连通分量
def rebuild():
    global m,num
    newside=[0]*m
    sides=0
    newmap=[[] for i in range(num+1)]
    for i in range(m):
        if belong[side[i][0]]!=belong[side[i][1]]:
            newside[sides]=[belong[side[i][0]],belong[side[i][1]]]
            newmap[belong[side[i][0]]].append(sides)
            sides+=1
    return newside,newmap
    
def dfs(v,newside,newmap):
    #强连通分量的序号从小到大 是从深到浅
    if ans[v]:
        return
    ans[v]=maxp[v]
    for i in range(len(newmap[v])):
        side=newmap[v][i]
        dfs(newside[side][1],newside,newmap)
        ans[v]=max(ans[v],ans[newside[side][1]])
n,m=map(int,input().split())
side=[0]*m
point=[[] for i in range(n+1)]
dfn=[0]*(n+1)
low=[0]*(n+1)
vis=[0]*(n+1)
stack=[]
time=0

num=0
belong=[0]*(n+1)
maxp=[0]*(2*n+1)
for i in range(m):
    side[i]=list(map(int,input().split()))
    point[side[i][0]].append(i)
for i in range(1,n+1):
    if not dfn[i]:
        tarjan(i)
ns,nm=rebuild()
ans=[0]*(num+1)
for i in range(1,num+1):
    if not ans[i]:
        dfs(i,ns,nm)

for i in range(1,n+1):
    print(ans[belong[i]],end=" ")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值