← Startseite
đŸ•žïž

Vorlesung 10

Graphen & Zeiger
Knoten, Pfeile — und der kĂŒrzeste Weg (BFS)

Fortgeschrittene Algorithmen und Programmierung
Prof. Dr. Alexandra Mikityuk
HTW Berlin

Graphen Zeiger (Pointer) BFS

Lernziele

  • Verstehen, was ein Graph ist — Knoten & Kanten — und wo sie ĂŒberall stecken
  • Einen Graphen in C als Adjazenzmatrix (2D-Array) speichern können
  • Zeiger (Pointer) begreifen: Adresse, Dereferenzieren, NULL — und warum eine Kante „ein Pfeil" ist
  • Mit BFS (Breitensuche) einen Graphen durchlaufen und den kĂŒrzesten Weg finden

Graphen sind ĂŒberall

Immer wenn Dinge miteinander verbunden sind, habt ihr einen Graphen — auch ohne es zu merken.

đŸ‘„ Soziale Netzwerke

Menschen = Knoten, Freundschaften = Kanten. „Freunde von Freunden" ist eine Graph-Frage.

đŸ—ș Karten & Navigation

Orte = Knoten, Straßen = Kanten. Das Navi sucht den kĂŒrzesten Weg im Graphen.

🌐 Das Internet

Router = Knoten, Leitungen = Kanten. Datenpakete suchen sich einen Weg durch den Graphen.

Weitere Beispiele: Web-Links (Seiten zeigen aufeinander), U-Bahn-Netze, AbhĂ€ngigkeiten in Projekten, MolekĂŒle, Stromnetze 
 Wer Graphen versteht, hat einen SchlĂŒssel zu sehr vielen Problemen.

Was ist ein Graph?

Ein Graph besteht aus zwei Dingen: Knoten (die Punkte) und Kanten (die Verbindungen).

A B C D E

đŸ”” Knoten (engl. node / vertex)

Die „Dinge": hier A, B, C, D, E. Personen, Orte, Router 


➖ Kanten (engl. edge)

Die Verbindungen: A–B, A–C, B–C, B–D, C–E, D–E. „X ist mit Y verbunden."

B's Nachbarn sind A, C und D — die Knoten, zu denen eine Kante fĂŒhrt. Die Anzahl der Kanten an einem Knoten heißt sein Grad (B hat Grad 3).

Zwei Eigenschaften, die du kennen musst

↔ vs âžĄïž Ungerichtet / Gerichtet

Ungerichtet: Kante geht in beide Richtungen. „Anna ist mit Bob befreundet" = „Bob mit Anna".

Gerichtet: Kante hat eine Richtung (ein Pfeil). „Ich folge dir" auf Instagram ist nicht automatisch gegenseitig.

ungerichtet (A – B) gerichtet (A → B)

⚖ Ungewichtet / Gewichtet

Ungewichtet: alle Kanten zĂ€hlen gleich (nur „verbunden ja/nein").

Gewichtet: jede Kante hat einen Wert — z. B. die LĂ€nge einer Straße in km, oder die Dauer in Minuten.

ungewichtet 7 gewichtet (7 km)
Merkt euch „gewichtet" gut: Heute arbeiten wir mit ungewichteten Graphen (BFS). Sobald die Kanten Gewichte bekommen (echte StraßenlĂ€ngen), brauchen wir einen stĂ€rkeren Algorithmus — Dijkstra, nĂ€chste Woche. 🚗

đŸ€” Wie speichern wir einen Graphen in C?

Auf Papier ist ein Graph schnell gemalt. Aber ein Computer kennt keine „Punkte und Striche" — er kennt Zahlen, Arrays und Speicheradressen. Wie also?

💡 Die naheliegende erste Idee: eine Tabelle

Eine Tabelle Knoten × Knoten (ein 2D-Array): „Ist Knoten X mit Knoten Y verbunden? 1 oder 0." Das nutzt nur, was ihr schon kennt — Arrays.

👇 Derselbe Graph als Tabelle

A B C
ABC
A011
B101
C110
Fangen wir mit dieser Tabelle an — der Adjazenzmatrix. Schauen wir sie uns gleich genauer an.

Variante 1: die Adjazenzmatrix

Eine Tabelle Knoten × Knoten. Steht eine 1 bei Zeile X, Spalte Y → es gibt eine Kante X–Y. Sonst 0.

A B C D E
ABCDE
A01100
B10110
C11001
D01001
E00110
// A=0, B=1, C=2, D=3, E=4
int graph[5][5] = {
    {0,1,1,0,0},  // A ist mit B,C verbunden
    {1,0,1,1,0},  // B mit A,C,D
    {1,1,0,0,1},  // C mit A,B,E
    {0,1,0,0,1},  // D mit B,E
    {0,0,1,1,0}   // E mit C,D
};
// "Sind B und D verbunden?" → graph[1][3] == 1 ✓

Adjazenzmatrix — stark, aber nicht perfekt

✅ Vorteile

  • Super einfach (nur ein 2D-Array)
  • „Sind X und Y verbunden?" → ein Blick, sofort (O(1))
  • Nutzt nur, was ihr schon könnt

⚠ Nachteile

  • Braucht immer N × N PlĂ€tze — auch wenn es kaum Kanten gibt
  • Bei 1 Mio. Knoten: 1 Billion Felder — unmöglich!
  • Die meisten echten Graphen sind dĂŒnn (wenige Kanten pro Knoten)

đŸ«„ Riesige Matrix, fast nur Nullen

Bei großen Netzwerken hat fast jedes Feld eine 0 (= keine Kante). Die paar grĂŒnen Felder sind die echten Kanten. Der ganze leere Rest kostet trotzdem Speicher.

Facebook hat ~3 Milliarden Knoten, aber jeder hat im Schnitt nur ein paar hundert Freunde. Eine Matrix wĂ€re zu 99,9999 % mit Nullen gefĂŒllt — pure Verschwendung. Geht das nicht sparsamer — so, dass wir nur die echten Kanten speichern?

đŸ€” Wie wĂŒrdet IHR das besser machen?

Die Matrix verschwendet Platz — fast nur Nullen. Bevor wir eine Lösung zeigen: ĂŒberlegt kurz selbst.

💭 Die Aufgabe

Wie könnte man einen Graphen speichern, sodass man nur die echten Kanten behĂ€lt — und den ganzen leeren Platz spart?

đŸ€” Eine natĂŒrlichere Idee

Statt einer riesigen Tabelle: was, wenn jeder Knoten einfach eine kleine Liste hĂ€tte — „auf diese Nachbarn zeige ich"?

A  →  B, C
B  →  A, C, D
C  →  A, B, E
D  →  B, E
E  →  C, D

Nur die echten Kanten, kein verschwendeter Platz.

Aber wie sagt man in C „A zeigt auf B"? Genau dafĂŒr gibt es Zeiger (Pointer) — die vielleicht wichtigste Sache in fortgeschrittenem C. Lernen wir jetzt.

Was ist ein Zeiger? — eine Adresse

Der Speicher (RAM) ist wie eine riesige Straße mit nummerierten HĂ€usern. Jede Variable wohnt in einem Haus mit einer Adresse.

🏠 Die Variable

Im Haus an Adresse 0x7f3a liegt der Wert 5. Das ist eure normale Variable int x = 5;.

đŸ—ș Der Zeiger

Ein Zeiger speichert nicht den Wert, sondern die Adresse — wie ein Zettel, auf dem „geh zu Haus 0x7f3a" steht. Eine Schatzkarte, kein Schatz.

p
enthÀlt 0x7f3a
— zeigt auf →
Haus 0x7f3a
x = 5
Warum genial: Mit einer Adresse kann man auf etwas verweisen, ohne es zu kopieren. Genau das brauchen wir, damit „A auf B zeigt" — und damit Dinge sich gegenseitig kennen.

Zeiger in C — zwei neue Symbole: & und *

int x = 5;          // normale Variable

int *p = &x;        // p zeigt auf x
//      └─ &x = "die Adresse von x"

printf("%d", *p);  // → 5
//            └ *p = "der Wert, auf den p zeigt"

*p = 9;             // Ă€ndert x ĂŒber den Zeiger!
printf("%d", x);   // → 9

🔑 Die zwei Operatoren

&x = „Adresse von x" (wo wohnt x?)

*p = „folge dem Zeiger" (was liegt dort?) — heißt Dereferenzieren.

📌 Der Stern hat zwei Rollen

int *p beim Deklarieren = „p ist ein Zeiger". *p beim Benutzen = „folge ihm".

Zeiger p 0x7f3a *p folgt dem Pfeil x (Adresse 0x7f3a) 5
Der Aha-Punkt: Über *p = 9 haben wir x verĂ€ndert, ohne x ĂŒberhaupt zu nennen. Der Zeiger gibt uns Fernzugriff.

WofĂŒr das gut ist: eine Funktion, die das Original Ă€ndert

Bisher haben eure Funktionen mit Kopien gearbeitet — und das ist völlig richtig so. Eine Funktion, die etwas berechnet und zurĂŒckgibt, braucht keine Zeiger. Nur wenn eine Funktion die Variablen des Aufrufers selbst verĂ€ndern soll, reichen Kopien nicht.

🟠 Mit Kopien — Tauschen klappt nicht

void tausche(int a, int b) {
    int t = a; a = b; b = t;
}
// a und b sind nur KOPIEN.
// Draußen Ă€ndert sich nichts.

✅ Mit Zeigern — Ă€ndert das Original

void tausche(int *a, int *b) {
    int t = *a; *a = *b; *b = t;
}
tausche(&x, &y);  // Adressen geben
// jetzt sind x und y wirklich
// vertauscht ✓
Wichtig — das ist kein „Fehler von frĂŒher": Mit Kopien zu arbeiten heißt Pass-by-Value (WertĂŒbergabe) — der Normalfall, fĂŒr fast alles genau richtig. Die Variante mit Zeigern nennt man Pass-by-Reference (ReferenzĂŒbergabe / „Call by Reference"): die Funktion bekommt die Adresse und kann damit das Original Ă€ndern. Nur fĂŒr diesen Sonderfall braucht man Zeiger.
Erinnert ihr euch an scanf("%d", &x) aus VL2? Jetzt versteht ihr das &: scanf bekommt die Adresse von x, um den eingelesenen Wert dort hineinzuschreiben. Ihr habt Zeiger lÀngst benutzt!

Der Zeiger ins Nichts: NULL

Achtung — zwei verschiedene Dinge: einen NULL-Zeiger zu haben ist völlig ok. Ihm zu folgen ist der Fehler.

✅ NULL haben — ok

int *p = NULL; heißt bewusst „zeigt gerade auf nichts". Ein gĂŒltiger, nĂŒtzlicher Zustand — z. B. als Ende einer Nachbarliste oder „noch kein Ergebnis".

đŸ’„ NULL folgen — Crash

*p sagt „geh zur Adresse und lies dort". Aber bei NULL gibt es keine Adresse → das Programm stĂŒrzt ab (Segmentation fault).

🧭 Daher die Faustregel: if (p != NULL) { *p ... } — bevor du dem Zeiger folgst, prĂŒfe, ob er ĂŒberhaupt irgendwohin zeigt. Bild: NULL = ein leerer Adress-Zettel — besitzen ist harmlos, aber zur „Adresse" auf einem leeren Zettel hinfahren knallt.
Zeiger p NULL zeigt auf nichts ∅
Der berĂŒhmteste Fehler der Informatik: der „Null-Pointer". Ihr Erfinder Tony Hoare nannte sie spĂ€ter seinen „Milliarden-Dollar-Fehler". Mit der != NULL-PrĂŒfung seid ihr auf der sicheren Seite.

Die große Idee: eine Kante ist ein Zeiger

Der Gedanke in drei Schritten: Kante A—B heißt „von A erreiche ich B".  â†’  In C heißt „erreichen" = einen Zeiger haben.  â†’  Also speichert jeder Knoten Zeiger auf seine Nachbarn.

A B C D E
Jeder Knoten → seine Nachbar-Zeiger:
A ──â–ș B ──â–ș C ──â–ș ∅
B ──â–ș A ──â–ș C ──â–ș D ──â–ș ∅
C ──â–ș A ──â–ș B ──â–ș E ──â–ș ∅
D ──â–ș B ──â–ș E ──â–ș ∅
E ──â–ș C ──â–ș D ──â–ș ∅
jeder Pfeil ──â–ș ist ein Zeiger = eine Kante · ∅ = NULL (Ende)
Schaut auf B (lila): im Graphen gehen von B drei Kanten ab — zu A, C, D. In der Liste hat B genau dieselben drei Zeiger: B ──â–ș A ──â–ș C ──â–ș D. Einem Zeiger folgen = eine Kante entlanggehen. So „lĂ€uft" BFS spĂ€ter durch den Graphen: es folgt den Zeigern.
Das ist die Adjazenzliste — sie speichert nur echte Kanten (keine Nullen wie die Matrix). FĂŒr heute reicht die Idee: jeder Knoten kennt seine Nachbarn ĂŒber Zeiger.

Matrix vs. Liste — wann was?

Adjazenzmatrix (Array)Adjazenzliste (Zeiger)
Speicherimmer N×Nnur echte Kanten
„X–Y verbunden?"sofort (O(1))Liste durchgehen
Nachbarn auflistenganze Zeile prĂŒfen (N)direkt da
Gut fĂŒrkleine / dichte Graphengroße / dĂŒnne Graphen
In C2D-Array (kennt ihr)Knoten mit Zeigern auf Nachbarn
Heute nehmen wir die Matrix — damit wir uns ganz auf den Algorithmus konzentrieren können. Und der wichtigste Graph-Algorithmus ĂŒberhaupt kommt jetzt.

Die Kernfrage: Wie durchsuche ich einen Graphen?

Zwei der hÀufigsten Fragen der Informatik:

A B C D E Start Ziel?

🔍 Erreichbarkeit

„Komme ich von A ĂŒberhaupt zu E?" — Besuche alle Knoten, die von A aus erreichbar sind.

📏 KĂŒrzester Weg

„Wie wenige Schritte von A nach E?" — z. B. „wie viele Ecken bis zu diesem Kontakt?"

Ein Algorithmus löst beides auf einmal: die Breitensuche (engl. Breadth-First Search, kurz BFS).

BFS — wie eine Welle im Wasser

Wirf einen Stein ins Wasser: die Welle erreicht erst alle nahen Punkte, dann die nĂ€chst-ferneren. BFS erkundet den Graphen genau so — Ebene fĂŒr Ebene.

A B C D E

🟱 Ebene 0

Start: A (Abstand 0)

đŸ”” Ebene 1

A's Nachbarn: B, C (Abstand 1)

🟠 Ebene 2

deren neue Nachbarn: D, E (Abstand 2)

Kernregel: erst alle Knoten in einem Abstand fertig besuchen, bevor man weiter nach außen geht. Deshalb „Breiten-Suche".

Das Werkzeug von BFS: die Warteschlange (Queue)

Beim Erkunden entdeckt ihr laufend neue Knoten — besuchen könnt ihr aber immer nur einen nach dem anderen. Die ĂŒbrigen mĂŒsst ihr euch merken. Genau dafĂŒr die Queue: eine To-do-Liste der Knoten „entdeckt, aber noch nicht besucht".

âŹ…ïž Herausnehmen (vorne)

Den nĂ€chsten Knoten von der Liste nehmen und ihn besuchen — d. h. seine Nachbarn anschauen. Danach ist er erledigt (raus). Darum „nimmt man heraus": der Knoten ist abgehakt.

âžĄïž Anstellen (hinten)

Die dabei neu entdeckten Nachbarn ans Ende der Liste schreiben — sie sind noch zu erledigen und kommen spĂ€ter dran.

dequeue vorne raus B C D enqueue hinten rein

👉 So macht BFS einen Schritt (Start bei A)

Queue: [ A ] → A vorne herausnehmen, A's neue Nachbarn (B, C) hinten anstellen → Queue: [ B, C ]


dann dasselbe mit B (vorne raus, dessen neue Nachbarn hinten dran), dann C, 
 bis die Queue leer ist.

Warum ausgerechnet eine Queue? Neue (also fernere) Nachbarn landen immer ganz hinten — sie kommen erst dran, wenn alle nĂ€heren vorne abgearbeitet sind. So entsteht die Wellen-Reihenfolge ganz von allein (erst Abstand 1, dann Abstand 2 
). Das „besucht"-Markieren verhindert dabei, dass man im Kreis lĂ€uft.

BFS Schritt fĂŒr Schritt — Start bei A

Die Welle wĂ€chst — ● Start, ● Abstand 1, ● Abstand 2:

A B C D E
Start: nur A
→
A B C D E
A besucht → B, C
→
A B C D E
B, C besucht → D, E
Schrittnimm herausneue NachbarnQueue danachbesucht
Start—A[A]A
1AB, C[B, C]A,B,C
2BD (A,C schon besucht)[C, D]A,B,C,D
3CE (A,B schon besucht)[D, E]A,B,C,D,E
4D— (alle besucht)[E]A,B,C,D,E
5E—[ ]fertig ✓
Besuchsreihenfolge: A → B → C → D → E. Genau die Wellen-Reihenfolge. Und nebenbei kennen wir jeden Abstand von A: B,C = 1 Schritt, D,E = 2 Schritte.

Queue ≠ besucht — zwei getrennte Listen

HĂ€ufige Frage: „Wenn ich einen Knoten aus der Queue nehme — ist er dann nicht weg?" Nein: es gibt zwei Listen mit zwei Aufgaben. Schaut, was bei einem Schritt passiert (A wird bearbeitet):

📋 Queue
To-do: „wer ist als NĂ€chstes dran?" — schrumpft
A A raus → B C A ist hier weg
🧠 besucht
GedĂ€chtnis: „kenne ich den schon?" — wĂ€chst nur
A A bleibt, B C dazu → A B C A bleibt drin
Derselbe Schritt, zwei Wirkungen: A verlĂ€sst die Queue (erledigt) — bleibt aber in besucht (damit wir A nie wieder anfassen). „Herausnehmen" löscht A nur aus der To-do-Liste, nicht aus dem GedĂ€chtnis — und schon gar nicht aus dem Graphen.
In Code sind es zwei Variablen: eine queue und ein Array besucht[]. dequeue Àndert nur die queue; besucht[A] bleibt gesetzt.

Der Clou: BFS findet den kĂŒrzesten Weg

Nebenbei liefert BFS etwas Wertvolles gratis — den kĂŒrzesten Weg (in Schritten). Aber warum eigentlich?

A B C D E A → C → E = 2 Schritte

🌊 Warum „erstes Erreichen = kĂŒrzester Weg"?

BFS geht Welle fĂŒr Welle: erst alle Knoten in Abstand 1, dann alle in Abstand 2 
 Ein Knoten wird also genau in der Welle entdeckt, die seinem Abstand entspricht.

GĂ€be es einen kĂŒrzeren Weg, hĂ€tte ihn eine frĂŒhere Welle schon erreicht. Also ist das erste Ankommen automatisch das kĂŒrzeste.

Beispiel E: kein direkter Nachbar von A → kann frĂŒhestens in Welle 2 auftauchen. BFS findet E genau dort (A→C→E) → 2 Schritte sind das Minimum. ✓

đŸ‘„ „Degrees of Separation"

Wie viele Ecken bis zu einer fremden Person? BFS im Freundes-Graphen. (BerĂŒhmt: ~6 Schritte zu jedem Menschen.)

đŸ§© Labyrinth lösen

Jede Zelle = Knoten, Nachbarzellen = Kanten. BFS findet den kĂŒrzesten Weg zum Ausgang.

🌐 Netzwerke

Wenigste „Hops" von einem Rechner zum anderen — wieder eine BFS-Frage.

Wichtige Grenze: BFS zĂ€hlt nur Schritte — es nimmt an, dass jede Kante gleich „teuer" ist. Sobald Kanten Gewichte haben (km, Minuten, Kosten), reicht BFS nicht mehr.

🚗 NĂ€chste Woche: wenn Wege unterschiedlich teuer sind

BFS sagt „nimm den Weg mit den wenigsten Kanten". Aber das ist nicht immer der schnellste.

10 3 4 A B C

💭 Das Problem

BFS nimmt A→B: nur 1 Kante — aber Kosten 10.

KlĂŒger: A→C→B ĂŒber 2 Kanten, aber nur 3 + 4 = 7. Der Weg mit mehr Knoten ist hier der gĂŒnstigere!

BFS zĂ€hlt nur Kanten — nicht ihr Gewicht. Deshalb wĂ€hlt es falsch.

Die Lösung heißt Dijkstra — der Algorithmus hinter eurem Navi: er findet den kĂŒrzesten Weg in gewichteten Graphen. Den schauen wir uns nĂ€chste Woche an.

đŸ€” Mini-Quiz

Was bedeutet *p in C?

A) Die Adresse der Variablen p
B) Der Wert, auf den p zeigt (dem Zeiger folgen)
C) Eine Multiplikation mit p
D) Ein neuer Zeiger auf p

đŸ€” Mini-Quiz — Runde 2

Warum findet BFS den kĂŒrzesten Weg (in ungewichteten Graphen)?

A) Weil es eine Adjazenzmatrix benutzt
B) Weil es zufÀllig Knoten ausprobiert
C) Weil es Ebene fĂŒr Ebene vorgeht — nĂ€here Knoten werden zuerst erreicht
D) Weil es die Kantengewichte vergleicht

Zusammenfassung — in einer Slide

  • Graph = Knoten + Kanten. Überall: soziale Netze, Karten, Internet.
  • Adjazenzmatrix = 2D-Array (1 = verbunden). Einfach, aber N×N Platz.
  • Zeiger: &x = Adresse von x · *p = Wert, auf den p zeigt · NULL = zeigt auf nichts.
  • Eine Kante ist ein Zeiger → Adjazenzliste speichert nur echte Kanten.
  • BFS erkundet Ebene fĂŒr Ebene (mit einer Queue) und findet den kĂŒrzesten Weg (ungewichtet).
  • Gewichtete Wege (echte Entfernungen) → Dijkstra, nĂ€chste Woche.

📚 Quellen & zum Ausprobieren

đŸŽ„ Anschauen

  • Computerphile — „Graphs" & „Pointers"
  • 3Blue1Brown / Reducible — schöne Graph-Visualisierungen (YouTube)
  • VisuAlgo.net — BFS Schritt fĂŒr Schritt animiert

📖 Nachlesen

  • Sedgewick, Algorithms — Kapitel Graphen
  • Kernighan & Ritchie, The C Programming Language — Kapitel 5 (Pointers)
  • cs50 (Harvard) — sehr gute Pointer-ErklĂ€rung
Selbst-Test: Malt einen eigenen Graphen mit 6 Knoten, schreibt die Adjazenzmatrix auf und fĂŒhrt BFS von einem Startknoten von Hand aus. Wer das kann, hat heute alles verstanden.

Vielen Dank!

Eine Kante ist ein Pfeil. đŸ•žïž

Prof. Dr. Alexandra Mikityuk

HTW Berlin · BĂŒro Raum 308

NĂ€chste Woche: Dijkstra — der kĂŒrzeste Weg

1 /