https://github.com/ufkapano/graphtheory/
Poniżej podana jest propozycja interfejsu ADT dla grafu z wagami, skierowanego lub nieskierowanego. W implementacji macierzy sąsiedztwa wierzchołkami grafu są liczby od 0 do n-1 i właściwie wierzchołki już są utworzone (zwykle się ich nie usuwa). W innych implementacjach pythonowych wierzchołki mogą być liczbami, stringami, czy innymi obiektami hashowalnymi, z porządkiem liniowym. Tutaj pojawia się problem z kompatybilnością różnych implementacji, jeżeli przewidujemy usuwanie wierzchołków.
Interfejs korzysta z obiektów krawędzi, które odpowiadają krawędziom skierowanym ważonym.
Jeżeli graf jest nieskierowany, to krawędzie o przeciwnych kierunkach są równoważne. W praktyce krawędź nieskierowana jest przechowywana w grafie jako dwie krawędzie skierowane przeciwnie.
class Edge:
"""Klasa dla krawędzi skierowanej z wagą."""
def __init__(self, source, target, weight=1):
"""Konstruktor krawędzi."""
self.source = source
self.target = target
self.weight = weight
def __repr__(self):
"""Zwraca reprezentację napisową krawędzi."""
if self.weight == 1:
return "Edge({}, {})".format(repr(self.source), repr(self.target))
else:
return "Edge({}, {}, {})".format(
repr(self.source), repr(self.target), repr(self.weight))
def __eq__(self, other):
"""Porównywanie krawędzi (e1 == e2)."""
return (self.source, self.target, self.weight) == (
other.source, other.target, other.weight)
def __ne__(self, other):
"""Porównywanie krawędzi (e1 != e2)."""
return not self == other
def __lt__(self, other):
"""Porównywanie krawędzi (e1 < e2)."""
return (self.weight, self.source, self.target) < (
other.weight, other.source, other.target)
def __le__(self, other):
"""Porównywanie krawędzi (e1 <= e2)."""
return (self.weight, self.source, self.target) <= (
other.weight, other.source, other.target)
def __hash__(self):
"""Krawędzie są hashowalne."""
return hash((self.source, self.target, self.weight))
def __invert__(self): # przydatne dla grafu nieskierowanego
"""Zwraca krawędź o przeciwnym kierunku (~edge)."""
return Edge(self.target, self.source, self.weight)
class Graph:
"""Klasa dla grafu ważonego, skierowanego lub nieskierowanego."""
def __init__(self, *, n=0, directed=False):
self.n = n # kompatybilność
self.directed = directed # bool, czy graf skierowany
def v(self): pass # zwraca liczbę wierzchołków
def e(self): pass # zwraca liczbę krawędzi
def is_directed(self): # bool, czy graf skierowany
return self.directed
def add_node(self, node): pass # dodaje wierzchołek
def has_node(self, node): pass # bool
def del_node(self, node): pass # usuwa wierzchołek z krawędziami incydentnymi
def add_edge(self, edge): pass # wstawienie krawędzi
#def add_edge(self, (source, target)): pass # wstawienie krawędzi
def has_edge(self, edge): pass # bool
#def has_edge(self, (source, target)): pass # bool
def del_edge(self, edge): pass # usunięcie krawędzi
#def del_edge(self, (source, target)): pass # usunięcie krawędzi
def weight(self, edge): pass # zwraca wagę krawędzi
#def weight(self, (source, target)): pass # zwraca wagę krawędzi
def iternodes(self): pass # iterator po wierzchołkach
def iteradjacent(self, node): pass # iterator po wierzchołkach sąsiednich
def iteroutedges(self, node): pass # iterator po krawędziach wychodzących
def iterinedges(self, node): pass # iterator po krawędziach przychodzących
def iteredges(self): pass # iterator po krawędziach
# w grafie nieskierowanym można przyjąć, że edge.source < edge.target
def copy(self): pass # zwraca kopię grafu
Mogą być implementacje dict+list (graf bez wag), dict+set (graf bez wag), dict+dict (graf ważony).
Krawędź to będzie 2-krotka (source, target) lub 3-krotka z wagą (source, target, weight).
node_list = list(graph)
node in graph # bool, czy wierzchołek należy do grafu
len(graph) # liczba wierzchołków grafu
len(graph[node]) # stopień wierzchołka (graf nieskierowany)
for node in graph: # iteracja po wierzchołkach
print("wierzchołek {}".format(node))
for target in graph[source]: # iteracja po sąsiadach wierzchołka source
print("{} sąsiaduje z {}".format(source, target))
target in graph[source] # bool, czy jest sąsiadem
for source in graph: # iteracja po krawędziach bez wag
for target in graph[source]:
print("krawędź {} {}".format(source, target))
sum(len(graph[node]) for node in graph)
# liczba krawędzi dla grafu skierowanego
graph[source][target] # waga dla implementacji dict+dict
[node for node in graph if not graph[node]]
# lista wierzcholkow izolowanych
sorted(len(graph[node]) for node in graph)
# posortowana lista stopni wierzcholkow grafu nieskierowanego