基于networkx中的边权重排序的邻居边(Python)

时间:2015-06-15 04:20:39

标签: python graph networkx

我已经建立了一个基于networkx的图形,其中边缘代表它们之间的距离。

  GraphLoc.G.add_edge(fnode_id, snode_id, score=score)

得分是边缘权重。

我无法找到可以提供边缘的相邻节点的API,结果按重量排序。

显然我也可以自己排序,但我不想要运行时计算。 networkx也为此提供了任何解决方案

2 个答案:

答案 0 :(得分:3)

选择单个节点将返回其邻居。然后,您可以相当轻松地对边缘列表进行排序。首先,我们设置图表。

>>> G = nx.Graph()
>>> G.add_edge('a', 'b', score=3)
>>> G.add_edge('b', 'c', score=4)
>>> G.add_edge('a', 'c', score=1)

如果我们想要a的邻居,我们只是直接访问该节点:

>>> G['a']
{'b': {'score': 3}, 'c': {'score': 1}}

要对这些结果进行排序,我们使用标准Python工具箱中的工具。 .items()要将dict转换为tuplesorted内置来对结果进行排序:

>>> sorted(G['a'].items(), key=lambda edge: edge[1]['score'])
[('c', {'score': 1}), ('b', {'score': 3})]

如果您需要明确地将结果与原始节点之间的关系进行显式化,则可以使用列表推导将其包含在结果中:

>>> neighbors = sorted(G['a'].items(), key=lambda edge: edge[1]['score'])
>>> [('a', node) for node, _metadata in neighbors]
[('a', 'c'), ('a', 'b')] 

答案 1 :(得分:1)

首先,您应该进行一些测试,并确保按分数对节点的邻居进行排序是代码中的瓶颈。在由典型数据源产生的许多图中,节点不可能具有许多邻居。在这种情况下,按分数对邻居进行排序非常便宜,并且不值得预先计算。

如果您因某种原因决定这是不够的,并且您必须预先分配节点,我认为您最好的选择是继承networkx.Graph并为其提供自己的映射类型图的内部表示。请注意,这仅在networkx开发版本中记录,因此此功能尚未保证可用或稳定。

有两种方法,第二种方法更通用但更复杂。在第一种情况下,我假设您可以控制向图表添加边缘的顺序,并且您可以一次性添加边缘。在第二种情况下,我没有做出这样的假设,但代码会更复杂。

案例1:边缘按排序顺序插入

在这种情况下,我假设您可以按分数增加的顺序插入边。所有需要做的就是为子类提供一个工厂,用于邻接映射,它保留了边的顺序。 collections.OrderedDict会这样做:

import networkx as nx
import collections

class OrderedGraph(nx.Graph):
    adjlist_dict_factory = collections.OrderedDict

g = OrderedGraph()
g.add_edge(1, 3, score=17)
g.add_edge(1, 2, score=42)
g.add_edge(1, 4, score=55)

请注意,根据分数,边缘按升序添加!现在,如果我们写:

>>> g.neighbors(1)
[3, 2, 4]

根据需要。但请注意,如果我们稍后添加边缘,则必须具有比其任何相邻边缘更大的分数,以使分类的分数不变,以保持不间断。也就是说,您无法运行上述代码,然后执行此操作:

>>> g.add_edge(1, 5, score=1)

并希望它能够发挥作用。你会得到:

>>> g.neighbors(1)
[3, 2, 4, 5]

需要[5, 3, 2, 4]的地方。如果您不能一次添加所有边,那么,并且不能保证它们只按排序顺序插入,那么您需要更通用的实现,例如下一种情况。

案例2:以任意顺序插入边缘

在这种情况下,我们需要一个类似于映射的类,但它按照其得分的递增顺序跟踪插入的节点。为此,我们将一个堆与一个继承自collections.abc.MutableMapping的类中的字典一起使用。堆将以额外内存为代价保持节点的分数顺序。

以下实施非常粗糙,因此请谨慎使用:

import heapq

class SortedScoreMap(collections.abc.MutableMapping):

    def __init__(self):
        self._data = {}
        self._heap = []

    def __getitem__(self, key):
        return self._data[key]

    def __setitem__(self, key, value):
        if key in self._data:
            self._heap_remove_key(key)
        self._data[key] = value
        heapq.heappush(self._heap, (value['score'], key))

    def __delitem__(self, key):
        del self._data[key]
        self._heap_remove_key(key)

    def __iter__(self):
        yield from (key for (score, key) in self._heap)

    def __len__(self):
        return len(self._data)

    def _heap_remove_key(self, key_to_remove):
        entries = []
        for score, key in self._heap:
            if key == key_to_remove:
                entries.append((score, key))

        for entry in entries:
            self._heap.remove(entry)

SortedScoreMap期望其值为带有score键的字典。为了空间的利益,上述代码并未执行此操作。这是一个演示:

>>> sm = SortedScoreMap()
>>> sm[5] = {'score': 17}
>>> sm[7] = {'score': 2}
>>> sm[1] = {'score': 42}
>>> list(sm.keys())
[7, 5, 1]
>>> sm[7] = 99
[5, 1, 7]

正如您所看到的,这会使得按键按顺序排列。现在我们将它用作邻接映射工厂:

import networkx as nx
import collections

class OrderedGraph(nx.Graph):
    adjlist_dict_factory = SortedScoreMap

g = OrderedGraph()
g.add_edge(1, 3, score=17)
g.add_edge(1, 2, score=42)
g.add_edge(1, 4, score=55)

现在,如果我们写:

>>> g.neighbors(1)
[3, 2, 4]

如预期的那样,我们甚至可以这样做:

>>> g.add_edge(1, 5, score=1)
>>> g.neighbors(1)
[5, 3, 4, 2]

它有效!

现在,这需要额外的内存(每个密钥有效复制)和计算,因为需要一个额外的间接层。根据问题的大小,您可能会发现每次需要时对邻居进行排序实际上比这种方法更快。正如我在开头所说的那样:分析你的代码,找出实际的瓶颈是什么,只有然后实现改进。