Python에서 그래프 (데이터 구조) 표현


105

파이썬 에서 그래프 를 어떻게 깔끔하게 표현할 수 있습니까? (처음부터 시작, 즉 라이브러리가 없습니다!) 어떤 데이터 구조 (예 : dicts / tuples / dict (tuples))가 빠르지 만 메모리 효율적일까요? 다양한 그래프 작업 을 수행 할 수 있어야 합니다. 지적했듯이 다양한 그래프 표현 이 도움이 될 수 있습니다. 파이썬으로 구현하는 방법은 무엇입니까? 도서관에 관해서는 이 질문 에 꽤 좋은 답변이 있습니다.







1
그래프를 구현하려면 메모리와 속도 모두에서 일반적인 구현과 효율성을 나열하는 Wikipedia 기사를 참조하십시오. en.wikipedia.org/wiki/…
Kassym Dorsel

GitHub.com/thePastor/pangaia를 사용해 볼 수 있습니다. 표준 라이브러리의 defaultdict를 사용하려면 약간의 재 작성이 필요합니다. 재귀 데이터 구조를 사용하여 다른 구현보다 더 우아하게 만듭니다.
theDoctor

1
방향 그래프의 경우 python.org 의이 에세이 는 a dictof lists를 제안합니다 . 기본적으로 {<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

2
가장자리에 가중치를 추가하려면 어떤 종류의 수정이 필요합니까?
pshirishreddy

3
@pshirishreddy 흥미로운 질문입니다! 나는 그것에 대해 생각하지 않았지만 내 본능은 heapqlib를 사용하여 세트 대신 튜플 목록을 힙 화하는 것입니다. 예를 들어 그래프는 같은 힙의 DICT 될 것이다 : _graph = {'A': heapify([(0.3, 'D'), (0.5, 'B'), (0.75, 'A'), (0.9, 'C')])}(참고 : 당신은 실제로 사용하지 것이다 heapifylib 디렉토리에 대한 도움말을 읽고,이 같은) 당신이 사용할 수있는, heapq가중 가장자리를 삽입하고 얻을 기능.
mVChr

@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를 선호합니다.
그들이 기능을 만드는 방법을 알기 위해 코드를 참조하십시오. 여러 아이디어를 얻은 다음 데이터 구조를 사용하여 그래프를 만드는 방법을 선택할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.