Представлення графіків (структури даних) в Python


105

Як можна акуратно представити графік на Python ? (Починаючи з нуля, тобто немає бібліотек!)
Яка структура даних (наприклад, диктовки / кортежі / диктат (кортежі)) буде швидкою, але також ефективною пам'яттю?
Потрібно вміти робити різні графічні операції на ньому.

Як зазначалося, різні графічні зображення можуть допомогти. Як можна реалізувати їх у Python?

Щодо бібліотек, то на це питання є досить хороші відповіді.



1
Для втілення графіку дивіться статтю у Вікіпедії, в якій перераховані загальні реалізації та їх ефективність як у пам’яті, так і у швидкості: en.wikipedia.org/wiki/…
Кассім Дорсел

Ви можете спробувати GitHub.com/thePastor/pangaia. Щоб скористатися типовим рішенням бібліотеки за замовчуванням (якого не було, коли написано код), потрібно трохи переписати його. Він використовує рекурсивну структуру даних, щоб зробити її більш елегантною, ніж інші реалізації.
TheDoctor

1
Для спрямованих графів, це есе з python.org пропонує dictз listс. В основному щось на кшталт {<parent>: [<child>, ...], ...}.
djvg

Ви можете реалізувати використання словника як списку суміжності з ключами як вузлами та значеннями як списку суміжних вузлів для кожного ключа.
Шахрух хан

Відповіді:


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 Цікаве запитання! Я не думав про це, але мій інстинкт heapqполягав би в тому, щоб використовувати lib для складання списків кортежів замість наборів. Наприклад, графік буде накресленням наборів на зразок: _graph = {'A': heapify([(0.3, 'D'), (0.5, 'B'), (0.75, 'A'), (0.9, 'C')])}(зверніть увагу: ви насправді не використовуєте heapifyподібне, читайте довідку для lib), тоді ви можете використовувати heapqфункції для вставки та отримання зважених країв.
mVChr

@mVChr, що означатиме logдоступ до часу. Але як розширити словник, який ви використовували для відображення і nodeID, і ваги?
орезвані

Приємно! Функція називається рекурсивно. Це, здається, 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

Є дві чудові бібліотеки графіків NetworkX та igraph . Обидва вихідні коди бібліотеки можна знайти на GitHub. Ви завжди можете бачити, як записані функції. Але я віддаю перевагу NetworkX, тому що його легко зрозуміти.
Перегляньте їх коди, щоб знати, як вони роблять функції. Ви отримаєте кілька ідей, а потім зможете вибрати, як ви хочете скласти графік, використовуючи структури даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.