【问题标题】:Why are graphs represented using dictionaries in Python? [closed]为什么在 Python 中使用字典表示图形? [关闭]
【发布时间】:2015-10-06 15:35:46
【问题描述】:

Python 不直接支持图形,但很多消息来源说它们可以使用字典来表示,例如。

graph = { "a" : ["c"],
          "b" : ["c", "e"],
          "c" : ["a", "b", "d", "e"],
          "d" : ["c"],
          "e" : ["c", "b"],
          "f" : []
        }

由于这是一个无向图,而字典是有向映射,看起来真的很不简洁。说 graph = {'x':['y'], 'y':['x']} 而不是 graph = {{'x', 'y'}} 真的更好吗?

【问题讨论】:

  • @EXO,它似乎适合邻接表的模型
  • 许多图算法涉及扫描从给定节点辐射的边集。即使以一定数量的冗余为代价,拥有一种可以简化此操作的表示也是非常有用的。
  • 想想你用图表做什么。你从一个节点开始,你想知道你可以从它到达哪些节点。你会如何用你的符号来做到这一点?您必须搜索元组列表,查找包含起始节点的任何对。
  • 无向字典几乎肯定必须在内部实现为您问题中的字典。每次添加a<=>b 条目时,它实际上会将b 推送到a=>[] 列表并将a 推送到b=>[] 列表。否则,您如何使ab 的查找都高效?
  • 因此,如果这是您想要的,您可以轻松编写一个类来封装它。

标签: python math dictionary graph set


【解决方案1】:

将它们存储为连接使行走变得非常容易:

vertex = 'x'
connected_to = graph[vertex]
second_degree_connections = {p for subset in graph[p] for p in connected_to}

尝试使用一组二元组有效地做到这一点。不是很容易,对吧?

【讨论】:

    【解决方案2】:

    如果图是无向的,一组2元素集更适合表示它:

    graph = set()
    

    添加边:

    >>> graph.add(frozenset(('a', 'b')))
    

    (注意:你必须使用不可变的 freezeset(),因为 set() 是不可散列的,因为它的可变性)

    检查一条边是否在图中:

    >>> {'b', 'a'} in graph
    True
    

    添加更多边:

    >>> graph.add(frozenset(('a', 'c')))
    >>> graph.add(frozenset(('c', 'd')))
    >>> graph.add(frozenset(('d', 'e')))
    >>> graph.add(frozenset(('d', 'f')))
    

    让边缘接触“d”:

    >>> set(x for x in graph if 'd' in x)
    {frozenset({'c', 'd'}), frozenset({'f', 'd'}), frozenset({'d', 'e'})}
    

    但是,对于最后一个操作,使用字典的表示在时间方面更有效。

    【讨论】:

    • 不错的答案(当然不值得投反对票),但在许多方面,您的最后一句话与您的第一句话相矛盾。除非图表非常大以至于内存成为问题,否则有序对而不是 2 元素集更有用。
    • 我看不出矛盾。但是,是的,我不太推荐我的这个答案。在大多数情况下,使用双字典的方法更好。我会在另一个答案中添加它。
    • 我对此表示赞同,因为它确实回答了这个问题,但我同意@JohnColeman。
    • @mescalinum 它是(部分)矛盾的意义是第一句是不合格的,但最后一句给出了一个限定(在许多应用程序中——尤其是那些 Python 适合的应用程序)破坏第一个。尽管如此,OP 似乎对内存使用比速度更重要的应用程序感兴趣,所以我也会赞成它。
    • 图的“好”实现高度依赖于预期用途:邻接矩阵、关联矩阵、邻接列表、关联列表等。请参阅Wikipedia
    【解决方案3】:

    无向图是一种特定类型的有向图,其中每条边 (a, b) 也存在 (b, a)。

    所以你可以实现它两次存储边(正常和反向),但最好写一个类来保持一致:

    from collections import defaultdict
    
    class UndirectedGraph:
        def __init__(self):
            self.edge = defaultdict(set)
    
        def add_edge(self, a, b):
            self.edge[a].add(b)
            self.edge[b].add(a)
    
        def remove_edge(self, a, b):
            self.edge[a].remove(b)
            self.edge[b].remove(a)
    
        def has_edge(self, a, b):
            return b in self.edge[a] or a in self.edge[b]
    
        def neighbours(self, a):
            return set(self.edge[a])
    

    这是非常基本的,根据您要执行的操作,您可能会删除某些方法,也可能需要添加其他方法。

    >>> g=UndirectedGraph()
    >>> g.add_edge('a','b')
    >>> g.add_edge('a','c')
    >>> g.add_edge('c','d')
    >>> g.add_edge('a','d')
    >>> g.remove_edge('a','d')
    >>> g.has_edge('a','d')
    False
    >>> g.has_edge('a','c')
    True
    >>> g.neighbours('c')
    {'a', 'd'}
    

    【讨论】:

    • 是的,也许您确实可以使用两条有向边来表示“无向边”,但关键是要使其高效且不占用不必要的内存和时间。
    • 有一个折衷:要么你选择存储两倍的边或等效地有两个索引来访问 O(1) 上的顶点邻居,或者你是内存高效但速度较慢,即 O(n)
    • @JohnColeman,那么是否存在双向或无向字典?如果是这样,那将非常有帮助。然后你可以将它表示为 graph = {'y'{'x', 'z'}} 或者 graph = {{'y'}{'x', 'z'}} 我不是当然——EXO 12 分钟前@JohnColeman,图的图/图也将包含数百万到数十亿个节点,甚至不包括边,我的硬盘只有 1.5 TB,因此尽可能简洁和压缩构建它至关重要。 – EXO 4 分钟前
    • 如果有一个无向字典,那么你将如何权衡?我觉得这应该是可能的。
    • 什么是“无向字典”?
    猜你喜欢
    • 2021-11-11
    • 2016-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-08-17
    • 1970-01-01
    • 2021-12-28
    • 2013-09-15
    • 1970-01-01
    相关资源
    最近更新 更多