【问题标题】:Representing graphs (data structure) in Python用 Python 表示图(数据结构)
【发布时间】:2013-10-28 15:31:51
【问题描述】:

如何在Python 中巧妙地表示graph? (从头开始,即没有库!)
什么数据结构(例如 dicts/tuples/dict(tuples))既快又节省内存?
必须能够在上面做各种图形operations .

正如所指出的,各种graph representations 可能会有所帮助。如何在 Python 中实现它们?

至于库,this question 有很好的答案。

【问题讨论】:

  • 要实现 Graph,请查看 Wikipedia 文章,该文章列出了常见的实现及其在内存和速度方面的效率:en.wikipedia.org/wiki/…
  • 你可以试试 GitHub.com/thePastor/pangaia。使用标准库的 defaultdict 需要稍微重写(在编写代码时它还没有出现)。它使用递归数据结构使其比其他实现更优雅。
  • 对于有向图,这个essay from python.org 建议dictlists。基本上类似于{<parent>: [<child>, ...], ...}
  • 您可以使用字典作为邻接列表,将键作为节点,将值作为每个键的相邻节点列表。

标签: python data-structures graph


【解决方案1】:

NetworkX 是一个很棒的 Python 图形库。你会很难找到你需要但它还没有做的东西。

而且它是开源的,因此您可以看到他们如何实现他们的算法。您还可以添加其他算法。

https://github.com/networkx/networkx/tree/master/networkx/algorithms

【讨论】:

  • 这就是为什么 NetworkX 是一个很棒的资源。它是开源的,所以你可以看到他们是如何实现他们的算法的。您还可以添加其他算法。
  • graph.py --> class Graph 的代码大约有 2000 行。我只想看看他们如何使用__iter__
【解决方案2】:

首先,经典 listmatrix 表示的选择取决于目的(取决于您想对表示做什么)。众所周知的问题和算法都与选择有关。抽象表示类型的选择决定了它应该如何实现。

其次,问题是顶点和边是否应该仅根据存在来表示,或者它们是否携带一些额外的信息。

从 Python 内置数据类型的角度来看,其他地方包含的任何值都表示为对目标对象的(隐藏)引用。如果它是一个变量(即命名引用),那么名称和引用总是存储在(内部)字典中。如果您不需要名称,则可以将引用存储在您自己的容器中——这里可能 Python 列表 将始终用于 list 作为抽象。

Python list 实现为引用的动态数组,Python tuple 实现为具有恒定内容的静态引用数组(引用的值不能更改)。因此,它们可以很容易地被索引。这样,列表也可以用于矩阵的实现。

表示矩阵的另一种方式是由标准模块array 实现的数组——在存储类型、同质值方面更受限制。元素直接存储值。 (该列表存储对值对象的引用)。这样,内存效率更高,访问值也更快。

有时,您可能会发现有用的更受限制的表示形式,例如 bytearray

【讨论】:

    【解决方案3】:

    尽管这是一个有点老的问题,但我想我会为遇到此问题的任何人提供一个实用的答案。

    假设您将连接的输入数据作为元组列表获取,如下所示:

    [('A', 'B'), ('B', 'C'), ('B', 'D'), ('C', 'D'), ('E', 'F'), ('F', 'C')]
    

    我发现对 Python 中的图最有用和最有效的数据结构是 集合字典。这将是我们Graph 类的底层结构。您还必须知道这些连接是弧(有向,单向连接)还是边(无向,双向连接)。我们将通过将directed 参数添加到Graph.__init__ 方法来处理这个问题。我们还将添加一些其他有用的方法。

    import pprint
    from collections import defaultdict
    
    
    class Graph(object):
        """ Graph data structure, undirected by default. """
    
        def __init__(self, connections, directed=False):
            self._graph = defaultdict(set)
            self._directed = directed
            self.add_connections(connections)
    
        def add_connections(self, connections):
            """ Add connections (list of tuple pairs) to graph """
    
            for node1, node2 in connections:
                self.add(node1, node2)
    
        def add(self, node1, node2):
            """ Add connection between node1 and node2 """
    
            self._graph[node1].add(node2)
            if not self._directed:
                self._graph[node2].add(node1)
    
        def remove(self, node):
            """ Remove all references to node """
    
            for n, cxns in self._graph.items():  # python3: items(); python2: iteritems()
                try:
                    cxns.remove(node)
                except KeyError:
                    pass
            try:
                del self._graph[node]
            except KeyError:
                pass
    
        def is_connected(self, node1, node2):
            """ Is node1 directly connected to node2 """
    
            return node1 in self._graph and node2 in self._graph[node1]
    
        def find_path(self, node1, node2, path=[]):
            """ Find any path between node1 and node2 (may not be shortest) """
    
            path = path + [node1]
            if node1 == node2:
                return path
            if node1 not in self._graph:
                return None
            for node in self._graph[node1]:
                if node not in path:
                    new_path = self.find_path(node, node2, path)
                    if new_path:
                        return new_path
            return None
    
        def __str__(self):
            return '{}({})'.format(self.__class__.__name__, dict(self._graph))
    

    我将把它作为“读者练习”来创建find_shortest_path 和其他方法。

    让我们看看实际情况......

    >>> connections = [('A', 'B'), ('B', 'C'), ('B', 'D'),
                       ('C', 'D'), ('E', 'F'), ('F', 'C')]
    >>> g = Graph(connections, directed=True)
    >>> pretty_print = pprint.PrettyPrinter()
    >>> pretty_print.pprint(g._graph)
    {'A': {'B'},
     'B': {'D', 'C'},
     'C': {'D'},
     'E': {'F'},
     'F': {'C'}}
    
    >>> g = Graph(connections)  # undirected
    >>> pretty_print = pprint.PrettyPrinter()
    >>> pretty_print.pprint(g._graph)
    {'A': {'B'},
     'B': {'D', 'A', 'C'},
     'C': {'D', 'F', 'B'},
     'D': {'C', 'B'},
     'E': {'F'},
     'F': {'E', 'C'}}
    
    >>> g.add('E', 'D')
    >>> pretty_print.pprint(g._graph)
    {'A': {'B'},
     'B': {'D', 'A', 'C'},
     'C': {'D', 'F', 'B'},
     'D': {'C', 'E', 'B'},
     'E': {'D', 'F'},
     'F': {'E', 'C'}}
    
    >>> g.remove('A')
    >>> pretty_print.pprint(g._graph)
    {'B': {'D', 'C'},
     'C': {'D', 'F', 'B'},
     'D': {'C', 'E', 'B'},
     'E': {'D', 'F'},
     'F': {'E', 'C'}}
    
    >>> g.add('G', 'B')
    >>> pretty_print.pprint(g._graph)
    {'B': {'D', 'G', 'C'},
     'C': {'D', 'F', 'B'},
     'D': {'C', 'E', 'B'},
     'E': {'D', 'F'},
     'F': {'E', 'C'},
     'G': {'B'}}
    
    >>> g.find_path('G', 'E')
    ['G', 'B', 'D', 'C', 'F', 'E']
    

    【讨论】:

    • 尽管这个问题已经很老了,但我想这正是我当时所期待的答案。该示例确实有助于解释如何在保持其非常简单的同时进行实施。可以从不同的开源库中找到实现,但解释并不相同。谢谢!
    • 需要什么样的修改来增加边缘的权重?
    • @pshirishreddy 有趣的问题!我没有想到这一点,但我的直觉是使用heapq lib 来堆积元组列表而不是集合。例如,该图将是堆的字典,例如:_graph = {'A': heapify([(0.3, 'D'), (0.5, 'B'), (0.75, 'A'), (0.9, 'C')])}(注意:您实际上不会像这样使用heapify,请阅读库的帮助),然后您可以使用heapq 函数插入和得到加权边缘。
    • @mVChr 这意味着log 时间访问。但是如何扩展你用来映射 nodeID 和 weight 的字典呢?
    • 您好,可以将此数据结构称为邻接表实现吗?
    【解决方案4】:

    有两个优秀的图形库 NetworkXigraph。你可以在 GitHub 上找到这两个库的源代码。您总是可以看到函数是如何编写的。但我更喜欢 NetworkX,因为它易于理解。
    查看他们的代码以了解他们如何实现这些功能。您将获得多种想法,然后可以选择使用数据结构制作图表的方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-19
      • 2012-03-20
      • 2012-09-03
      • 1970-01-01
      相关资源
      最近更新 更多