简体   繁体   English

使用有向图与无向图的可见集

[英]Using a seen set for a directed graph vs. undirected graph

I've been deepening my understanding of algorithms for an undirected graph vs. undirected graph problems on LeetCode.我一直在加深对 LeetCode 上无向图与无向图问题算法的理解。 The key difference I realized is for a problem like 841 Keys and Rooms because this is directed I need to add the "0" node to the seen set.我意识到的关键区别在于像841 Keys and Rooms这样的问题,因为这是定向的,我需要将“0”节点添加到所见集。 Specifically this line early on:特别是早期的这条线:

seen_rooms.add(0)

On the other hand, for 547. Number of Provinces , because the graph is undirected I never needed to add it "early" on.另一方面,对于547. Number of Provinces ,因为该图是无向的,我从不需要“早期”添加它。 I could have added it later in my loop我可以稍后在我的循环中添加它

Problem 547:问题 547:

class Solution():    
    def findCircleNum(self, A):
#Finds your neighboring cities 
        seen_cities = set()
        def find_cities(cur_city):
            nei_cities = A[cur_city]
            #First iter (0) nei city 
            #cur_city = 0 
            #find_cities (0) go through neighbors of 0
            #Don't need to add b/c this is going through it each time so when 1 -> 2 we would have had 1 <- 2 as well 
            # seen_cities.add(cur_city)
            for nei_city, can_go in enumerate(nei_cities):
                if can_go == 1 and nei_city not in seen_cities:
                    seen_cities.add(nei_city)
                    find_cities(nei_city)
                    
        #Go a DFS on all neighboring cities
        provinces = 0
        for city in range(len(A)):
            #We haven't visited the city 
            if city not in seen_cities:
                # seen_cities.add(city)
                find_cities(city)
                #After the above DFS I would have found all neighboring cities and increase it's province by 1 
                #Then go onto the next one's 
                provinces += 1 
            
        return provinces

Problem 841问题 841

class Solution:
    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:

        #canvisitallrooms
        #pos means you can visit such rooms 
        
        #This one is directed so u needa add it ahead of time 
        total_rooms = []
        #When adding to the stack we needa add to seen as well 
        stack = [0]
        seen_rooms = set()
        seen_rooms.add(0)
        
        #We never necessairly mentioned room 0 so we need to add room 0 since it STARTS there as well compared to another prob like 547
       #[[1],[2],[3],[]]
        while stack:
            cur_room = stack.pop()
            nei_rooms = rooms[cur_room]
            for nei_room in nei_rooms:
                if nei_room not in seen_rooms:
                    seen_rooms.add(nei_room)
                    stack.append(nei_room)

        return len(seen_rooms) == len(rooms)
    

Is the reason why it can be done like this for an undirected graph, ie not having to add the positions to the seen early on like I stated above, is that because it's undirected, we'll visit such path again and can add it to the seen set to prevent us from seeing it again?对于无向图可以这样做的原因,即不必像我上面所说的那样将位置添加到早期看到的位置,因为它是无向的,我们将再次访问这样的路径并将其添加到看到的设置阻止我们再次看到它? Whereas in a directed graph like keys and rooms, we won't every "visit" room 0 again potentially?而在像钥匙和房间这样的有向图中,我们不会潜在地再次“访问”房间 0 吗?

The reason for the visited set is essentially cycle avoidance, which is an issue in both directed and undirected graphs.访问集的原因本质上是避免循环,这在有向图和无向图中都是一个问题。 Undirected graphs are simply a special case of directed graphs where every edge from A to B has an opposite edge from B to A , so you can have cycles.无向图只是有向图的一种特殊情况,其中从AB的每条边都有从BA的对边,因此您可以有循环。 In "provinces", the adjacency matrix is defined with self-cycles for every node.在“省”中,邻接矩阵由每个节点的自循环定义。

Why is it that sometimes you initialize a visited set ahead of time and sometimes later?为什么有时您会提前初始化访问集,有时会延迟? It has nothing to do with directedness;它与定向无关; it's mainly an implementation detail having to do with where you make checks for terminal states.它主要是一个实现细节,与您检查终端状态的位置有关。 For example, on "keys and rooms", both of the below solutions are valid.例如,在“钥匙和房间”上,以下两种解决方案都有效。 As long as prior visitation is tested before exploring and nothing is accidentally marked visited before pushing its neighbors, it's pretty much the same.只要在探索之前测试了先前的访问,并且在推送其邻居之前没有意外标记为已访问,它几乎是一样的。

def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
    visited = set([0]) # <--
    stack = [0]
    
    while stack:
        for key in rooms[stack.pop()]:
            if key not in visited:
                visited.add(key)
                stack.append(key)
                
    return len(visited) == len(rooms)
def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
    visited = set()
    stack = [0]
    
    while stack:
        i = stack.pop()
        visited.add(i) # <--
        
        for key in rooms[i]:
            if key not in visited:
                stack.append(key)
                
    return len(visited) == len(rooms)

On "provinces" it's the same story--you can move the checks and set insertions around as long as you've made sure to explore everything once.在“省”上情况相同——只要您确保对所有内容进行一次探索,您就可以移动检查并设置插入。

For example, my solution below (which I wrote without looking at your code) performs the visited check and marks the root node visited one time at the start of the recursive call.例如,我下面的解决方案(我没有查看您的代码就编写了该解决方案)执行访问检查并在递归调用开始时标记一次访问的根节点。 Your version makes two checks, one in the main loop iterating over all nodes and a second time inside the neighbor loop.您的版本进行了两次检查,一次在主循环中迭代所有节点,第二次在邻居循环内。

If you really wanted to, you could go a step further and "prime" the visited set in the caller before the recursive call, but it's not a particularly elegant approach because it spreads more of the recursive logic into two places.如果你真的想要,你可以更进一步 go 并在递归调用之前“准备”调用者中的访问集,但这不是一种特别优雅的方法,因为它将更多的递归逻辑传播到两个地方。

def findCircleNum(self, adj_mat: List[List[int]]) -> int:
    visited = set()
    
    def explore(i):
        if i not in visited:
            visited.add(i)
        
            for j, x in enumerate(adj_mat[i]):
                if x:
                    explore(j)
            
            return True
        
    return len([i for i, _ in enumerate(adj_mat) if explore(i)])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM