แสดงกราฟ (โครงสร้างข้อมูล) ใน Python


108

เราจะแสดงกราฟในPython ได้อย่างไร? (เริ่มตั้งแต่เริ่มต้นคือไม่มีไลบรารี!)
โครงสร้างข้อมูลอะไร (เช่น dicts / tuples / dict (tuples)) จะเร็ว แต่หน่วยความจำก็มีประสิทธิภาพด้วย?
เราต้องสามารถดำเนินการกราฟต่างๆได้

ตามที่ระบุไว้การแสดงกราฟต่างๆอาจช่วยได้ เราจะนำมันไปใช้ใน Python ได้อย่างไร?

สำหรับห้องสมุดคำถามนี้มีคำตอบที่ค่อนข้างดี


1
มีห้องสมุดมากมายอยู่แล้ว: graph-tool.skewed.de/performance , code.google.com/p/python-graph , networkx.github.io
Kassym Dorsel

1
สำหรับการใช้งาน Graph ให้ดูที่บทความ Wikipedia ซึ่งแสดงการใช้งานทั่วไปและประสิทธิภาพทั้งในหน่วยความจำและความเร็ว: en.wikipedia.org/wiki/…
Kassym Dorsel

คุณอาจลองใช้ GitHub.com/thePastor/pangaia ต้องเขียนใหม่เล็กน้อยเพื่อใช้ defaultdict ของไลบรารีมาตรฐาน (ซึ่งไม่ออกเมื่อเขียนโค้ด) ใช้โครงสร้างข้อมูลแบบวนซ้ำเพื่อให้สวยงามกว่าการใช้งานอื่น ๆ
theDoctor

1
สำหรับกราฟกำกับบทความนี้จาก python.orgแนะนำ a dictของ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))

ฉันจะปล่อยให้มันเป็น "แบบฝึกหัดสำหรับผู้อ่าน" เพื่อสร้าง a 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
แม้ว่าคำถามนี้จะเก่ามาก แต่ฉันคิดว่านี่เป็นคำตอบที่ฉันคาดหวังในตอนนั้น ตัวอย่างนี้ช่วยอธิบายได้จริง ๆ ว่าเราสามารถดำเนินการได้อย่างไรในเวลาเดียวกันทำให้ง่ายมาก สามารถค้นหาการใช้งานจากไลบรารีโอเพนซอร์สที่แตกต่างกันได้ แต่คำอธิบายจะไม่เท่ากัน ขอบคุณ!
shade0w_wa1k3r

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

37

NetworkXเป็นไลบรารีกราฟ Python ที่ยอดเยี่ยม คุณจะยากที่จะหาสิ่งที่คุณต้องการซึ่งยังไม่มี

และเป็นโอเพนซอร์สเพื่อให้คุณสามารถดูว่าพวกเขาใช้อัลกอริทึมของพวกเขาอย่างไร คุณยังสามารถเพิ่มอัลกอริทึมเพิ่มเติม

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


7
นั่นเป็นเหตุผลที่ NetworkX เป็นแหล่งข้อมูลที่ยอดเยี่ยม เป็นโอเพนซอร์สเพื่อให้คุณสามารถดูว่าพวกเขาใช้อัลกอริทึมอย่างไร คุณยังสามารถเพิ่มอัลกอริทึมเพิ่มเติม
jterrace

2
ประมาณ 2,000 บรรทัดของรหัสสำหรับgraph.py --> class Graph. __iter__และทั้งหมดที่ฉันต้องการที่จะเห็นเป็นวิธีการที่พวกเขาใช้
ทีวู้ดดี้

8

ขั้นแรกการเลือกรายการแบบคลาสสิกเทียบกับการแสดงเมทริกซ์ขึ้นอยู่กับวัตถุประสงค์ (คุณต้องการทำอะไรกับการเป็นตัวแทน) ปัญหาและอัลกอริทึมที่รู้จักกันดีเกี่ยวข้องกับทางเลือก การเลือกประเภทการแสดงนามธรรมเป็นตัวกำหนดว่าควรนำไปใช้อย่างไร

ประการที่สองคำถามคือควรแสดงจุดยอดและขอบในแง่ของการดำรงอยู่เท่านั้นหรือว่ามีข้อมูลเพิ่มเติมหรือไม่

จาก Python ในตัวชนิดข้อมูล point-of-view ค่าใด ๆ ที่อยู่ในที่อื่นจะแสดงเป็นการอ้างอิง (ซ่อน) ไปยังวัตถุเป้าหมาย หากเป็นตัวแปร (เช่นการอ้างอิงที่มีชื่อ) ชื่อและการอ้างอิงจะถูกเก็บไว้ในพจนานุกรม (ภายใน) เสมอ หากคุณไม่ต้องการชื่อก็สามารถเก็บข้อมูลอ้างอิงไว้ในคอนเทนเนอร์ของคุณเองได้ซึ่งที่นี่อาจมีการใช้รายการ Pythonสำหรับรายการที่เป็นนามธรรมเสมอ

รายการ Python ถูกนำไปใช้เป็นอาร์เรย์แบบไดนามิกของการอ้างอิง Python tuple ถูกนำไปใช้เป็นอาร์เรย์แบบคงที่ของการอ้างอิงที่มีเนื้อหาคงที่ (ค่าของการอ้างอิงไม่สามารถเปลี่ยนแปลงได้) ด้วยเหตุนี้จึงสามารถสร้างดัชนีได้อย่างง่ายดาย วิธีนี้สามารถใช้รายการสำหรับการใช้เมทริกซ์ได้ด้วย

อีกวิธีหนึ่งในการแสดงเมทริกซ์คืออาร์เรย์ที่ใช้งานโดยโมดูลมาตรฐานarrayซึ่งมีข้อ จำกัด มากกว่าเมื่อเทียบกับประเภทที่จัดเก็บซึ่งเป็นค่าที่เป็นเนื้อเดียวกัน องค์ประกอบจัดเก็บค่าโดยตรง (รายการเก็บการอ้างอิงไปยังออบเจ็กต์ค่าแทน) ด้วยวิธีนี้จะช่วยให้หน่วยความจำมีประสิทธิภาพมากขึ้นและยังเข้าถึงค่าได้เร็วขึ้น

บางครั้งคุณอาจพบว่ามีประโยชน์มากยิ่งขึ้นเป็นตัวแทนที่ จำกัด bytearrayเช่น


7

มีสองห้องสมุดกราฟที่ดีเยี่ยม NetworkXและigraph คุณสามารถค้นหาซอร์สโค้ดไลบรารีทั้งสองได้ใน GitHub คุณสามารถดูวิธีการเขียนฟังก์ชันได้ตลอดเวลา แต่ฉันชอบ NetworkX มากกว่าเพราะมันเข้าใจง่าย
ดูรหัสของพวกเขาเพื่อทราบว่าพวกเขาสร้างฟังก์ชันอย่างไร คุณจะได้รับแนวคิดมากมายจากนั้นสามารถเลือกวิธีที่คุณต้องการสร้างกราฟโดยใช้โครงสร้างข้อมูล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.