In Vorlesung 10 ging es um Graphen & Zeiger â was ein Graph ist (Knoten + Kanten), wie man ihn als Adjazenzmatrix speichert, was ein Zeiger ist (eine Adresse), und wie BFS einen Graphen Schicht fĂŒr Schicht durchsucht und dabei den kĂŒrzesten Weg findet.
Heute baut ihr das alles selbst: von Zeigern ĂŒber die Adjazenzmatrix bis zu BFS mit eigener Queue. Dazwischen schlieĂen wir die zwei Werkzeuge ein, die in der Vorlesung nur als Bild auftauchten (A âââș B âââș C âââș â
), aber noch nicht im Code: struct (Daten bĂŒndeln) und malloc (Speicher zur Laufzeit anfordern). Damit verwandelt ihr die gemalte âNachbarliste" in echten C-Code.
Alle Aufgaben sind reines C â ohne externe Bibliotheken, sie laufen ĂŒberall mit gcc lab.c. Wer in einer anderen Sprache arbeitet: Die Algorithmen (Adjazenzmatrix, Nachbarliste, BFS) sind 1:1 ĂŒbertragbar. Nur zwei C-Eigenheiten haben in höheren Sprachen kein direktes GegenstĂŒck: struct entspricht einer Klasse/einem Objekt (Python class, Java-Objekt), und malloc/free entfĂ€llt â dort verwaltet die Sprache den Speicher automatisch (Listen, new, Garbage Collection).
Musterlösungen liegen nur in C vor. Passwörter sind ganz unten frei zugÀnglich.
Adresse vs. Wert, etwas ĂŒber einen Zeiger Ă€ndern, tausche mit *
Adjazenzmatrix lesen, Nachbarn finden, Verbindungen prĂŒfen
Daten bĂŒndeln, Speicher zur Laufzeit anfordern und freigeben
Queue + besucht-Liste, kĂŒrzeste Distanz Schicht fĂŒr Schicht
Wir benutzen durchgehend denselben kleinen, ungerichteten Graphen aus der Vorlesung â 5 Knoten A B C D E (intern 0 1 2 3 4):
Behalte dieses Bild im Kopf â jede Aufgabe arbeitet mit genau diesem Graphen.
Ziel: den Unterschied zwischen Wert und Adresse mit eigenen HĂ€nden spĂŒren â und sehen, wie eine Funktion das Original Ă€ndert, wenn man ihr die Adresse gibt.
int x = 5; an und einen Zeiger int *p = &x; (p zeigt auf x).x und *p aus. Ăndere dann x ĂŒber den Zeiger mit *p = 42; und gib x erneut aus.tausche(int *a, int *b), das zwei Variablen vertauscht â ĂŒber ihre Adressen. Teste mit tausche(&x, &y);.tausche mit normalen Parametern (int a, int b) funktionieren? Nein â die Funktion bekĂ€me nur Kopien, drauĂen Ă€ndert sich nichts. Erst die Adresse (&x) erlaubt es, das Original zu verĂ€ndern. Genau dieses Prinzip steckt hinter Zeigern auf Graphen, structs und allem, was gleich kommt.
#include <stdio.h>
void tausche(int *a, int *b) {
int t = *a; // Wert, auf den a zeigt
*a = *b;
*b = t;
}
int main(void) {
int x = 5, y = 9;
int *p = &x; // p zeigt auf x
printf("x = %d, *p = %d\n", x, *p); // 5, 5
*p = 42; // x UEBER den Zeiger aendern
printf("nach *p = 42: x = %d\n", x); // 42
printf("vorher: x = %d, y = %d\n", x, y);
tausche(&x, &y); // Adressen uebergeben
printf("nachher: x = %d, y = %d\n", x, y);
return 0;
}
Erwartete Ausgabe:
x = 5, *p = 5 nach *p = 42: x = 42 vorher: x = 42, y = 9 nachher: x = 9, y = 42
Ziel: den Beispielgraphen als Adjazenzmatrix speichern und zwei Grundfragen beantworten: âSind zwei Knoten verbunden?" und âWer sind die Nachbarn eines Knotens?"
int graph[5][5] an. graph[i][j] == 1 bedeutet: Kante zwischen i und j.B(1) und D(3) verbunden sind â also graph[1][3].C(2) aus: laufe Zeile 2 durch und drucke jeden Knoten j mit graph[2][j] == 1.graph[i][j] ist immer gleich graph[j][i]. âNachbarn finden" heiĂt einfach: eine Zeile durchlaufen.
#include <stdio.h>
int main(void) {
// A=0, B=1, C=2, D=3, E=4
int graph[5][5] = {
{0,1,1,0,0}, // A - B,C
{1,0,1,1,0}, // B - A,C,D
{1,1,0,0,1}, // C - A,B,E
{0,1,0,0,1}, // D - B,E
{0,0,1,1,0} // E - C,D
};
char namen[5] = {'A','B','C','D','E'};
// Sind B(1) und D(3) verbunden?
printf("B-D verbunden? %d\n", graph[1][3]); // 1 = ja
// Nachbarn von C(2)
printf("Nachbarn von C: ");
for (int j = 0; j < 5; j++) {
if (graph[2][j] == 1) printf("%c ", namen[j]);
}
printf("\n");
return 0;
}
Erwartete Ausgabe:
B-D verbunden? 1 Nachbarn von C: A B E
struct?Bisher lag dein Graph in mehreren losen Variablen herum: die Matrix hier, die Namen dort, die Knotenzahl irgendwo. Ein struct bĂŒndelt zusammengehörige Daten unter einem Namen â wie ein Steckbrief mit mehreren Feldern.
Auf die Felder greifst du mit dem Punkt-Operator zu: g.n, g.matrix[1][3], g.namen[2]. So reist der ganze Graph als ein Ding durch dein Programm.
struct Graph mit den Feldern n, matrix[5][5] und namen[5].struct Graph g an und fĂŒlle sie mit unserem Beispielgraphen.g.n aus und die Nachbarn von B(1) â diesmal ĂŒber g.matrix und g.namen.void drucke(struct Graph g) vor â du ĂŒbergibst einen Parameter statt drei. In Python/Java wĂ€re struct eine Klasse; in C ist es dasselbe in GrĂŒn, nur ohne Methoden.
#include <stdio.h>
// Ein struct buendelt zusammengehoerige Daten unter EINEM Namen
struct Graph {
int n; // Anzahl Knoten
int matrix[5][5]; // Adjazenzmatrix
char namen[5]; // Knotennamen
};
int main(void) {
struct Graph g = {
5,
{
{0,1,1,0,0},
{1,0,1,1,0},
{1,1,0,0,1},
{0,1,0,0,1},
{0,0,1,1,0}
},
{'A','B','C','D','E'}
};
// Zugriff mit dem Punkt-Operator
printf("Knoten: %d\n", g.n);
printf("Nachbarn von %c: ", g.namen[1]); // B
for (int j = 0; j < g.n; j++) {
if (g.matrix[1][j] == 1) printf("%c ", g.namen[j]);
}
printf("\n");
return 0;
}
Erwartete Ausgabe:
Knoten: 5 Nachbarn von B: A C D
malloc?Bisher kanntest du nur Arrays mit fester GröĂe: int besucht[5]; â die Zahl muss schon beim Schreiben des Programms feststehen. Aber was, wenn die Knotenzahl erst zur Laufzeit bekannt ist (z. B. aus einer Datei)?
malloc (âmemory allocate") fordert genau so viel Speicher an, wie du brauchst, und gibt dir einen Zeiger auf den Anfang zurĂŒck:
Ein besucht[i] funktioniert danach genau wie bei einem normalen Array. Wichtig: Was du mit malloc holst, musst du mit free wieder zurĂŒckgeben â sonst entsteht ein Speicherleck.
n = 5.malloc(n * sizeof(int)) ein besucht-Array an und setze alle EintrÀge auf 0.C(2) als besucht (besucht[2] = 1), gib das Array aus und gib den Speicher mit free wieder frei.<stdlib.h> einbinden (dort wohnen malloc/free), und jedes malloc braucht am Ende sein free. Dieses besucht-Array ist exakt das, was BFS in Aufgabe 6 braucht.
#include <stdio.h>
#include <stdlib.h> // malloc, free
int main(void) {
int n = 5;
// Speicher zur LAUFZEIT anfordern: Platz fuer n int
int *besucht = malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
besucht[i] = 0; // 0 = noch nicht besucht
}
besucht[2] = 1; // Knoten C als besucht markieren
for (int i = 0; i < n; i++) {
printf("besucht[%d] = %d\n", i, besucht[i]);
}
free(besucht); // Speicher IMMER wieder freigeben
return 0;
}
Erwartete Ausgabe:
besucht[0] = 0 besucht[1] = 0 besucht[2] = 1 besucht[3] = 0 besucht[4] = 0
Ziel: das Bild aus der Vorlesung â C âââș A âââș B âââș E âââș â
â in echten Code verwandeln. Eine Adjazenzliste ist fĂŒr jeden Knoten eine kleine verkettete Liste seiner Nachbarn. Hier kommen alle drei neuen Werkzeuge zusammen: struct (ein Listenknoten), malloc (jeden Knoten anlegen) und Zeiger (next zeigt auf den nĂ€chsten).
Ein struct Node *adj[5] ist dann ein Array von ListenanfĂ€ngen: adj[i] zeigt auf den ersten Nachbarn von Knoten i. Das Listenende ist NULL (das â
aus der Vorlesung).
In VL 10 hast du Funktionen gesehen, die einen Zeiger bekommen (z. B. tausche(int *a, int *b) aus Aufgabe 1). Jetzt machen wir den kleinen Schritt weiter: eine Funktion, die einen Zeiger zurĂŒckgibt. Der RĂŒckgabetyp ist dann kein int, sondern struct Node * â âein Zeiger auf einen Node":
Warum ĂŒberhaupt etwas zurĂŒckgeben? Weil sich der Anfang der Liste Ă€ndert: Der frisch per malloc erzeugte Knoten wird zum neuen ersten Element. Die Funktion reicht dir diesen neuen Anfang zurĂŒck, und du speicherst ihn wieder in adj[i]:
Lies das wie x = x + 1: rechts der alte Stand rein, links das Ergebnis wieder zurĂŒck. Mehr ist es nicht â derselbe Zeiger-Typ wie immer, nur diesmal als RĂŒckgabe.
struct Node mit ziel und next.anhaengen(liste, ziel): per malloc einen neuen Knoten anlegen, vorne einhĂ€ngen, neuen Listenanfang zurĂŒckgeben.C(2) die Nachbarn A(0), B(1), E(4) auf und laufe die Liste mit p = p->next durch.->: Wenn p ein Zeiger auf ein struct ist, schreibt man p->ziel statt (*p).ziel. Der Pfeil heiĂt: âfolge dem Zeiger und nimm dann das Feld". Genau dafĂŒr stehen die Pfeile aus der Vorlesung.
n Ă n Speicher â auch bei wenigen Kanten. Die Liste speichert nur, was wirklich da ist. Bei groĂen, âdĂŒnnen" Graphen (wenige Kanten pro Knoten) ist die Liste deutlich sparsamer â der Grund, warum es beide Darstellungen gibt.
#include <stdio.h>
#include <stdlib.h>
// Ein Knoten der verketteten Liste: ein Nachbar + Zeiger auf den naechsten
struct Node {
int ziel; // Index des Nachbarknotens
struct Node *next; // naechster Nachbar (oder NULL = Ende)
};
// Haengt einen neuen Nachbarn VORNE an die Liste an
struct Node* anhaengen(struct Node *liste, int ziel) {
struct Node *neu = malloc(sizeof(struct Node));
neu->ziel = ziel;
neu->next = liste; // neuer Knoten zeigt auf den bisherigen Anfang
return neu; // neuer Anfang
}
int main(void) {
char namen[5] = {'A','B','C','D','E'};
// adj[i] = Anfang der Nachbarliste von Knoten i
struct Node *adj[5] = {NULL, NULL, NULL, NULL, NULL};
// C(2) hat Nachbarn A(0), B(1), E(4)
adj[2] = anhaengen(adj[2], 0);
adj[2] = anhaengen(adj[2], 1);
adj[2] = anhaengen(adj[2], 4);
// Liste von C durchlaufen
printf("Nachbarn von C: ");
for (struct Node *p = adj[2]; p != NULL; p = p->next) {
printf("%c ", namen[p->ziel]);
}
printf("\n");
return 0;
}
Erwartete Ausgabe:
Nachbarn von C: E B A
Hinweis zur Reihenfolge: Wir hĂ€ngen jeden neuen Nachbarn vorne an, deshalb erscheinen sie umgekehrt (E B A statt A B E). FĂŒr einen Graphen ist das egal â die Menge der Nachbarn zĂ€hlt, nicht ihre Reihenfolge.
Ziel: den zentralen Algorithmus der Vorlesung selbst bauen â BFS durchsucht den Graphen wie eine Welle im Wasser: erst alle direkten Nachbarn, dann deren Nachbarn, Schicht fĂŒr Schicht. Das Werkzeug dafĂŒr ist eine Queue (zuerst rein, zuerst raus) plus eine besucht-Liste.
Queue â besucht â zwei getrennte Dinge: Die Queue ist die Warteschlange ânoch abzuarbeiten", besucht verhindert, dass ein Knoten doppelt eingereiht wird.
int queue[5] mit zwei Indizes kopf (lesen) und ende (schreiben).A(0) und gib die Knoten in Besuchsreihenfolge aus.queue[ende++] = x; hÀngt hinten an, int x = queue[kopf++]; nimmt vorne weg. Solange kopf < ende, ist noch etwas drin.
#include <stdio.h>
#define N 5
int main(void) {
int graph[N][N] = {
{0,1,1,0,0},
{1,0,1,1,0},
{1,1,0,0,1},
{0,1,0,0,1},
{0,0,1,1,0}
};
char namen[N] = {'A','B','C','D','E'};
int besucht[N] = {0};
int queue[N];
int kopf = 0, ende = 0; // kopf = lesen, ende = schreiben
int start = 0; // A
besucht[start] = 1;
queue[ende++] = start;
printf("BFS ab A: ");
while (kopf < ende) {
int aktuell = queue[kopf++]; // vorne aus der Queue
printf("%c ", namen[aktuell]);
for (int j = 0; j < N; j++) { // alle Nachbarn
if (graph[aktuell][j] == 1 && besucht[j] == 0) {
besucht[j] = 1; // erst markieren ...
queue[ende++] = j; // ... dann einreihen
}
}
}
printf("\n");
return 0;
}
Erwartete Ausgabe:
BFS ab A: A B C D E
Warum diese Reihenfolge? Schicht 0 = A; Schicht 1 = die direkten Nachbarn B, C; Schicht 2 = deren neue Nachbarn D, E. Die Welle breitet sich aus.
Ziel: den âClou" aus der Vorlesung beweisen â BFS liefert nicht nur eine Reihenfolge, sondern die kĂŒrzeste Distanz (Anzahl Kanten) vom Start zu jedem Knoten. Der Trick: ein dist-Array statt einer reinen Ja/Nein-Markierung.
besucht durch int dist[N], initialisiert mit -1 (= ânoch nicht erreicht", dient gleichzeitig als besucht-Markierung).dist[start] = 0. Beim Einreihen eines Nachbarn: dist[j] = dist[aktuell] + 1;.A aus.#include <stdio.h>
#define N 5
int main(void) {
int graph[N][N] = {
{0,1,1,0,0},
{1,0,1,1,0},
{1,1,0,0,1},
{0,1,0,0,1},
{0,0,1,1,0}
};
char namen[N] = {'A','B','C','D','E'};
int dist[N];
for (int i = 0; i < N; i++) dist[i] = -1; // -1 = noch nicht erreicht
int queue[N];
int kopf = 0, ende = 0;
int start = 0; // A
dist[start] = 0;
queue[ende++] = start;
while (kopf < ende) {
int aktuell = queue[kopf++];
for (int j = 0; j < N; j++) {
if (graph[aktuell][j] == 1 && dist[j] == -1) {
dist[j] = dist[aktuell] + 1; // ein Schritt weiter
queue[ende++] = j;
}
}
}
for (int i = 0; i < N; i++) {
printf("A -> %c: %d Schritte\n", namen[i], dist[i]);
}
return 0;
}
Erwartete Ausgabe:
A -> A: 0 Schritte A -> B: 1 Schritte A -> C: 1 Schritte A -> D: 2 Schritte A -> E: 2 Schritte
D und E sind 2 Schritte weg (z. B. AâBâD bzw. AâCâE) â genau die kĂŒrzeste Kantenzahl.
Die Passwörter stehen unten â frei zugĂ€nglich. Bitte fair spielen: erst selbst implementieren, dann mit der Musterlösung vergleichen. Eine Lösung, die ihr nur abgeschrieben habt, hilft euch in der Klausur nicht.
| Aufgabe | Thema | Passwort |
|---|---|---|
| Aufgabe 1 | Zeiger aufwÀrmen | zeiger |
| Aufgabe 2 | Adjazenzmatrix | matrix |
| Aufgabe 3 | struct â Daten bĂŒndeln | struct |
| Aufgabe 4 | malloc â Speicher zur Laufzeit | malloc |
| Aufgabe 5 | Adjazenzliste (struct + malloc) | liste |
| Aufgabe 6 | BFS mit Queue | bfs |
| Aufgabe 7 | BFS kĂŒrzester Weg (Bonus) | distanz |
Hinweis fĂŒr Python/Java/Rust-Lösungen: die Algorithmen sind 1:1 ĂŒbertragbar â struct wird zur Klasse/zum Objekt, malloc/free entfĂ€llt. Eigene Musterlösungen fĂŒr jede Sprache gibt es nicht.
© 2026 HTW Berlin · Prof. Dr. Alexandra Mikityuk