在Python中表示图(数据结构)


104

如何用Python巧妙地表示图形?(从头开始,即没有库!)哪种数据结构(例如dicts / tuples / dict(tuples))既快速又具有存储效率?必须能够对它执行各种图形操作。 如前所述,各种图形表示可能会有所帮助。如何在Python中实现它们?至于图书馆,这个问题有很好的答案。







1
用于实现图形看维基百科的文章,列出常见的实现及其在内存和速度效率:en.wikipedia.org/wiki/...
Kassym Dorsel

您可以尝试GitHub.com/thePastor/pangaia。要使用标准库的defaultdict(编写代码时还没有出来),需要进行一些重写。它使用递归数据结构使其比其他实现更优雅。
医生

1
对于定向图表,这从python.org文章提出了一种dictlist秒。基本上像{<parent>: [<child>, ...], ...}
djvg

您可以实现使用字典作为邻接表,其中键作为节点,值作为每个键的相邻节点的列表。
沙鲁克汗

Answers:


139

即使这是一个有点老的问题,我还是想为遇到问题的任何人提供一个切实可行的答案。

假设您以元组列表的形式获取连接的输入数据,如下所示:

[('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']

6
即使这个问题很老,我认为这正是我当时所期望的答案。该示例确实有助于说明如何使实施同时保持非常简单。可以从不同的开放源代码库中找到实现,但是解释并不能相提并论。谢谢!
shad0w_wa1k3r 2015年

2
需要什么样的修改才能增加边缘的重量?
pshirishreddy

3
@pshirishreddy有趣的问题!我没有考虑过,但是我的直觉是使用heapqlib堆化元组列表而不是集合。例如,该图将是一个堆的字典,例如:(_graph = {'A': heapify([(0.3, 'D'), (0.5, 'B'), (0.75, 'A'), (0.9, 'C')])}注意:您实际上不会这样使用heapify,请阅读lib的帮助),然后可以使用heapq函数插入并获取加权边。
mVChr 2015年

@mVChr表示需要log时间访问。但是,如何扩展用于映射nodeID和weight的字典?
orezvani

真好!函数被递归调用。这似乎是一个DFS,因为它不断扩展节点。对于最短的路径,我们可以比较路径的长度,最后只返回最短的路径。
Jwalant Bhatt

36

NetworkX是一个很棒的Python图形库。您将很难找到尚未需要的东西。

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

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


7
这就是NetworkX是绝佳资源的原因。它是开源的,因此您可以了解他们如何实现算法。您还可以添加其他算法。
jterrace

2
大约2000行代码graph.py --> class Graph。我只想看看它们的用法__iter__
T.Woody

8

首先,经典列表矩阵表示形式的选择取决于目的(取决于您要如何使用表示形式)。众所周知的问题和算法与选择有关。对抽象表示类型的选择决定了应如何实现它。

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

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

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

表示矩阵的另一种方法是由标准模块实现的数组array-相对于存储的类型(均值)更受限制。元素直接存储值。(该列表将存储对值对象的引用)。这样,它可以提高内存效率,并且对值的访问也更快。

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


7

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.