Pythonでのグラフ(データ構造)の表現


105

どうすればPythonでグラフをきれいに表現できますか?(最初から、つまりライブラリがない場合!)どのデータ構造(たとえば、dicts / tuples / dict(tuples))が高速で、メモリ効率も良いでしょうか?さまざまなグラフ操作を実行できる必要があります。 指摘したように、さまざまなグラフ表現が役立つ場合があります。それらをPythonに実装するにはどうすればよいですか?ライブラリに関しては、この質問はかなり良い答えを持っています。






1
そこすでにそこにライブラリがたくさん出:graph-tool.skewed.de/performancecode.google.com/p/python-graphnetworkx.github.io
Kassym Dorsel

1
:ウィキペディアの記事一覧一般的な実装とメモリと速度の両方での効率のグラフを見て実装するためのen.wikipedia.org/wiki/...
Kassym Dorsel

GitHub.com/thePastor/pangaiaを試してみてください。標準ライブラリのdefaultdictを使用するために少し書き直す必要があります(コードが書かれたときに出ていませんでした)。再帰的なデータ構造を使用して、他の実装よりもエレガントにしています。
theDoctor 2018年

1
以下のために向かうグラフ、このpython.orgからエッセイを示唆するdictlist秒。基本的にのようなもの{<parent>: [<child>, ...], ...}
djvg

ノードとしてのキーと各キーの隣接ノードのリストとしての値を持つ隣接リストとして辞書を使用して実装できます。
Shahrukh khan

回答:


140

これはやや古い質問ですが、これに遭遇した人には実用的な答えを出そうと思いました。

接続の入力データを次のようなタプルのリストとして取得するとします。

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

@mVChrはlog時間アクセスを意味します。しかし、nodeIDとweightの両方をマップするために使用した辞書を拡張する方法は?
orezvani

いいね!関数は再帰的に呼び出されます。ノードを拡張し続けるため、これはDFSのようです。最短経路については、経路の長さを比較して、最短経路のみを最後に返すことができます。
Jwalant Bhatt 2017

36

NetworkXは素晴らしいPythonグラフライブラリです。あなたはそれがまだしていないあなたが必要とする何かを見つけるのに苦労するでしょう。

そしてそれはオープンソースなので、彼らがアルゴリズムをどのように実装したかを見ることができます。アルゴリズムを追加することもできます。

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


7
NetworkXが素晴らしいリソースであるのはそのためです。オープンソースなので、アルゴリズムがどのように実装されているかを確認できます。アルゴリズムを追加することもできます。
jterrace 2013年

2
の約2000行のコードgraph.py --> class Graph。そして、私が見たいのは、彼らの使い方__iter__です。
T.Woody 2018年

8

まず、古典的なリスト行列表現のどちらを選択するかは、目的(表現をどのように処理するか)によって異なります。よく知られている問題とアルゴリズムは、選択に関連しています。抽象表現の種類の選択によって、実装方法が決まります。

第二に、問題は、頂点とエッジが存在の観点からのみ表現されるべきか、それともそれらが何らかの追加情報を運ぶかどうかです。

Pythonの組み込みデータ型の視点から、他の場所に含まれる値はすべて、ターゲットオブジェクトへの(非表示の)参照として表されます。変数(つまり、名前付き参照)の場合、名前と参照は常に(内部)辞書に格納されます。名前が不要な場合は、参照を独自のコンテナに保存できます。ここでは、リストとして抽象化として常にPythonリストが使用されます。

Pythonリストは参照の動的配列として実装され、Pythonタプルは一定の内容を持つ参照の静的配列として実装されます(参照の値は変更できません)。そのため、簡単にインデックスを付けることができます。このように、リストは行列の実装にも使用できます。

行列を表現するもう1つの方法は、標準モジュールによって実装される配列arrayです。これは、保存された型、同種の値に関してより制約されます。要素は値を直接格納します。(リストには、代わりに値オブジェクトへの参照が格納されます)。これにより、メモリ効率が向上し、値へのアクセスも高速になります。

ときどき、のようなさらに限定された表現が役立つことがありますbytearray


7

NetworkXigraphの 2つの優れたグラフライブラリがあります 。GitHubで両方のライブラリソースコードを見つけることができます。関数がどのように記述されているかを常に確認できます。しかし、私は理解しやすいのでNetworkXを好みます。
彼らがどのように機能するかを知るために彼らのコードを見てください。複数のアイデアが得られ、データ構造を使用してグラフを作成する方法を選択できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.