Zwei Werte tauschen
In a steht 5, in b steht 8, und danach soll es umgekehrt sein. Der naheliegende Versuch a = b, dann b = a geht schief. Erinnere dich an Lektion 2: Eine Zuweisung überschreibt den alten Wert der Variablen links, und der ist danach weg.
Falsch: der alte Wert geht verloren
Richtig: mit Hilfsvariable
| Anweisung | a | b | hilf |
|---|---|---|---|
| Start | 5 | 8 | · |
| hilf = a | 5 | 8 | 5 |
| a = b | 8 | 8 | 5 |
| b = hilf | 8 | 5 | 5 |
Ohne hilf stünde nach a = b in beiden Variablen die 8, und b = a wäre wirkungslos. Der Tausch ist also immer ein Dreischritt:
hilf = a
a = b
b = hilfDie Hilfsvariable heißt in Lösungshinweisen oft hilf, temp oder tausch; der Name ist egal, die drei Zeilen nicht. Bei einem Feld (Array) tauschst du genauso, nur mit indizierten Elementen: hilf = zahlen[i], zahlen[i] = zahlen[i + 1], zahlen[i + 1] = hilf. Genau das braucht das Sortieren.
Bubblesort
Bubblesort sortiert ein Feld aufsteigend nach einer einfachen Idee: Gehe das Feld von vorn nach hinten durch und vergleiche jedes Element mit seinem rechten Nachbarn. Steht links der größere Wert, tausche die beiden. Nach einem solchen Durchlauf ist das größte Element ganz hinten angekommen; es ist wie eine Luftblase nach oben gestiegen, daher der Name. Dann beginnt der nächste Durchlauf, der nur noch bis vor das bereits einsortierte Ende gehen muss.
Struktogramm
Pseudocode
FÜR durchlauf = 1 BIS n - 1
FÜR i = 1 BIS n - durchlauf
WENN zahlen[i] > zahlen[i + 1] DANN
hilf = zahlen[i]
zahlen[i] = zahlen[i + 1]
zahlen[i + 1] = hilf
ENDE WENN
ENDE FÜR
ENDE FÜRZwei Zählschleifen ineinander. Die äußere zählt die Durchläufe: Bei n Elementen reichen n − 1, weil nach jedem Durchlauf ein weiteres Element hinten sicher steht und das letzte übrig bleibende von selbst richtig liegt. Die innere Schleife vergleicht die Nachbarpaare und läuft nur bis n − durchlauf, weil dahinter schon sortiert ist. Der Vergleich zahlen[i] > zahlen[i + 1] greift bis auf zahlen[n - durchlauf + 1] zu, im ersten Durchlauf also bis zum letzten Element; das ist der Grund, warum die innere Grenze eins kleiner sein muss als die Anzahl der noch unsortierten Elemente.
Der Schreibtischtest für zahlen = [5, 2, 4, 1] mit n = 4, das erste Element hat den Index 1. Die Spalte „Feld danach“ zeigt den Zustand nach dem jeweiligen Vergleich:
| durchlauf | i | Vergleich | Tausch? | Feld danach |
|---|---|---|---|---|
| 1 | 1 | 5 > 2 | ja | [2, 5, 4, 1] |
| 1 | 2 | 5 > 4 | ja | [2, 4, 5, 1] |
| 1 | 3 | 5 > 1 | ja | [2, 4, 1, 5] |
| 2 | 1 | 2 > 4 | nein | [2, 4, 1, 5] |
| 2 | 2 | 4 > 1 | ja | [2, 1, 4, 5] |
| 3 | 1 | 2 > 1 | ja | [1, 2, 4, 5] |
Nach Durchlauf 1 steht die 5 hinten, nach Durchlauf 2 die 4 an vorletzter Stelle, nach Durchlauf 3 ist das Feld sortiert. Die innere Schleife hat 3, 2 und 1 Durchläufe, zusammen 6 Vergleiche; allgemein sind es n · (n − 1) / 2. Das musst du nicht herleiten können, aber es erklärt, warum Bubblesort bei großen Feldern langsam ist und in Lehrbüchern und Aufgaben trotzdem verbreitet ist: Er ist kurz und lässt sich gut nachvollziehen.
Unterprogramme: Funktion und Prozedur
Die Grundmuster aus Lektion 5 tauchen in größeren Aufgaben mehrfach auf. Statt sie jedes Mal neu hinzuschreiben, lagerst du sie in ein Unterprogramm aus: ein eigenes Struktogramm mit Namen, das vom Hauptprogramm aufgerufen wird. Zwei Arten gibt es:
- Eine Funktion liefert ein Ergebnis zurück. Die letzte Anweisung ist typischerweise
Rückgabe …, und der Aufruf steht dort, wo man den Wert braucht: in einer Zuweisung oder in einer Bedingung. - Eine Prozedur tut etwas (gibt aus, verändert ein Feld), liefert aber nichts zurück. Sie wird als eigene Anweisung aufgerufen.
Der Titel des Struktogramms ist die Signatur: Name, in Klammern die Parameter mit Datentyp, und bei Funktionen nach dem Doppelpunkt der Rückgabetyp. Parameter sind die Werte, die der Aufrufer hineingibt; im Unterprogramm benutzt du sie wie Variablen.
Struktogramm
Pseudocode
FUNKTION istGerade(zahl: Ganzzahl): Wahrheitswert
WENN zahl MOD 2 == 0 DANN
RÜCKGABE wahr
SONST
RÜCKGABE falsch
ENDE WENN
ENDE FUNKTIONistGerade(7) liefert falsch, istGerade(10) wahr. Kürzer wäre eine einzige Anweisung Rückgabe zahl MOD 2 == 0; beides ist richtig. Die Summe aus Lektion 5 als Funktion mit zwei Parametern:
Struktogramm
Pseudocode
FUNKTION summe(zahlen: Feld, n: Ganzzahl): Ganzzahl
ergebnis = 0
FÜR i = 1 BIS n
ergebnis = ergebnis + zahlen[i]
ENDE FÜR
RÜCKGABE ergebnis
ENDE FUNKTIONBeachte den Namen ergebnis für den Akkumulator: Die Variable darf nicht so heißen wie die Funktion selbst, sonst wird es beim Lesen unklar. Und eine Prozedur, die ein Feld ausgibt:
Struktogramm
Pseudocode
PROZEDUR ausgabeFeld(zahlen: Feld, n: Ganzzahl)
FÜR i = 1 BIS n
AUSGABE zahlen[i]
ENDE FÜR
ENDE PROZEDURDer Aufruf im Hauptprogramm
Für den Aufruf einer Prozedur ist ein eigener Kasten mit doppelten Seitenlinien üblich. Das Ergebnis einer Funktion wird dagegen meist in einer normalen Anweisung verwendet, also zugewiesen oder direkt in einer Bedingung geprüft:
Struktogramm
Pseudocode
ausgabeFeld(zahlen, n)
gesamt = summe(zahlen, n)
AUSGABE gesamt
WENN istGerade(gesamt) DANN
AUSGABE "Summe ist gerade"
SONST
AUSGABE "Summe ist ungerade"
ENDE WENNBeim Aufruf stehen die Argumente in derselben Reihenfolge wie die Parameter in der Signatur: summe(zahlen, n) übergibt das Feld an zahlen und die Anzahl an n. Für zahlen = [4, 9, 2, 7] gibt das Hauptprogramm 4, 9, 2, 7 aus, dann 22, dann „Summe ist gerade“. Ob du den Funktionsaufruf mit Zuweisung als normale Anweisung oder ebenfalls im Aufrufkasten zeichnest, ist in vielen Lösungshinweisen gleichwertig; wichtig ist, dass der Rückgabewert irgendwo landet und nicht verloren geht. Wird ein Feld übergeben, bekommt das Unterprogramm das Feld selbst, keine Kopie: Änderungen an den Elementen, etwa durch ein Sortieren, wirken nach außen und sind nach dem Aufruf im Hauptprogramm sichtbar.
Übungen
Führen Sie einen Schreibtischtest durch: Das Feld zahlen = [7, 3, 9, 1] wird mit dem Bubblesort aus dieser Lektion aufsteigend sortiert; das erste Element hat den Index 1, n = 4. Geben Sie den Zustand des Feldes nach dem ersten und nach dem zweiten Durchlauf der äußeren Schleife an. Notiere jeden Vergleich einzeln.
Musterlösung anzeigen
| durchlauf | i | Vergleich | Tausch? | Feld danach |
|---|---|---|---|---|
| 1 | 1 | 7 > 3 | ja | [3, 7, 9, 1] |
| 1 | 2 | 7 > 9 | nein | [3, 7, 9, 1] |
| 1 | 3 | 9 > 1 | ja | [3, 7, 1, 9] |
| 2 | 1 | 3 > 7 | nein | [3, 7, 1, 9] |
| 2 | 2 | 7 > 1 | ja | [3, 1, 7, 9] |
Nach dem ersten Durchlauf: [3, 7, 1, 9], die 9 ist hinten. Nach dem zweiten Durchlauf: [3, 1, 7, 9], die 7 steht an vorletzter Stelle. Der dritte Durchlauf tauscht noch 3 und 1, Endzustand [1, 3, 7, 9]. Typischer Fehler: im zweiten Durchlauf noch einmal bis i = 3 vergleichen; die innere Schleife läuft nur bis n − 2 = 2.
Ergänzen Sie das Struktogramm an den Stellen (1) und (2), damit es das Feld zahlen mit n Elementen aufsteigend sortiert; das erste Element hat den Index 1. Lücke (1) ist der Kopf der inneren Zählschleife, Lücke (2) die Bedingung, unter der getauscht wird.
Musterlösung anzeigen
(1) für i = 1 bis n - durchlauf: Im ersten Durchlauf bis n − 1, weil der Vergleich auf zahlen[i + 1] zugreift und das bei i = n über das Feldende hinausginge. In jedem weiteren Durchlauf eins weniger, weil hinten schon sortiert ist. (2) zahlen[i] > zahlen[i + 1]: Getauscht wird, wenn links der größere Wert steht; >= in Lücke (2) sortiert ebenfalls, nur mit unnötigen Tauschen gleicher Werte. Gleichwertig, nur langsamer: die innere Schleife immer bis n − 1 laufen zu lassen; falsch wäre eine Grenze n, weil dann zahlen[n + 1] gelesen würde.
Entwerfen Sie die Funktion anzahlGroesser(zahlen: Feld, n: Ganzzahl, grenze: Ganzzahl): Ganzzahl, die zurückgibt, wie viele Elemente des Feldes größer als die Grenze sind; das erste Element hat den Index 1. Entwerfen Sie außerdem ein Hauptprogramm, das eine Grenze einliest, die Funktion aufruft und das Ergebnis ausgibt.
Musterlösung anzeigen
Funktion
Hauptprogramm
Die Funktion ist das Zählmuster aus Lektion 5, nur dass die Grenze als Parameter hereinkommt und die Ausgabe durch Rückgabe anzahl ersetzt ist: Eine Funktion gibt nichts aus, sie liefert. Das Hauptprogramm liest die Grenze ein, ruft die Funktion mit drei Argumenten in der Reihenfolge der Signatur auf, legt das Ergebnis in einer Variablen ab und gibt es aus. Probe mit [4, 9, 2, 7] und grenze = 5: Rückgabe 2.
So wird typischerweise bewertet
- Signatur als Titel mit drei Parametern und Rückgabetyp Ganzzahl
- Zähler ab 0, Zählschleife über alle n Elemente, Verzweigung zahlen[i] > grenze
- Rückgabe des Zählers nach der Schleife, keine Ausgabe in der Funktion
- Hauptprogramm: Eingabe der Grenze, Aufruf mit Argumenten in Signaturreihenfolge, Ergebnis zugewiesen und ausgegeben
- Gleichwertig: andere Schleifenart mit denselben Grenzen (ein Zähler ab 0 nur mit Zugriff zahlen[i + 1]), Aufruf direkt in der Ausgabe ohne Zwischenvariable oder im Aufrufkasten
Entwerfen Sie die Funktion istPrimzahl(zahl: Ganzzahl): Wahrheitswert. Sie liefert wahr, wenn zahl eine Primzahl ist, sonst falsch. Prüfe dazu alle möglichen Teiler von 2 bis zahl − 1 mit MOD. Beachte, dass Zahlen kleiner als 2 keine Primzahlen sind. Kontrolliere deine Lösung mit zahl = 1, 2, 7 und 9.
Musterlösung anzeigen
Eine Primzahl ist nur durch 1 und sich selbst teilbar. Der Merker istPrim startet mit wahr und kippt auf falsch, sobald ein Teiler zwischen 2 und zahl − 1 gefunden wird. Der Sonderfall zahl < 2 wird vorab abgefangen, weil 0 und 1 keine Primzahlen sind. Für zahl = 2 läuft die Schleife „für i = 2 bis 1“ gar nicht, der Merker bleibt wahr, und das ist richtig. Probe: 9 hat den Teiler 3 (9 MOD 3 == 0), Rückgabe falsch; 7 hat keinen Teiler von 2 bis 6, Rückgabe wahr. Gleichwertig ist eine kopfgesteuerte Schleife „solange i <= zahl − 1 UND istPrim“, die nach dem ersten Teiler abbricht; die Zählschleife darf dafür nicht im Rumpf verändert werden.
So wird typischerweise bewertet
- Signatur mit Parameter zahl und Rückgabetyp Wahrheitswert
- Sonderfall zahl < 2 liefert falsch
- Merker mit Startwert wahr, Schleife von 2 bis zahl − 1 mit Prüfung zahl MOD i == 0
- Merker im Ja-Zweig auf falsch, Rückgabe des Merkers nach der Schleife
- Gleichwertig: kopfgesteuerte Schleife mit Abbruch nach dem ersten Teiler, direkte Rückgabe falsch im Ja-Zweig oder Schleife nur bis zur Wurzel von zahl
Jetzt selbst testen
Vier Fragen zu Tauschen, Bubblesort und Unterprogrammen.
a = 3, b = 9. Es werden nacheinander a = b und b = a ausgeführt. Was steht danach in a und b?
Was gilt nach dem ersten Durchlauf der äußeren Schleife eines aufsteigenden Bubblesorts sicher?
Worin unterscheidet sich eine Funktion von einer Prozedur?
Warum läuft die innere Schleife des Bubblesorts nur bis n − durchlauf und nicht bis n?