Algorithmen

Inf-Einf-B · Vorlesung 3, erster Teil

Prof. Dr. Dominik Herrmann

Einführung in die Informatik (Inf-Einf-B)

Otto-Friedrich-Universität Bamberg

WiSe 2026/27

Pause bis 16:15 Uhr

Pause · bis 16:15 Uhr Heute, 23:59 UhrDiese WocheAus dem letzten Jahrgang Übung 1 ist heute fällig. Besteht der Test mit dem Stern,zählt sie für die Joker-Regel. Übung 3 startet heute:Cäsar, Abgabe bis 16. November. Übung 2 läuft bis 9. November.Im Tutorium: Einstieg in Cäsar. Ohne Vorerfahrung, viel KI:1,4 von 5 Übungsaufgabenohne Hilfe zugetraut. Wenig KI: 2,9. Was davon Ursache ist, wissen wir nicht.Versuchen Sie Cäsar erst selbst.

Wie lange dauert das?

Heute

  • Suchenlinear und binär
  • LaufzeitO und Ω
  • Schleifenbreak und continue
  • Eigene Datentypenstruct
  • Im ersten Teil der heutigen Vorlesung haben Sie Programme gelesen und vorhergesagt, was sie tun. Jetzt geht es darum, wie lange ein Programm für seine Arbeit braucht, und wie man das abschätzt, ohne zu messen. Dazu sehen Sie einen Klassiker der Informatik, die Suche in einem Array, und lernen abzuschätzen, wie die Laufzeit wächst, wenn die Eingabe größer wird. Am Ende bauen Sie Ihren ersten eigenen Datentyp.

Wie oft wird strlen aufgerufen?

string s = "Hallo";
for (int i = 0; i < strlen(s); i++)
{
    printf("%c", s[i]);
}

6-mal: Die Bedingung wird vor jedem Durchlauf geprüft, auch vor dem, der nicht mehr stattfindet.

  • Die letzte Quizfrage war: Wie oft wird strlen in dieser Schleife aufgerufen?

    string s = "Hallo";
    for (int i = 0; i < strlen(s); i++)
    {
        printf("%c", s[i]);
    }

    Die Antwort ist 6: Die Bedingung i < strlen(s) wird vor jedem Durchlauf geprüft, auch vor dem, der nicht mehr stattfindet. Fünf Buchstaben, also fünf Durchläufe und eine sechste Prüfung.

Ein langer String

char s[4000001];
...
int zaehler = 0;
for (int i = 0; i < strlen(s); i++)
{
    if (s[i] == 'a')
    {
        zaehler++;
    }
}
$ time ./laenge 250000
250000

real    0m0.595s
  • Macht das einen Unterschied? Bei "Hallo" nicht. Bei einem sehr langen String schon: strlen zählt jedes Mal alle Zeichen bis zum \0, und das in jedem Durchlauf. Das folgende Programm baut einen String aus lauter a, so lang, wie Sie es beim Aufruf angeben, und zählt dann die a. Der String liegt in einem globalen Array oben in der Datei, außerhalb von main. Ein globales Array ist mit Nullen vorbelegt: Das \0 hinter dem letzten a steht also schon da, und vier Millionen Zeichen haben sicher Platz. Wie man so viel Speicher besser anfordert, sehen Sie in Vorlesung 4.

    #include <cs50.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    char s[4000001];
    
    int main(int argc, string argv[])
    {
        // Einen String aus lauter 'a' bauen, so lang wie angegeben
        int laenge = atoi(argv[1]);
        for (int i = 0; i < laenge; i++)
        {
            s[i] = 'a';
        }
    
        // Jedes Zeichen einmal ansehen
        int zaehler = 0;
        for (int i = 0; i < strlen(s); i++)
        {
            if (s[i] == 'a')
            {
                zaehler++;
            }
        }
        printf("%i\n", zaehler);
    }
  • Mit time vor dem Aufruf misst das Terminal, wie lange ein Programm läuft (Zeile real):

    $ time ./laenge 250000
    250000
    
    real    0m0.595s

Wie lange dauert eine Million?

Zeichen Zeit 250 000 0,6 s 500 000 2,4 s 1 000 000 A 5 · B 10 · C 20 s9,6 s 2 000 000 ?etwa 38 s
  • Gemessen auf dem Rechner der Vorlesung (im Kurs-Image):

    Zeichen i < strlen(s) n = strlen(s) (siehe unten)
    250 000 0,6 s wenige Tausendstel
    500 000 2,4 s wenige Tausendstel
    1 000 000 9,6 s wenige Tausendstel
    2 000 000 etwa 38 s wenige Tausendstel

    Doppelt so viele Zeichen, viermal so lange. Bei n Zeichen läuft die Schleife n-mal, und jeder Aufruf von strlen zählt wieder n Zeichen: zusammen etwa n · n Schritte.

Die Länge einmal vorher

for (int i = 0, n = strlen(s); i < n; i++)

Erster Teil: läuft einmal und legt i und n an.

Bedingung: vor jedem Durchlauf, nur noch i < n.

$ time ./laenge1 2000000
2000000

real    0m0.003s
  • Die Abhilfe stand schon in Notes 2 (Abschnitt Stringlänge), hier noch einmal Schritt für Schritt. Der Kopf einer for-Schleife hat drei Teile, getrennt durch Semikolons. Der erste Teil läuft genau einmal, vor dem ersten Durchlauf. Die Bedingung in der Mitte prüft das Programm vor jedem Durchlauf neu. Steht strlen(s) dort, zählt es also vor jedem Durchlauf wieder alle Zeichen.

  • Deshalb kommt der Aufruf in den ersten Teil: Die Länge wird einmal vor der Schleife bestimmt und in n gemerkt.

    for (int i = 0, n = strlen(s); i < n; i++)

    Im ersten Teil dürfen Sie mehrere Variablen desselben Typs anlegen, durch ein Komma getrennt: hier i mit 0 und n mit der Länge. Die Bedingung vergleicht danach nur noch zwei Zahlen. Jetzt ruft das Programm strlen genau einmal auf. Eine Million Zeichen dauern nur noch wenige Tausendstel Sekunden. Die Zeit steigt jetzt nur noch so wie die Länge: Doppelt so viele Zeichen, doppelt so lange, aber auf dem Niveau von Tausendsteln.

  • Moderne Compiler können so einen Aufruf oft selbst aus der Schleife ziehen, wenn man sie optimieren lässt (-O2). make übersetzt in unserer Kursumgebung ohne Optimierung, deshalb sehen Sie den Unterschied hier so deutlich. Verlassen sollten Sie sich darauf ohnehin nicht.

  • Wie man so etwas abschätzt, ohne zu messen, sehen Sie im Abschnitt Laufzeit. Mit time messen Sie in Übung 4 auch die Sortierprogramme.

Logarithmisch zählen

Drei Arten zu suchen

0 1 0 1 Größe des Problems Zeit n n/2 log₂ n
  • In Vorlesung 0 haben Sie drei Arten gesehen, in einem Telefonbuch einen Namen zu finden: Seite für Seite blättern, immer zwei Seiten auf einmal, oder das Buch in der Mitte aufschlagen und die falsche Hälfte wegwerfen. Wie viele Schritte das jeweils braucht, hängt von der Zahl der Seiten n ab:

    Seiten n Seite für Seite zwei auf einmal Halbierungen
    8 8 4 3
    16 16 8 4
    32 32 16 5

    Beim Halbieren kommt mit jeder Verdopplung von n nur ein Schritt dazu. Die Zahl der Schritte ist dann der Logarithmus zur Basis 2 von n, kurz log₂ n: wie oft man n halbieren kann, bis 1 übrig bleibt.

Wie viele sind wir?

  • 1.Stehen Sie auf und denken Sie an die Zahl 1.
  • 2.Bilden Sie ein Paar mit jemandem, der steht. Addieren Sie beide Zahlen, merken Sie sich die Summe.
  • 3.Eine Person des Paares setzt sich.
  • 4.Stehen Sie noch? Zurück zu 2.
  • Dasselbe Prinzip funktioniert im Hörsaal. Um herauszufinden, wie viele Personen im Raum sind, gehen alle so vor:

    1. Stehen Sie auf und denken Sie an die Zahl 1.
    2. Bilden Sie ein Paar mit jemandem, der steht, addieren Sie beide Zahlen und merken Sie sich die Summe.
    3. Eine Person des Paares setzt sich hin.
    4. Wenn Sie noch stehen, gehen Sie zurück zu Schritt 2.

Jede Runde halbiert.

110 Personen: etwa 7 Runden (2⁷ = 128). Doppelt so viele: eine Runde mehr.

  • Am Ende steht eine Person und kennt die Gesamtzahl. In jeder Runde setzt sich etwa die Hälfte. Bei 110 Personen sind das ungefähr 7 Runden (2⁷ = 128), bei 220 nur eine mehr. Durchzählen hätte 110 Schritte gedauert. Das geht nur so schnell, weil alle gleichzeitig addieren: Additionen sind es zusammen immer noch 109. Ein Computer, der allein zählt, braucht n Schritte.

Lineare Suche

Ist die 50 dabei?

?20 ?500 ?10 ?5 ?100 ?1 ?50 0 1 2 3 4 5 6 Eingabe: Kisten und 50 → Suche → true oder false
  • Stellen Sie sich sieben Kisten nebeneinander vor, in jeder liegt ein Geldschein. Sie wollen wissen: Ist die 50 dabei? Von außen sehen Sie nichts, Sie müssen eine Kiste öffnen, um hineinzuschauen. Genau so geht es einem Computer mit einem Array: Er kann immer nur ein Element ansehen.
  • Herauszufinden, ob ein Wert in einem Array vorkommt, ist ein Standardproblem der Informatik: die Suche. Die Eingabe ist das Array und der gesuchte Wert, die Ausgabe ist true oder false.

Lineare Suche als Pseudocode

Für jede Kiste von links nach rechts
    Wenn 50 in der Kiste ist
        Gib true zurück
Gib false zurück
Für i von 0 bis n-1
    Wenn 50 in kisten[i] ist
        Gib true zurück
Gib false zurück
  • Die einfachste Lösung: Kiste für Kiste von links nach rechts öffnen, bis die 50 auftaucht. Das heißt lineare Suche. Als Pseudocode, also als für Menschen lesbare Anleitung, die schon nach Programm aussieht:

    Für jede Kiste von links nach rechts
        Wenn 50 in der Kiste ist
            Gib true zurück
    Gib false zurück

    Mit Array-Schreibweise, wie Sie es gleich in C programmieren:

    Für i von 0 bis n-1
        Wenn 50 in kisten[i] ist
            Gib true zurück
    Gib false zurück

Was ist hier falsch?

Für jede Kiste von links nach rechts
    Wenn 50 in der Kiste ist
        Gib true zurück
    Sonst
        Gib false zurück

Gibt nach der ersten Kiste auf: Gib false zurück gehört hinter die Schleife.

  • Das Gib false zurück steht nach der Schleife. Eine häufige falsche Fassung sieht so aus:

    Für jede Kiste von links nach rechts
        Wenn 50 in der Kiste ist
            Gib true zurück
        Sonst
            Gib false zurück

    Sie gibt schon nach der ersten Kiste auf: Liegt dort nicht die 50, kommt false heraus, ohne dass die anderen Kisten geöffnet wurden.

  • Im schlimmsten Fall liegt die 50 in der letzten Kiste oder gar nicht da. Dann öffnet die lineare Suche alle n Kisten.

Binäre Suche

Die Kisten sind sortiert

1 5 10 20 50 100 500 0 1 2 3 4 5 6 50 > 20: rechts weiter 50 < 100: links weiter gefunden, 3 Kisten
  • Jetzt liegen die Scheine sortiert in den Kisten, vom kleinsten zum größten:

    Index 0 1 2 3 4 5 6
    Schein 1 5 10 20 50 100 500

    Dann geht es schneller, wie beim Telefonbuch: Öffnen Sie die mittlere Kiste (Index 3, die 20). Die 50 ist größer, also kann sie nur rechts liegen; die linke Hälfte fällt weg. In den Kisten 4 bis 6 ist die mittlere die 100. Die 50 ist kleiner, also bleibt nur Kiste 4: die 50. Drei Kisten statt sieben. Das ist die binäre Suche.

Binäre Suche als Pseudocode

Wenn 50 in der mittleren Kiste ist
    Gib true zurück
Sonst wenn 50 < mittlere Kiste
    Durchsuche die linke Hälfte
Sonst wenn 50 > mittlere Kiste
    Durchsuche die rechte Hälfte
Wenn keine Kisten übrig sind
    Gib false zurück
Wenn 50 in der mittleren Kiste ist
    Gib true zurück
Sonst wenn 50 < mittlere Kiste
    Durchsuche die linke Hälfte
Sonst wenn 50 > mittlere Kiste
    Durchsuche die rechte Hälfte
  • Als Pseudocode:

    Wenn keine Kisten übrig sind
        Gib false zurück
    Wenn 50 in der mittleren Kiste ist
        Gib true zurück
    Sonst wenn 50 < mittlere Kiste
        Durchsuche die linke Hälfte
    Sonst wenn 50 > mittlere Kiste
        Durchsuche die rechte Hälfte

    Die erste Zeile ist wichtig: Ohne sie würde der Algorithmus nie aufhören, wenn die 50 gar nicht da ist. Mit Array-Schreibweise werden aus den Hälften Bereiche von Indizes. Zwei Grenzen merken sich, welcher Bereich noch übrig ist: Am Anfang ist links 0 und rechts n − 1.

    Solange links <= rechts
        mitte = (links + rechts) / 2
        Wenn 50 in kisten[mitte] ist
            Gib true zurück
        Sonst wenn 50 < kisten[mitte]
            rechts = mitte - 1
        Sonst
            links = mitte + 1
    Gib false zurück

    „Keine Kisten übrig“ heißt jetzt: links ist größer als rechts. Bei einer geraden Zahl von Kisten gibt es zwei mittlere; (links + rechts) / 2 rundet als Ganzzahldivision ab und nimmt die linke.

  • Bei jeder geöffneten Kiste fällt die Hälfte der übrigen weg. Die binäre Suche öffnet auch die letzte übrige Kiste noch: Bei n Kisten sind es höchstens k Blicke, wenn 2^k größer als n ist. Bei 7 Kisten also höchstens 3, bei 8 schon 4, bei 15 höchstens 4. Das ist dasselbe Prinzip wie beim Zählen im Hörsaal.

  • Dieses Vorgehen heißt Teile und herrsche (Vorlesung 0): Das Problem wird in jedem Schritt kleiner, und auf dem kleineren Stück macht man dasselbe noch einmal. Wie man „mach dasselbe auf der Hälfte noch einmal“ direkt in C schreibt, sehen Sie im zweiten Teil (Rekursion).

Wo ist die 50?

Noch einmal. Die Kisten sind wieder sortiert.

Sie waren es nicht.

Die binäre Suche prüft das nie, sie vertraut Ihnen.

  • In der Vorlesung lagen die Kisten beim zweiten Durchgang so: 1, 50, 10, 20, 5, 100, 500. Zwei Scheine waren vertauscht. Die binäre Suche nach der 50 öffnet die 20 (rechts weiter), die 100 (links weiter) und die 5. Dann ist keine Kiste mehr übrig: „nicht gefunden“, obwohl die 50 in der zweiten Kiste liegt.
  • Die binäre Suche prüft nie, ob die Kisten wirklich sortiert sind. Sie geht davon aus. Stimmt die Voraussetzung nicht, liefert sie falsche Antworten, ohne Fehlermeldung. Wer die binäre Suche nutzen will, muss also vorher sortieren. Wie man sortiert und was das kostet, sehen Sie im zweiten Teil.

Jetzt Sie: Binäre Suche von Hand 3 Minuten, auf Papier, Laptop zu. Wählen Sie die Stufe, die Sie schaffen.

  • Stufe 13 8 15 23 42 57 91 Gesucht: 15. Welche Zahlen sehen Sie nacheinander an?
  • Stufe 22 4 7 11 16 22 29 37 46 56 67 79 92 106 121 Gesucht: 40. Welche Zahlen, und woran merken Sie, dass die 40 fehlt?
  • Stufe 3Wie viele Zahlen müssen Sie bei 1000 sortierten Zahlen höchstens ansehen? Bei einer Million?

Übung: Binäre Suche von Hand

Wählen Sie die Stufe, die Sie schaffen; alle drei sind gleich viel wert. Ohne Laptop, auf Papier.

  1. Stufe 1: In 3 8 15 23 42 57 91 suchen Sie mit der binären Suche die 15. Welche Zahlen sehen Sie nacheinander an?
  2. Stufe 2: In 2 4 7 11 16 22 29 37 46 56 67 79 92 106 121 (15 Zahlen) suchen Sie die 40. Welche Zahlen sehen Sie an, und woran merken Sie, dass die 40 fehlt?
  3. Stufe 3: Wie viele Zahlen müssen Sie bei 1000 sortierten Zahlen höchstens ansehen? Und bei einer Million?

Auflösung: Binäre Suche von Hand

  • Stufe 1In 3 8 15 23 42 57 91 die 15 suchen: 23, 8, 15
  • Stufe 2In 2 4 7 11 16 22 29 37 46 56 67 79 92 106 121 die 40 suchen: 37, 79, 56, 46. Links von der 46 ist nichts mehr übrig: Die 40 fehlt.
  • Stufe 3Höchstens wie viele Blicke bei 1000 Zahlen, bei einer Million? 10 und 20. Jeder Blick halbiert: 2 hoch 10 = 1024, 2 hoch 20 ≈ 1 Million.

Stufe 1. 23, 8, 15. Die Mitte der 7 Zahlen ist die 23; 15 ist kleiner, also bleiben 3 8 15. Deren Mitte ist die 8; 15 ist größer, also bleibt die 15.

Stufe 2. 37, 79, 56, 46, dann ist kein Bereich mehr übrig. Die Mitte der 15 Zahlen ist die 37 (40 ist größer, rechts weiter: 46 … 121). Mitte 79 (kleiner, links: 46 56 67). Mitte 56 (kleiner, links: 46). 46 ist nicht 40, und links von der 46 ist nichts mehr: Die 40 kommt nicht vor.

Stufe 3. 10 bei 1000 Zahlen und 20 bei einer Million. Jeder Blick halbiert den Rest: Nach 10 Blicken ist von 1000 Zahlen nichts mehr übrig, weil 2¹⁰ = 1024 größer als 1000 ist. Bei einer Million: 2²⁰ = 1 048 576.

Laufzeit

Die Form der Kurve zählt

  • GefragtWie wächst die Zahl der Schritte, wenn die Eingabe wächst?
  • EgalKonstanten: n und n/2 sind beide linear.
  • Egaldie Basis des Logarithmus: O(log n).
  • Wie lange ein Programm in Sekunden braucht, hängt vom Rechner ab. In der Informatik interessiert deshalb eine andere Frage: Wie wächst die Zahl der Schritte, wenn die Eingabe größer wird? Man nennt das die Ordnung der Laufzeit und schreibt sie in der O-Notation.
  • Dabei zählt nur die Form der Kurve, also der Teil, der am schnellsten wächst (bei n² + n nur n²). Ob ein Verfahren n oder n/2 Schritte braucht: Beides wächst linear, beides ist O(n). Auch die Basis des Logarithmus spielt keine Rolle, man schreibt einfach O(log n).

Laufzeitklassen

Klasse Beispiel O(n²) strlen in der Bedingung O(n log n) nächste Woche O(n) lineare Suche O(log n) binäre Suche O(1) kisten[3] lesen , n = strlen(s)
  • Die wichtigsten Klassen, von langsam nach schnell:

    Klasse Name Beispiel
    O(n²) quadratisch strlen in der Bedingung der Schleife (oben gemessen)
    O(n log n) n log n kommt im zweiten Teil (Merge Sort)
    O(n) linear lineare Suche; die Schleife mit n = strlen(s)
    O(log n) logarithmisch binäre Suche
    O(1) konstant ein Element eines Arrays über seinen Index lesen
  • Zwei ineinander geschachtelte Schleifen über n Elemente brauchen n · n Schritte, also O(n²). So eine Schleife steckt auch in strlen in der Bedingung: Die äußere ist Ihre for-Schleife, die innere zählt strlen. Beim Sortieren im zweiten Teil sehen Sie weitere.

  • O gibt eine Obergrenze an: So lange dauert es höchstens. Wir verwenden sie für den schlechtesten Fall (Worst Case). Die lineare Suche braucht im schlechtesten Fall n Schritte, die binäre Suche etwa log₂ n.

Der beste Fall: Ω

  • Ohöchstens: Obergrenze, für den schlechtesten Fall
  • Ωmindestens: Untergrenze, für den besten Fall
  • Lineare SucheO(n), Ω(1)
  • Binäre SucheO(log n), Ω(1)
  • Genauso wichtig ist der beste Fall (Best Case). Liegt die 50 gleich in der ersten Kiste, ist die lineare Suche nach einem Schritt fertig. Für eine Untergrenze (so schnell geht es mindestens) schreibt man den griechischen Buchstaben Ω (Omega); wir verwenden sie für den besten Fall: Die lineare Suche ist Ω(1), die binäre Suche auch (die 50 liegt genau in der Mitte). Sind Ober- und Untergrenze gleich, schreibt man Θ (Theta). Im schlechtesten Fall ist die binäre Suche viel schneller, deshalb nimmt man sie, wenn die Daten schon sortiert sind. Im zweiten Teil sehen Sie Sortierverfahren, bei denen bester und schlechtester Fall weit auseinanderliegen.

continue

Was gibt dieses Programm aus?

int scheine[] = {20, 500, 10, 5, 100, 1, 50};

int summe = 0;
for (int i = 0; i < 7; i++)
{
    if (scheine[i] < 10)
    {
        continue;
    }
    summe += scheine[i];
}
printf("%i\n", summe);

A 686 · B 680 · C 530

B: 680, denn die 5 und die 1 überspringt continue.

  • Neben break gibt es continue: Es überspringt nur den Rest dieses einen Durchlaufs und macht mit dem nächsten weiter. Das folgende Programm zählt die Scheine aus den Kisten zusammen, lässt aber das Kleingeld unter 10 aus:

    #include <stdio.h>
    
    int main(void)
    {
        int scheine[] = {20, 500, 10, 5, 100, 1, 50};
    
        int summe = 0;
        for (int i = 0; i < 7; i++)
        {
            if (scheine[i] < 10)
            {
                continue;
            }
            summe += scheine[i];
        }
        printf("%i\n", summe);
    }
  • Es gibt 680 aus: 20 + 500 + 10 + 100 + 50. Bei der 5 und der 1 springt continue zum nächsten Durchlauf, die Zeile summe += scheine[i]; läuft für sie nicht.

break oder continue

Anweisung beendet continue diesen Durchlauf break die Schleife (hier: 530) return die Funktion (in main: das Programm)
  • Mit break statt continue käme 530 heraus (20 + 500 + 10): Bei der 5 endet die ganze Schleife.

  • Zusammengefasst:

    Anweisung Was sie beendet
    continue den aktuellen Durchlauf der Schleife, weiter mit dem nächsten
    break die ganze Schleife, weiter hinter der Schleife
    return die ganze Funktion (in main: das Programm)
  • continue und break machen eine Schleife oft kürzer, aber nicht immer leichter zu lesen. Häufig lässt sich dasselbe auch mit einem if ausdrücken (hier: if (scheine[i] >= 10)).

Wörter suchen

Welche Bedingung findet Hund?

string strings[] = {"Schiff", "Auto", "Hund", "Katze", "Hut"};

// A
if (strings[i] == s)

// B
if (strcmp(strings[i], s))

// C
if (strcmp(strings[i], s) == 0)

C ist richtig. A findet nie etwas: == taugt für Strings nicht.

B findet alles: wahr, sobald die Wörter verschieden sind.

  • Was, wenn die Kisten Wörter enthalten? Dann ändert sich nur die Bedingung: Statt zwei Zahlen vergleicht das Programm zwei Strings.

  • Dafür gibt es die Funktion strcmp (string compare) aus string.h. Sie kam in Notes 2 schon vor (Abschnitt Stringlänge), hier noch einmal: strcmp geht beide Strings Zeichen für Zeichen durch und gibt eine Zahl zurück, kein true oder false.

    strcmp("Hund", "Hund")     // 0: gleich
    strcmp("Hund", "Katze")    // negativ: "Hund" kommt vorher
    strcmp("Katze", "Hund")    // positiv: "Katze" kommt später

    Gleich heißt 0. Vorher und später richten sich nach den ASCII-Werten der Zeichen.

  • Welche dieser Bedingungen findet das Wort?

    // A
    if (strings[i] == s)
    
    // B
    if (strcmp(strings[i], s))
    
    // C
    if (strcmp(strings[i], s) == 0)
#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string strings[] = {"Schiff", "Auto", "Hund", "Katze", "Hut"};

    string s = get_string("Wort: ");
    for (int i = 0; i < 5; i++)
    {
        if (strcmp(strings[i], s) == 0)
        {
            printf("Gefunden\n");
            return 0;
        }
    }
    printf("Nicht gefunden\n");
    return 1;
}
  • Nur C ist richtig. strcmp gibt 0 zurück, wenn beide Strings gleich sind.
  • A findet nie etwas: == vergleicht bei Strings, wo sie im Speicher stehen, nicht welche Zeichen darin stehen.
  • B findet alles, auch Maus: Ohne == 0 ist die Bedingung wahr, sobald strcmp nicht 0 zurückgibt, also sobald die Wörter verschieden sind. Schon beim ersten Wort im Array, das nicht passt, meldet das Programm Gefunden. Der Compiler warnt bei A und B nicht.
  • Warum gibt strcmp bei Gleichheit 0 zurück und nicht true? Weil es mehr kann: Das Vorzeichen sagt, welcher String zuerst kommt. Das ist nützlich zum Sortieren. Weil Großbuchstaben in ASCII vor den Kleinbuchstaben kommen, steht "Zebra" dabei vor "apfel".

Zwei gleiche Wörter

string s = get_string("s: ");    // Hallo
string t = get_string("t: ");    // Hallo
if (s == t)
{
    printf("Gleich\n");
}

Gibt nichts aus: == vergleicht bei Strings nicht die Zeichen.

Warum, sehen wir in ein paar Wochen.

  • Warum findet A nie etwas? Ein kleineres Beispiel: Zweimal Hallo eingeben, und trotzdem ist s == t falsch.

    string s = get_string("s: ");    // Hallo
    string t = get_string("t: ");    // Hallo
    if (s == t)
    {
        printf("Gleich\n");
    }
  • == vergleicht bei Strings nicht die Zeichen, sondern wo die Strings im Speicher stehen. Was das genau heißt, kommt in ein paar Wochen, wenn es um den Speicher geht. Bis dahin: Strings vergleichen Sie immer mit strcmp.

Strings vergleichen: strcmp

#include <string.h>

strcmp("Hund", "Hund")     // 0: gleich
strcmp("Hund", "Katze")    // negativ: "Hund" kommt vorher
strcmp("Katze", "Hund")    // positiv: "Katze" kommt später

Gleich heißt 0. Zeichen für Zeichen, nach ASCII.

Alles außer 0 ist wahr: Darum findet B alles.

Telefonbuch

Ein Telefonbuch mit zwei Arrays

string names[] = {"Office", "Dominik", "Bittle"};
string numbers[] = {"+49-951-863-2795",
                    "+49-951-863-2661",
                    "+49-951-863-2661"};

for (int i = 0; i < 3; i++)
{
    if (strcmp(names[i], name) == 0)
    {
        printf("Gefunden: %s\n", numbers[i]);
        return 0;
    }
}
  • Oft sucht man etwas, um an das zu kommen, was dazugehört: In einem Telefonbuch sucht man einen Namen und will die Nummer. Mit dem, was Sie bisher kennen, braucht das zwei Arrays, eines für die Namen und eines für die Nummern. Was zusammengehört, steht an derselben Stelle.
#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    string names[] = {"Office", "Dominik", "Bittle"};
    string numbers[] = {"+49-951-863-2795", "+49-951-863-2661", "+49-951-863-2661"};

    string name = get_string("Name: ");
    for (int i = 0; i < 3; i++)
    {
        if (strcmp(names[i], name) == 0)
        {
            printf("Gefunden: %s\n", numbers[i]);
            return 0;
        }
    }
    printf("Nicht gefunden\n");
    return 1;
}
  • Die Telefonnummern sind Strings und keine Zahlen: Sie enthalten + und -, eine führende 0 ginge bei einem int verloren, und rechnen will mit ihnen niemand.

Was passiert beim Sortieren?

Name und Nummer hängen nur über den Index zusammen.

  • Das Programm funktioniert, aber das Design ist schlecht (man nennt so etwas einen Code Smell). Name und Nummer hängen nur über den Index zusammen. Sortiert jemand die Namen alphabetisch und vergisst die Nummern, passt nichts mehr zusammen, und kein Compiler bemerkt es.
  • In der Vorlesung haben wir das mit drei Geldscheinen gezeigt, die Namen auf Zetteln darunter: nach Betrag sortiert, aber die Zettel blieben liegen, und plötzlich hatte Anna nur noch 5.
  • Schöner wäre ein Datentyp, in dem Name und Nummer zusammen stecken, so wie ein Zettel, der auf dem Schein klebt.

structs

Ein eigener Datentyp: person

typedef struct
{
    string name;
    string number;
}
person;
person people[3];
people[0].name = "Office";
people[0].number = "+49-951-863-2795";
  • In C können Sie eigene Datentypen anlegen, die mehrere Werte zusammenfassen. Für das Telefonbuch ist das eine person mit einem Namen und einer Nummer:

    typedef struct
    {
        string name;
        string number;
    }
    person;

    struct heißt: ein Datentyp, der aus mehreren Feldern (englisch members) besteht, hier name und number. typedef gibt ihm einen Namen, person. Diese Zeilen stehen oben in der Datei, vor main. Danach ist person ein Typ wie int oder string: Sie können Variablen und Arrays davon anlegen.

Das Telefonbuch mit person

string name = get_string("Name: ");
for (int i = 0; i < 3; i++)
{
    if (strcmp(people[i].name, name) == 0)
    {
        printf("Gefunden: %s\n", people[i].number);
        return 0;
    }
}
#include <cs50.h>
#include <stdio.h>
#include <string.h>

typedef struct
{
    string name;
    string number;
}
person;

int main(void)
{
    person people[3];

    people[0].name = "Office";
    people[0].number = "+49-951-863-2795";

    people[1].name = "Dominik";
    people[1].number = "+49-951-863-2661";

    people[2].name = "Bittle";
    people[2].number = "+49-951-863-2661";

    string name = get_string("Name: ");
    for (int i = 0; i < 3; i++)
    {
        if (strcmp(people[i].name, name) == 0)
        {
            printf("Gefunden: %s\n", people[i].number);
            return 0;
        }
    }
    printf("Nicht gefunden\n");
    return 1;
}
  • person people[3]; legt ein Array aus drei Personen an. Auf die Felder greifen Sie mit der Punktschreibweise zu: people[0].name ist der Name der Person an Stelle 0, people[0].number ihre Nummer.
  • Jetzt gehören Name und Nummer zusammen. Wer people sortiert, verschiebt immer ganze Personen, und die Nummer wandert mit.
  • Ein struct lässt sich leicht erweitern: Ein weiteres Feld (etwa eine E-Mail-Adresse) kommt in die Definition, der Rest des Programms bleibt, wie er ist.

Was gehört in einen song?

typedef struct
{
    ...
}
song;
typedef struct
{
    string titel;
    string kuenstler;
    int jahr;
}
song;
  • Was gehört in einen Datentyp für Songs? Ein Titel, wer ihn singt, das Jahr:

    typedef struct
    {
        string titel;
        string kuenstler;
        int jahr;
    }
    song;

    Die Felder eines struct dürfen verschiedene Typen haben, hier zweimal string und einmal int.

Wie viele Zeilen gibt playlist aus?

Bohemian Rhapsody 1975 · Flowers 2023 · Blinding Lights 2019

for (int i = 0; i < 3; i++)
{
    if (playlist[i].jahr > 2020)
    {
        printf("%s\n", playlist[i].titel);
    }
}

A 1 · B 2 · C 3

A. Nur Flowers ist nach 2020 erschienen.

#include <cs50.h>
#include <stdio.h>

typedef struct
{
    string titel;
    string kuenstler;
    int jahr;
}
song;

int main(void)
{
    song playlist[3];

    playlist[0].titel = "Bohemian Rhapsody";
    playlist[0].kuenstler = "Queen";
    playlist[0].jahr = 1975;

    playlist[1].titel = "Flowers";
    playlist[1].kuenstler = "Miley Cyrus";
    playlist[1].jahr = 2023;

    playlist[2].titel = "Blinding Lights";
    playlist[2].kuenstler = "The Weeknd";
    playlist[2].jahr = 2019;

    for (int i = 0; i < 3; i++)
    {
        if (playlist[i].jahr > 2020)
        {
            printf("%s\n", playlist[i].titel);
        }
    }
}
  • Das Programm gibt nur eine Zeile aus, Flowers: Nur dieser Song ist nach 2020 erschienen.

Zusammenfassung

Wer sortiert die Kisten?

Die binäre Suche braucht sortierte Kisten. Wie sortiert man sie, und was kostet das?

In diesem Teil haben Sie algorithmisch gedacht und Ihren ersten eigenen Datentyp gebaut.

Dies war Inf-Einf-B.

Übung 3 startet heute: Cäsar, Abgabe bis Montag, 16. November.

Übung 2 läuft: Scrabble und Lesbarkeit, Abgabe bis Montag, 9. November.

Shorts 3.1 (lineare Suche) und 3.2 (binäre Suche).

Sie haben gelernt:

  • dass strlen in der Bedingung einer Schleife bei jedem Durchlauf neu zählt und eine Schleife so von O(n) zu O(n²) macht;
  • wie die lineare Suche funktioniert und warum sie im schlechtesten Fall jedes Element ansieht;
  • wie die binäre Suche ein sortiertes Array halbiert, warum sie ein sortiertes Array braucht und wie Sie sie von Hand durchspielen;
  • wie man die Laufzeit in Klassen einteilt (O(1), O(log n), O(n), O(n log n), O(n²)), dass man mit O angibt, wie lange es höchstens dauert (schlechtester Fall), und mit Ω, wie schnell es mindestens geht (bester Fall);
  • wie Sie eine Schleife mit break verlassen, mit continue einen Durchlauf überspringen und mit return die ganze Funktion beenden;
  • dass Strings mit strcmp(…) == 0 verglichen werden;
  • wie Sie mit typedef struct eigene Datentypen anlegen und mit der Punktschreibweise auf ihre Felder zugreifen.

Sortieren (Selection Sort, Bubble Sort, Merge Sort) und Rekursion folgen im zweiten Teil der Vorlesung.

overview · drag pans · wheel zooms · ↑↓←→ selects · click or O/Enter lands · / search · Esc leaves