图题-记录

  • 拓扑排序
  • 并查集

2021/5/11

785. 判断二分图

这道题真的是烦,明明知道思路了,总是超出递归长度,关键点:

  • dfs 函数遇到fasle怎么 直接return 回去 结束遍历

自己这样写一是有错误,混了dfs和bfs,二是超出递归长度

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n=len(graph)
        self.flag=True
        # 给节点分成两组 节点和一阶邻居要分开
        # idx是下一个要分类的点
        set1=[False]*n
        set2=[False]*n

        # 返回true 说明当前还没有冲突
        def dfs(idx):
            if(set1[idx] and set2[idx]):
                return False
            # 节点idx 还未分类;分到1中去
            if(not set1[idx] and not set2[idx]):
                set1[idx]=True
                for v in graph[idx]:
                    set2[v]=True
                    if(set1[v]):
                        return False

            # 节点idx 被分类了
            else:
                # idx 在1中
                if(set1[idx]):
                    for v in graph[idx]:
                        set2[v]=True
                        if(set1[v]):
                            return False

                # idx 在2中
                else:
                    for v in graph[idx]:
                        set1[v]=True
                        if(set2[v]):
                            return False
            # 遍历一阶邻居
            for v in graph[idx]:
                if(not dfs(v)):
                    return False
            return True
        
        for i in range(n):
            if(not set1[i] and not set2[i]):
                if(not dfs(i)):
                    return False

        return True

然后理清楚怎么终止dfs的return 方法之后,还是超出遍历

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n=len(graph)
        self.flag=True
        # 给节点分成两组 节点和一阶邻居要分开
        # 0,1,2表示未分组,第一组,第二组
        mark=[0]*n

        # idx是要分类的点
        def dfs(idx):
            # 如果当前节点未分类 且遍历到了 直接分到第一组
            # 也就是第一个点和所有不联通图上的第一个遍历点 所以是正确的
            # if bfs,就不对了
            if(mark[idx]==0):
                mark[idx]=1
            markNeighbor=2 if mark[idx]==1 else 1
            for v in graph[idx]:
                # 判断是否出现邻居点 被分组 且当前分组冲突的情况
                if(mark[v]==mark[idx]):
                    self.falg=False
                    return # 终止遍历 返回上一depth

                # 没有分组冲突 进行遍历
                mark[v]=markNeighbor
                dfs(v)
                # 重点!!!! 遍历完depth+1,depth+2,...终点之后
                # 判断flag 如果false 那么从当前depth 终止这条path
                if(not self.falg):
                    return 
        
        # 用i in range 是保证不联通图的情况下
        for i in range(n):
            dfs(i)
            if(not self.flag):
                return False

        return True
class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n=len(graph)
        self.flag=True
        # 给节点分成两组 节点和一阶邻居要分开
        # 0,1,2表示未分组,第一组,第二组
        mark=[0]*n

        # idx是要分类的点
        def dfs(idx):
            # 如果当前节点未分类 且遍历到了 直接分到第一组
            # 也就是第一个点和所有不联通图上的第一个遍历点 所以是正确的
            # if bfs,就不对了
            markNeighbor=2 if mark[idx]==1 else 1
            for v in graph[idx]:
                # 判断是否出现邻居点当前分组冲突的情况
                if(mark[v]==mark[idx]):
                    self.flag=False
                    return # 终止遍历 返回上一depth

                # 邻居v没被分组
                elif(mark[v]==0):
                    # 没有分组冲突 进行遍历
                    mark[v]=markNeighbor
                    dfs(v)
                    # 重点!!!! 遍历完depth+1,depth+2,...终点之后
                    # 判断flag 如果false 那么从当前depth 终止这条path
                    if(not self.flag):
                        return 


                
        # 用i in range 是保证不联通图的情况下
        for i in range(n):
            #print('节点{}'.format(i))
            if(mark[i]==0):
                mark[i]=1
                dfs(i)
                if(not self.flag):
                    return False

        return self.flag
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 相当于一个有向图上的节点 不能沿着有向方向出现环
        mark=set()
        from collections import defaultdict
        graph=defaultdict(set)
        for v in prerequisites:
            graph[v[0]].add(v[1])

        # 每个节点可达的节点
        #arrive=defaultdict(set)
        flag=True
        # 遍历dfs 从当前节点 继续探索path
        def dfs(curNode,curPath):
            mark.add(curNode)
            nonlocal flag
            if(curNode in curPath):
                flag=False
                return # 一旦发现有环了 就终止遍历
            # # 如果preNode有先验 就dfs
            for preNode in graph[curNode]:
                #if(preNode in graph.keys()):
                #    return
                #注意这里的return 不应该加 
                if(preNode in graph.keys()):
                    dfs(preNode,curPath+[curNode])
                    if(not flag):
                        return # 终止这一条path

        for node in graph.keys():
            if(node not in mark):
                dfs(node,[])
                if(not flag):
                    return False

        
        return flag

自己的倒数第二个实例超时间了,还是题解巧妙,自己使用mark集合来判断哪个节点完全访问过了,curPath存储当前路径的所有节点;但是题解的巧妙之处在于

  • 使用数组表示mark,这样可以用0,1,2表示节点的三种状态:未访问;正在访问;完全访问结束。那么即mark就可以即当作visied数组;又当作path的集合
  • 只要邻居节点一出现mark[i]=1,那么相当于又遇到正在访问的点了,即有环,return False;很巧妙

DFS

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 相当于一个有向图上的节点 不能沿着有向方向出现环
        from collections import defaultdict
        graph=defaultdict(set)
        for v in prerequisites:
            graph[v[0]].add(v[1])
        flag=True
        # 0未访问;1正在访问;2访问完了
        mark=[0]*numCourses 

        # 遍历dfs 从当前节点 继续探索path
        def dfs(curNode):
            if(mark[curNode]==2 or curNode not in graph.keys()):
                return 

            mark[curNode]=1
            nonlocal flag

            for preNode in graph[curNode]:        
                if(mark[preNode]==0):
                    dfs(preNode)
                    if(not flag):
                        return # 终止这一条path
                elif(mark[preNode]==1):
                    flag=False
                    return

            mark[curNode]=2

        for node in graph.keys():
            if(mark[node]==0):
                dfs(node)
                if(not flag):
                    return False

        return flag
  • 自己注意一个错误:想跳过一些情况时,就是当节点不满足条件时,注意return到底加不加!! 注意这里的return 不应该加 一return后面还未遍历到的同阶节点得不到dfs
  for preNode in graph[curNode]:
                #if(preNode in graph.keys()):
                #    return
                #注意这里的return 不应该加 一return后面还未遍历到的同阶节点得不到dfs
                if(preNode in graph.keys()):
                    dfs(preNode,curPath+[curNode])
                    if(not flag):
                        return # 终止这一条path

BFS

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 相当于一个有向图上的节点 不能沿着有向方向出现环

        from collections import defaultdict
        from collections import deque
        graph=defaultdict(set)
        preNum=[0]*numCourses # i需要pre的数量
        for v in prerequisites:
            graph[v[0]].add(v[1])
            preNum[v[1]]+=1
        
        # bfs 先得到无需先验的课程 然后一步一步找到可以完成的课程
        q=deque([idx for idx in range(numCourses) if preNum[idx]==0])
        cnt=len(q)
        while(q):
            cur=q.popleft()
            # 遍历cur的邻居 pre-1
            if(cur in graph.keys()):
                for v in graph[cur]:
                    preNum[v]-=1
                    if(preNum[v]==0):
                        q.append(v)
                        cnt+=1

        return cnt==numCourses

2021/5/14

684. 冗余连接

并查集,这题卡了好久,结果是一种经典算法

自己下意识要写dfs 结果写半天也不对↓这个不对

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        from collections import defaultdict
        graph=defaultdict(set)

        for e in edges:
            graph[e[0]].add(e[1])
            graph[e[1]].add(e[0])
        mark=[0]*(len(edges)+1)
        self.result=[]
        # dfs找环
        def dfs(cur,curPath):
            print('dfs')
            print(curPath)
            # 正在visit
            mark[cur]=1
            if(cur in graph.keys()):
                print('如果在grapg中')
                print(curPath)
                # 遍历邻居节点
                for negbor in graph[cur]:
                    if(negbor!=cur):
                        print('negbor')
                        print(curPath)
                        if(mark[negbor]==1 or mark[negbor]==2):
                            print('ok')
                            self.result=curPath+[(cur,negbor)]
                            print(self.result)
                            return
                        else:
                            dfs(negbor,curPath+[(cur,negbor)])
                            if(len(self.result)>0):
                                return
            mark[cur]=2
        
        dfs(1,[])

        print(self.result)
            
        return self.result

并查集
可以理解为每个节点都连着一个根节点(代表人物);当有一条边时,判断当前边的两个节点n1,n2的根节点是否相同:

  • n1 n2根节点相同,表明n1 n2本来就在一社区里,本来就可以通过各种边相连;然后当前边又连了,说明出现了环(之间就能连 现在又连了)
  • n1 n2根节点不同,说明n1 n2之前不相连,现在通过当前边连起来了,那么就将n2的根节点连接到n1的根节点上(反之也可以)
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        from collections import defaultdict
        #graph=defaultdict(set)
        # 并查集 父节点都是自己
        parent=list(range(len(edges)+1)) # 0,1,2...,n

        # 返回idx的根节点
        def find(idx):
            # 父节点=自己 说明是根节点
            if(parent[idx]==idx):
                return idx
            else:
                return find(parent[parent[idx]])

        # 根节点不同 合并
        def union(n1,n2):
            # n1,n2的根节点都是自己
            # 把n2的根节点连接到n1即可
            parent[find(n2)]=find(n1)


        for n1,n2 in edges:
            # 父节点不同 就连接
            # r1,r2=find(n1),find(n2)
            if(find(n1)!=find(n2)):
                union(n1,n2)
            else:
                return [n1,n2]
            # if(r1!=r2):
            #     parent[r2]=r1
            # else:
            #     return [n1,n2]

        return []            

210. 课程表 II

这道题竟然卡在了 返回这个路径上。。。 自己写了一个rank列表 想着每走一步 rank+1;但是不知道哪里出现了问题(应该是从有先验的课倒着遍历 选max先验rank+1;自己是从无先验的开始遍历 rank+1,会忽略真正的排序情况),最后几个例子过不去

  • 实际上只要定义一个stack,每当到达一个终点 就append进去就好了。。
class Solution:
    def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
        # 有向图 如果存在环就[] 不存在环就返回顺序
        from collections import defaultdict
        graph=defaultdict(set)

        # k-v:k是v的先验
        for e in prerequisites:
            graph[e[1]].add(e[0])

        self.flag=True
        # 0表示未visit;1表示正在visit;2表示完全visit过了
        mark=[0]*numCourses
        
        rank=[]

        # dfs遍历 判断是否有环
        def dfs(cur):
            mark[cur]=1
            # 如果有先验 就dfs遍历邻居
            if(cur in graph.keys()):
                for v in graph[cur]:
                    # 遇到环了
                    if(mark[v]==1):
                        self.flag=False
                        return
                    # 遇到mark[v]=2时 不用判断了 因为v没有环 如果有早就返回了
                    elif(mark[v]==0):
                        dfs(v)
                        if(not self.flag):
                            return # 遇到环 终止这条path

            mark[cur]=2
            # 每当完成一个说明到达一个path的终点
            rank.append(cur)

        for i in range(numCourses):
            if(mark[i]==0):
                dfs(i)
                if(not self.flag):
                    return []

        return rank[::-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值