Представлення графіків (структури даних) в 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.