python 实现有向图和相关的DFS,循环检测算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python 实现有向图和相关的DFS,循环检测算法相关的知识,希望对你有一定的参考价值。


class DiGraph(object):
    def __init__(self):
        super(DiGraph, self).__init__()

        self.g = dict()

    def add_node_and_neighbors(self, node, neighbors):
        assert node
        assert isinstance(node, basestring)

        self.g[node] = set(neighbors)

    def add_edge(self, from_table_name, to_table_name):

        assert isinstance(from_table_name, basestring)
        assert isinstance(to_table_name, basestring)

        if from_table_name in self.g:
            self.g[from_table_name].add(to_table_name)
        else:
            self.g[from_table_name] = set(to_table_name)

    def num_nodes(self):
        """
        Number of vertices (nodes)
        :return:
        :rtype:
        """
        return len(self.g)

    def num_edges(self):
        """

        :return: number of edges in this graph
        :rtype: int
        """
        count = 0
        for node in self.g:
            count += len(self.g[node])

        return count

    def degree(self, node):
        """
        :param node:
        :type node: str
        :return: number of adjacent (neighboring) vertices (nodes)
        :rtype: int
        """
        assert isinstance(node, basestring)

        if node not in self.g:
            raise Exception("Node %s not present in graph!" % node)

        return len(self.g[node])

    def has_self_loops(self):
        """
        A "self-loop" is a node that points to itself.
        Note that this isn't the same as detecting a 'cycle' in a graph.

        :return:
        :rtype: bool
        """
        for node in self.g:
            for neighbor in self.g[node]:
                if neighbor == node:
                    print("Node %s has a self-loop!" % node)
                    return node, True

        return None, False

    def neighbors(self, node):
        """

        :param node:
        :type node: basestring
        :return: set of neighboring nodes
        :rtype: frozenset
        """
        assert isinstance(node, basestring)

        if node not in self.g:
            raise Exception("Node %s not present in graph!" % node)

        return frozenset(self.g[node])

    def __str__(self):
        s = StringIO()
        pprint(self.g, s)
        return s.getvalue()

    def dependencies(self, start_node, resolved_deps):
        for neighbor in self.neighbors(start_node):
            if neighbor not in resolved_deps:
                self.dependencies(neighbor, resolved_deps)

        resolved_deps.add(start_node)

    def reverse(self):
        r = DiGraph()

        for n in self.g:
            for adj in self.neighbors(n):
                r.add_edge(adj, n)

        return r


class DirectedCycle(object):
    def __init__(self, di_graph):
        super(DirectedCycle, self).__init__()
        assert isinstance(di_graph, DiGraph)

        self.di_graph = di_graph

        # this is the set of nodes that have been visited.
        # Note that a node being in this set is not an indication of a cycle: a cycle is present only if
        # it is in the on_stack list
        self.marked = set()

        # This keeps track of the nodes that are in the path of the node where the dfs originally began.
        # i.e, when doing the recursive dfs, there is a cycle, if we encounter a node that was traversed as part of this dfs
        self.on_stack = list()

        # This is a list of nodes that form a cycle; this is the list used to construct the 'path' of the cycle.
        self.cycle = list()

        # This is an associative array mapping the child node to its parent node. In this class, this array is used to construct the path of the cycle.
        self.edge_to = dict()

        for i, node in enumerate(self.di_graph.g):
            if node not in self.marked:
                # print("Detecting cycles beginning with %s, iteration # %s " % (node, i))
                self.dfs(node)

    def dfs(self, node):

        # Mark that this node has been visited
        self.marked.add(node)

        # Mark that this node is part of a dfs traversal; not the pop at the end of the for loop, marking that the dfs traversal is done..
        self.on_stack.append(node)

        for neighbor_node in self.di_graph.neighbors(node):
            if len(self.cycle):
                # this is to break infinite looping?
                return
            elif neighbor_node not in self.marked:
                # we have reached an adjacent ("neighbor") node that we haven't visited; so mark the edgeTo[child-node] = parent-node and do a dfs on this node
                self.edge_to[neighbor_node] = node
                self.dfs(neighbor_node)
            elif neighbor_node in self.on_stack:
                # so : we have reached an adjacent node has been part of this dfs traversal call stack....so, there is a link from the current node - the one
                # pointed to by the variable 'node' to this 'neighbor_node'. This edge (node -> neighbor_node) forms the last link of the 'cycle'.
                # So, we traverse the edge_to associative array, till we reach the node pointed to by the current 'neighbor_node' variable (since that's the last link to the cycle)
                # and we add the last two nodes to the cycle list : the node pointed to by the variable 'node' and the node pointed to by the variable 'neighbor_node'.

                child_node = self.edge_to[node]
                while child_node != neighbor_node:
                    self.cycle.append(child_node)
                else:
                    self.cycle.append(node)
                    self.cycle.append(neighbor_node)

        # we are done with the for-loop : we did the entire dfs recursive search across the graph, and now, we take the node that started this dfs sub-search off the stack
        assert self.on_stack.pop() == node


    @property
    def path_str(self):
        return '->'.join(self.cycle)

    def has_cycle(self):
        return len(self.cycle) is not 0


class DepthFirstSearch(object):
    def __init__(self, graph, start_node):
        super(DepthFirstSearch, self).__init__()
        assert isinstance(graph, DiGraph)
        self.graph = graph
        self.count = 0
        self.marked = set()
        self.edge_to = dict()

        self.dfs(start_node)

    def dfs(self, node):
        assert isinstance(node, basestring)
        self.marked.add(node)

        for neighboring_node in self.graph.neighbors(node):
            if neighboring_node not in self.marked:
                # edge_to is an associative array, mapping child to parent.
                self.edge_to[neighboring_node] = node
                self.dfs(neighboring_node)

    def path_length(self):
        len(self.marked)

    def path(self):
        pass

    def has_path_to(self, node):
        """
        Checks if there is a path from start_node to the node passed as the parameter...

        :param node:
        :type node:
        :return:
        :rtype:
        """
        assert node
        assert isinstance(node, basestring)

        # self.marked is the set of nodes visited while doing a depth first search from 'from_node'.
        # if the passed 'node' is in this set
        return node in self.marked

以上是关于python 实现有向图和相关的DFS,循环检测算法的主要内容,如果未能解决你的问题,请参考以下文章

为啥用 DFS 而不是 BFS 在图中寻找循环

循环有向图和无向图

如何使用递归 DFS 查找图是不是包含循环?

使用 DFS 算法对有向图和无向图进行拓扑排序

undirected_dfs 是不是检测到图的所有循环?

05胡凡-10图算法