summaryrefslogtreecommitdiff
path: root/graph
diff options
context:
space:
mode:
Diffstat (limited to 'graph')
-rw-r--r--graph/TSP.cpp3
-rw-r--r--graph/bellmannFord.cpp40
-rw-r--r--graph/bitonicTSP.cpp19
-rw-r--r--graph/capacityScaling.cpp35
-rw-r--r--graph/dijkstra.cpp36
-rw-r--r--graph/edmondsKarp.cpp14
-rw-r--r--graph/euler.cpp15
-rw-r--r--graph/floydWarshall.cpp15
-rw-r--r--graph/graph.tex57
-rw-r--r--graph/maxCarBiMatch.cpp10
-rw-r--r--graph/minCostMaxFlow.cpp21
-rw-r--r--graph/scc.cpp32
12 files changed, 190 insertions, 107 deletions
diff --git a/graph/TSP.cpp b/graph/TSP.cpp
index c38cbb2..0161d3c 100644
--- a/graph/TSP.cpp
+++ b/graph/TSP.cpp
@@ -1,4 +1,5 @@
-//nodes[0] has to be the start and end node.
+// Laufzeit: O(n*2^n)
+// nodes[0] ist Start- und Endknoten.
vector<vector<int>> dist;
vector<int> TSP() {
int n = dist.size(), m = 1 << n;
diff --git a/graph/bellmannFord.cpp b/graph/bellmannFord.cpp
index 94fa034..45efd6b 100644
--- a/graph/bellmannFord.cpp
+++ b/graph/bellmannFord.cpp
@@ -1,18 +1,30 @@
-//n = number of vertices, edges is vector of edges
-dist.assign(n, INF); dist[0] = 0;
-parent.assign(n, -1);
-for (i = 0; i < n - 1; i++) {
- for (j = 0; j < (int)edges.size(); j++) {
- if (dist[edges[j].from] + edges[j].cost < dist[edges[j].to]) {
- dist[edges[j].to] = dist[edges[j].from] + edges[j].cost;
- parent[edges[j].to] = edges[j].from;
+// Laufzeit: O(|V|*|E|)
+struct edge {
+ int from; int to; int cost;
+ edge () {};
+ edge (int from, int to, int cost) : from(from), to(to), cost(cost) {};
+};
+
+vector<edge> edges; // Kanten einfügen!
+vector<int> dist, parent;
+
+void bellmannFord() {
+ dist.assign(NUM_VERTICES, INF); dist[0] = 0;
+ parent.assign(NUM_VERTICES, -1);
+ for (int i = 0; i < NUM_VERTICES - 1; i++) {
+ for (int j = 0; j < (int)edges.size(); j++) {
+ if (dist[edges[j].from] + edges[j].cost < dist[edges[j].to]) {
+ dist[edges[j].to] = dist[edges[j].from] + edges[j].cost;
+ parent[edges[j].to] = edges[j].from;
+ }
}
}
-}
-//now dist and parent are correct shortest paths
-//next lines check for negative cycles
-for (j = 0; j < (int)edges.size(); j++) {
- if (dist[edges[j].from] + edges[j].cost < dist[edges[j].to]) {
- //NEGATIVE CYCLE found
+
+ // "dist" und "parent" sind korrekte kürzeste Pfade.
+ // Folgende Zeilen prüfen nur negative Kreise.
+ for (int j = 0; j < (int)edges.size(); j++) {
+ if (dist[edges[j].from] + edges[j].cost < dist[edges[j].to]) {
+ // Negativer Kreis gefunden.
+ }
}
}
diff --git a/graph/bitonicTSP.cpp b/graph/bitonicTSP.cpp
index 4e4dda0..bf01f9e 100644
--- a/graph/bitonicTSP.cpp
+++ b/graph/bitonicTSP.cpp
@@ -1,13 +1,16 @@
-vector< vector<double> > dp; //initialize with -1
-vector< vector<double> > dist; //initialize with all dists between points
-vector<int> lr, rl; //left-to-right and right-to-left paths
-int n; //number of points
-double get(int p1, int p2) { //call get(0, 0) to get length of shortest bitonic route
+// Laufzeit: O(|V|^2)
+vector< vector<double> > dp; // Initialisiere mit -1
+vector< vector<double> > dist; // Initialisiere mit Entfernungen zwischen Punkten.
+vector<int> lr, rl; // Links-nach-rechts und rechts-nach-links Pfade.
+int n; // #Knoten
+
+// get(0, 0) gibt die Länge der kürzesten bitonischen Route.
+double get(int p1, int p2) {
int v = max(p1, p2) + 1;
if (v == n - 1) return dist[p1][v] + dist[v][p2];
if (dp[p1][p2] > -0.5) return dp[p1][p2];
double tryLR = dist[p1][v] + get(v, p2), tryRl = dist[v][p2] + get(p1, v);
- if (tryLR < tryRL) lr.push_back(v); //reconstructs the path, pushes v to rl if the choice does not matter
- else rl.push_back(v); //change this if needed
+ if (tryLR < tryRL) lr.push_back(v); // Baut die Pfade auf. Fügt v zu rl hinzu, falls beide gleich teuer.
+ else rl.push_back(v); // Änder das, falls nötig.
return min(tryLR, tryRL);
-} \ No newline at end of file
+}
diff --git a/graph/capacityScaling.cpp b/graph/capacityScaling.cpp
new file mode 100644
index 0000000..2349ddd
--- /dev/null
+++ b/graph/capacityScaling.cpp
@@ -0,0 +1,35 @@
+// Ford Fulkerson with capacity scaling.
+// Laufzeit: O(|E|^2 * log C)
+const int MAXN = 190000, MAXC = 1<<29;
+struct edge { int dest, capacity, rev; };
+vector<edge> adj[MAXN];
+int vis[MAXN], target, iter, cap;
+
+void addedge(int x, int y, int c) {
+ adj[x].push_back(edge {y, c, (int)adj[y].size()});
+ adj[y].push_back(edge {x, 0, (int)adj[x].size() - 1});
+}
+
+bool dfs(int x) {
+ if (x == target) return 1;
+ if (vis[x] == iter) return 0;
+ vis[x] = iter;
+ for (edge& e: adj[x])
+ if (e.capacity >= cap && dfs(e.dest)) {
+ e.capacity -= cap;
+ adj[e.dest][e.rev].capacity += cap;
+ return 1;
+ }
+ return 0;
+}
+
+int maxflow(int S, int T) {
+ cap = MAXC, target = T;
+ int flow = 0;
+ while(cap) {
+ while(++iter, dfs(S))
+ flow += cap;
+ cap /= 2;
+ }
+ return flow;
+} \ No newline at end of file
diff --git a/graph/dijkstra.cpp b/graph/dijkstra.cpp
index 1d4f7a1..649cf6e 100644
--- a/graph/dijkstra.cpp
+++ b/graph/dijkstra.cpp
@@ -1,20 +1,26 @@
-priority_queue<ii, vector<ii>, greater<ii> > pq;
-vector<int> dist;
-dist.assign(NUM_VERTICES, INF);
-dist[START] = 0;
-pq.push(ii(0, START));
+// Laufzeit: O((|E|+|V|)*log |V|)
+void dijkstra(int start) {
+ priority_queue<ii, vector<ii>, greater<ii> > pq;
+ vector<int> dist, parent;
+ dist.assign(NUM_VERTICES, INF);
+ parent.assign(NUM_VERTICES, -1);
-while (!pq.empty()) {
- ii front = pq.top(); pq.pop();
- int curNode = front.second, curDist = front.first;
-
- if (curDist > dist[curNode]) continue;
-
- for (int i = 0; i < (int)adjlist[curNode].size(); i++) {
- int nextNode = adjlist[curNode][i].first, nextDist = curDist + adjlist[curNode][i].second;
+ dist[start] = 0;
+ pq.push(ii(0, start));
+
+ while (!pq.empty()) {
+ ii front = pq.top(); pq.pop();
+ int curNode = front.second, curDist = front.first;
+
+ if (curDist > dist[curNode]) continue;
- if (nextDist < dist[nextNode]) {
- dist[nextNode] = nextDist; pq.push(ii(nextDist, nextNode));
+ for (int i = 0; i < (int)adjlist[curNode].size(); i++) {
+ int nextNode = adjlist[curNode][i].first, nextDist = curDist + adjlist[curNode][i].second;
+
+ if (nextDist < dist[nextNode]) {
+ dist[nextNode] = nextDist; parent[nextNode] = curNode;
+ pq.push(ii(nextDist, nextNode));
+ }
}
}
}
diff --git a/graph/edmondsKarp.cpp b/graph/edmondsKarp.cpp
index 9181062..dcfd85b 100644
--- a/graph/edmondsKarp.cpp
+++ b/graph/edmondsKarp.cpp
@@ -1,7 +1,8 @@
-int s, t, f; //source, target, single flow
-int res[MAX_V][MAX_V]; //adj-matrix
+// Laufzeit: O(|V|*|E|^2)
+int s, t, f; // Quelle, Senke, single flow
+int res[MAX_V][MAX_V];
vector< vector<int> > adjlist;
-int p[MAX_V]; //bfs spanning tree
+int p[MAX_V];
void augment(int v, int minEdge) {
if (v == s) { f = minEdge; return; }
@@ -10,14 +11,15 @@ void augment(int v, int minEdge) {
res[p[v]][v] -= f; res[v][p[v]] += f;
}}
-int maxFlow() { //first inititalize res, adjList, s and t
+// Initialisiere res, adjList, s und t.
+int maxFlow() {
int mf = 0;
while (true) {
f = 0;
bitset<MAX_V> vis; vis[s] = true;
queue<int> q; q.push(s);
memset(p, -1, sizeof(p));
- while (!q.empty()) { //BFS
+ while (!q.empty()) { // BFS
int u = q.front(); q.pop();
if (u == t) break;
for (int j = 0; j < (int)adjlist[u].size(); j++) {
@@ -26,7 +28,7 @@ int maxFlow() { //first inititalize res, adjList, s and t
vis[v] = true; q.push(v); p[v] = u;
}}}
- augment(t, INF); //add found path to max flow
+ augment(t, INF); // Pfad zu Fluss hinzufügen.
if (f == 0) break;
mf += f;
}
diff --git a/graph/euler.cpp b/graph/euler.cpp
index 74b3399..6551ac3 100644
--- a/graph/euler.cpp
+++ b/graph/euler.cpp
@@ -1,3 +1,4 @@
+// Laufzeit: O(|V|+|E|)
vector< vector<int> > adjlist;
vector< vector<int> > otherIdx;
vector<int> cycle;
@@ -14,9 +15,9 @@ void swapEdges(int n, int a, int b) { // Vertauscht Kanten mit Indizes a und b v
otherIdx[neighB][idxNeighB] = a;
}
-void removeEdge(int n, int i) { // Entfernt Kante i von Knoten n (und die zugehoerige Rueckwaertskante).
+void removeEdge(int n, int i) { // Entfernt Kante i von Knoten n (und die zugehörige Rückwärtskante).
int other = adjlist[n][i];
- if (other == n) { //Schlingen
+ if (other == n) { //Schlingen.
validIdx[n]++;
return;
}
@@ -28,14 +29,14 @@ void removeEdge(int n, int i) { // Entfernt Kante i von Knoten n (und die zugeho
validIdx[other]++;
}
-//findet Eulerzyklus an Knoten n startend
-//teste vorher, dass Graph zusammenhaengend ist! (isolierte Punkte sind ok)
-//teste vorher, ob Eulerzyklus ueberhaupt existiert!
+// Findet Eulerzyklus an Knoten n startend.
+// Teste vorher, dass Graph zusammenhängend ist! Was ist mit isolierten Knoten?
+// Teste vorher, ob Eulerzyklus überhaupt existiert!
void euler(int n) {
while (validIdx[n] < (int)adjlist[n].size()) {
int nn = adjlist[n][validIdx[n]];
removeEdge(n, validIdx[n]);
euler(nn);
}
- cycle.push_back(n); //Zyklus am Ende in cycle
-} \ No newline at end of file
+ cycle.push_back(n); // Zyklus am Ende in cycle (umgekehrte Reihenfolge).
+}
diff --git a/graph/floydWarshall.cpp b/graph/floydWarshall.cpp
index f5c950d..30d463a 100644
--- a/graph/floydWarshall.cpp
+++ b/graph/floydWarshall.cpp
@@ -1,8 +1,11 @@
-//initialize adjmat, adjmat[i][i] = 0, adjmat[i][j] = INF if no edge is between i and j, length otherwise
-for (k = 0; k < MAX_V; k++) {
- for (i = 0; i < MAX_V; i++) {
- for (j = 0; j < MAX_V; j++) {
- if (adjmat[i][k] + adjmat[k][j] < adjmat[i][j]) adjmat[i][j] = adjmat[i][k] + adjmat[k][j];
+// Laufzeit: O(|V|^3)
+// Initialize adjmat: adjmat[i][i] = 0, adjmat[i][j] = INF if no edge is between i and j, length otherwise.
+void floydWarshall() {
+ for (k = 0; k < NUM_VERTICES; k++) {
+ for (i = 0; i < NUM_VERTICES; i++) {
+ for (j = 0; j < NUM_VERTICES; j++) {
+ if (adjmat[i][k] + adjmat[k][j] < adjmat[i][j]) adjmat[i][j] = adjmat[i][k] + adjmat[k][j];
+ }
}
}
-} \ No newline at end of file
+}
diff --git a/graph/graph.tex b/graph/graph.tex
index 38deee6..7a85337 100644
--- a/graph/graph.tex
+++ b/graph/graph.tex
@@ -1,11 +1,17 @@
\section{Graphen}
\subsection{Minimale Spannbäume}
+% Add an implementation.
Benutze Algorithmus von \textsc{Kruskal} oder Algorithmus von \textsc{Prim}.
-\begin{description}
- \item[Schnitteigenschaft] Für jeden Schnitt $C$ im Graphen gilt: Gibt es eine Kante $e$, die echt leichter ist als alle anderen Schnittkanten, so gehört diese zu allen minimalen Spannbäumen. ($\Rightarrow$ Die leichteste Kante in einem Schnitt kann in einem minimalen Spannbaum verwendet werden.)
- \item[Kreiseigenschaft] Für jeden Kreis $K$ im Graphen gilt: Die schwerste Kante auf dem Kreis ist nicht Teil des minimalen Spannbaums.
-\end{description}
+
+\paragraph{Schnitteigenschaft}
+Für jeden Schnitt $C$ im Graphen gilt:
+Gibt es eine Kante $e$, die echt leichter ist als alle anderen Schnittkanten, so gehört diese zu allen minimalen Spannbäumen.
+($\Rightarrow$ Die leichteste Kante in einem Schnitt kann in einem minimalen Spannbaum verwendet werden.)
+
+\paragraph{Kreiseigenschaft}
+Für jeden Kreis $K$ im Graphen gilt:
+Die schwerste Kante auf dem Kreis ist nicht Teil des minimalen Spannbaums.
\subsection{Kürzeste Wege}
@@ -14,20 +20,23 @@ Kürzeste Pfade in Graphen ohne negative Kanten.
\lstinputlisting{graph/dijkstra.cpp}
\subsubsection{\textsc{Bellmann-Ford}-Algorithmus}
-Kürzestes Pfade in Graphen mit negativen Kanten. Erkennt negative Zyklen.
+Kürzestes Pfade in Graphen mit negativen Kanten.
+Erkennt negative Zyklen.
\lstinputlisting{graph/bellmannFord.cpp}
\subsubsection{\textsc{Floyd-Warshall}-Algorithmus}
-Alle kürzesten Pfade im Graphen.
\lstinputlisting{graph/floydWarshall.cpp}
\begin{itemize}
- \item \textsc{Floyd-Warshall} findet auch negative Kreise. Es existiert genau dann ein negativer Kreis, wenn \lstinline{dist[i][i] < 0} ist.
- \item Evtl. überschreibt die Eingabe die Nullen auf der Hauptdiagonalen.
+ \item \textsc{Floyd-Warshall} findet auch negative Kreise.
+ Es existiert genau dann ein negativer Kreis, wenn \lstinline{dist[i][i] < 0} ist.
+ \item \textbf{Evtl. überschreibt die Eingabe die Nullen auf der Hauptdiagonalen.}
+ Das ist fast immer ungewollt!
\end{itemize}
\subsection{Strongly Connected Components (\textsc{Tarjans}-Algorithmus)}
\lstinputlisting{graph/scc.cpp}
+% TODO (pjungeblut): This has errors for bridges!
\subsection{Artikulationspunkte und Brücken}
\lstinputlisting{graph/articulationPoints.cpp}
@@ -36,26 +45,38 @@ Alle kürzesten Pfade im Graphen.
\item Zyklus existiert, wenn jeder Knoten geraden Grad hat (ungerichtet), bzw. bei jedem Knoten Ein- und Ausgangsgrad übereinstimmen (gerichtet).
\item Pfad existiert, wenn alle bis auf (maximal) zwei Knoten geraden Grad haben (ungerichtet), bzw. bei allen Knoten bis auf zwei Ein- und Ausgangsgrad übereinstimmen, wobei einer eine Ausgangskante mehr hat (Startknoten) und einer eine Eingangskante mehr hat (Endknoten).
\item \textbf{Je nach Aufgabenstellung überprüfen, wie isolierte Punkte interpretiert werden sollen.}
- \item Der Code unten läuft in Linearzeit. Wenn das nicht notwenidg ist (oder bestimmte Sortierungen verlangt werden), gehts mit einem \lstinline{set} einfacher.
+ \item Der Code unten läuft in Linearzeit.
+ Wenn das nicht notwenidg ist (oder bestimmte Sortierungen verlangt werden), gehts mit einem \lstinline{set} einfacher.
\end{itemize}
\begin{figure}[h]
-\begin{lstlisting}
-VISIT(v):
- forall e=(v,w) in E
- delete e from E
- VISIT(w)
- print e
-\end{lstlisting}
-\caption{Idee für Eulerzyklen}
+ \begin{lstlisting}
+ VISIT(v):
+ forall e=(v,w) in E
+ delete e from E
+ VISIT(w)
+ print e
+ \end{lstlisting}
+ \caption{Idee für Eulerzyklen}
\end{figure}
\lstinputlisting{graph/euler.cpp}
+\paragraph{Achtung:}
+\begin{itemize}
+ \item Die Ausgabe erfolgt in falscher Reihenfolge.
+ \item Algorithmus schlägt nicht fehl, falls kein Eulerzyklus existiert.
+ Die Existenz muss separat geprüft werden.
+\end{itemize}
\subsection{Lowest Common Ancestor}
\lstinputlisting{graph/LCA.cpp}
-\subsection{Max-Flow (\textsc{Edmonds-Karp}-Algorithmus)}
+\subsection{Max-Flow}
+
+\subsubsection{\textsc{Edmonds-Karp}-Algorithmus}
\lstinputlisting{graph/edmondsKarp.cpp}
+\subsubsection{Capacity Scaling}
+\lstinputlisting{graph/capacityScaling.cpp}
+
\subsubsection{Maximum Edge Disjoint Paths}
Finde die maximale Anzahl Pfade von $s$ nach $t$, die keine Kante teilen.
\begin{enumerate}
diff --git a/graph/maxCarBiMatch.cpp b/graph/maxCarBiMatch.cpp
index fb9cb3d..c086abc 100644
--- a/graph/maxCarBiMatch.cpp
+++ b/graph/maxCarBiMatch.cpp
@@ -1,5 +1,6 @@
-vector< vector<int> > adjlist; //seems to work directed, from left to right
-vector<int> pairs; //for every node, stores the matching node on the other side or -1
+// Laufzeit: O(n*(|V|+|E|))
+vector< vector<int> > adjlist; // Gerichtete Kanten, von links nach rechts.
+vector<int> pairs; // Zu jedem Knoten der gematchte Knoten rechts, oder -1.
vector<bool> visited;
bool dfs(int i) {
@@ -13,12 +14,13 @@ bool dfs(int i) {
return false;
}
-int kuhn(int n, int m) { // n = nodes on left side (numbered 0..n-1), m = nodes on the right side
+// n = #Knoten links (0..n-1), m = #Knoten rechts
+int kuhn(int n, int m) {
pairs.assign(n + m, -1);
int ans = 0;
for (int i = 0; i < n; i++) {
visited.assign(n + m, false);
ans += dfs(i);
}
- return ans; //size of the MCBM
+ return ans; // Größe des Matchings.
}
diff --git a/graph/minCostMaxFlow.cpp b/graph/minCostMaxFlow.cpp
index 56e6223..a4e997f 100644
--- a/graph/minCostMaxFlow.cpp
+++ b/graph/minCostMaxFlow.cpp
@@ -1,23 +1,24 @@
-int s, t, f, c; //source, target, single flow, single cost
-int res[MAX_V][MAX_V]; //residual graph
-vector<edge> edges; //edge list
-vector<int> p, dist; //parent pointer, dist field
+// Laufzeit: O(|V|^2 * |E|^2)
+int s, t, f, c; // Quelle, Senke, single flow, single cost
+int res[MAX_V][MAX_V];
+vector<edge> edges;
+vector<int> p, dist;
void augment(int v, int minEdge) {
- if (v == s) { f = minEdge; c = minEdge * dist[t]; return; } //c = minEdge * dist[t] added
+ if (v == s) { f = minEdge; c = minEdge * dist[t]; return; } // c = minEdge * dist[t] added
else if (p[v] != -1) {
augment(p[v], min(minEdge, res[p[v]][v]));
res[p[v]][v] -= f; res[v][p[v]] += f;
}}
-//first inititalize res, edges, s and t
-int minCostMaxFlow(int v) { //v is number of vertices
+// Initialisiere res, edges, s und t.
+int minCostMaxFlow(int v) { // v = #Knoten
int mf = 0, mc = 0, i, j;
while (true) {
f = 0; c = 0;
dist.assign(v, INF); dist[s] = 0;
p.assign(v, -1);
- for (i = 0; i < v - 1; i++) { //Bellmann-Ford
+ for (i = 0; i < v - 1; i++) { // Bellmann-Ford.
for (j = 0; j < (int)edges.size(); j++) {
if (res[edges[j].from][edges[j].to] > 0 && dist[edges[j].from] + edges[j].cost < dist[edges[j].to]) {
dist[edges[j].to] = dist[edges[j].from] + edges[j].cost;
@@ -26,9 +27,9 @@ int minCostMaxFlow(int v) { //v is number of vertices
}
}
- augment(t, INF); //add found path to max flow, method as in EdmondsKarp
+ augment(t, INF); // Gefunden Pfad zum Fluss hinzufügen.
if (f == 0) break;
mf += f; mc += c;
}
- return mf; //returns max flow, for in cost, use mc
+ return mf; // mf is der maximale Fluss, mc sind die minimalen Kosten.
} \ No newline at end of file
diff --git a/graph/scc.cpp b/graph/scc.cpp
index b33616b..f2883ef 100644
--- a/graph/scc.cpp
+++ b/graph/scc.cpp
@@ -1,4 +1,5 @@
-int counter, sccCounter, n; //n == number of vertices
+// Laufzeit: O(|V|+|E|)
+int counter, sccCounter;
vector<bool> visited, inStack;
vector< vector<int> > adjlist;
vector<int> d, low, sccs;
@@ -6,11 +7,8 @@ stack<int> s;
void visit(int v) {
visited[v] = true;
- d[v] = counter;
- low[v] = counter;
- counter++;
- inStack[v] = true;
- s.push(v);
+ d[v] = counter; low[v] = counter; counter++;
+ inStack[v] = true; s.push(v);
for (int i = 0; i < (int)adjlist[v].size(); i++) {
int u = adjlist[v][i];
@@ -25,9 +23,7 @@ void visit(int v) {
if (d[v] == low[v]) {
int u;
do {
- u = s.top();
- s.pop();
- inStack[u] = false;
+ u = s.top(); s.pop(); inStack[u] = false;
sccs[u] = sccCounter;
} while(u != v);
sccCounter++;
@@ -35,20 +31,20 @@ void visit(int v) {
}
void scc() {
- //read adjlist
-
- visited.clear(); visited.assign(n, false);
- d.clear(); d.resize(n);
- low.clear(); low.resize(n);
- inStack.clear(); inStack.assign(n, false);
- sccs.clear(); sccs.resize(n);
+ // Initialisiere adjlist!
+ visited.clear(); visited.assign(NUM_VERTICES, false);
+ d.clear(); d.resize(NUM_VERTICES);
+ low.clear(); low.resize(NUM_VERTICES);
+ inStack.clear(); inStack.assign(NUM_VERTICES, false);
+ sccs.clear(); sccs.resize(NUM_VERTICES);
counter = 0;
sccCounter = 0;
- for (i = 0; i < n; i++) {
+ for (i = 0; i < NUM_VERTICES; i++) {
if (!visited[i]) {
visit(i);
}
}
- //sccs has the component for each vertex
+ // sccCounter ist Anzahl der starkem Zusammenhangskomponenten.
+ // sccs enthält den Index der SCC für jeden Knoten.
} \ No newline at end of file