English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

nu

Gast
1 / ?
zurück zu den Lektionen

Was macht eine Liste nützlich

Listen: Deine erste Datenstruktur

Eine Liste (in manchen Sprachen Array genannt) speichert Elemente in einer bestimmten Reihenfolge. Du kannst:

- Auf jedes Element nach seiner Position zugreifen: names[0] greift das erste Element. O(1) — augenblicklich.

- Elemente am Ende hinzufügen: names.append("Zoe"). O(1) — augenblicklich.

- Alle Elemente der Reihe nach durchgehen: for name in names. O(N) — besucht jedes Element einmal.

Listen bewahren die Reihenfolge, in der du Dinge eingegeben hast. Das erste Element bleibt das erste. Das letzte bleibt das letzte.

# Eine Liste mit Haustieren
pets = ["cat", "dog", "cat", "fish", "dog"]

print(pets[0])   # "cat"  — O(1) augenblicklich
print(pets[3])   # "fish" — O(1) augenblicklich
print(len(pets)) # 5 — Duplikate werden beibehalten

Beachte: "cat" erscheint zweimal. "dog" erscheint zweimal. Listen erlauben Duplikate. Sie speichern genau das, was du ihnen gibst, in der Reihenfolge, wie du es eingegeben hast.

Aber Listen haben eine Schwäche. Schau, was passiert, wenn du fragst: Ist "fish" in dieser Liste?

# Zugehörigkeitsprüfung — ist "fish" in der Liste?
if "fish" in pets:    # durchsucht ["cat", "dog", "cat", "fish"...] nacheinander
    print("found!")   # musste 4 Elemente prüfen, bevor es gefunden wurde

Der Computer prüft "cat" — nein. "dog" — nein. "cat" — nein. "fish" — ja! Er hat 4 Elemente durchsucht, um die Antwort zu finden. Bei 1000 Elementen könnte er alle 1000 durchsuchen. Diese Zugehörigkeitsprüfung kostet O(N).

Vorhersage der Scan-Kosten

Du hast eine Liste mit 500 Schülernamen in zufälliger Reihenfolge.

Du möchtest überprüfen, ob "Zara" in der Liste vorkommt.

students = ["Alex", "Maria", ..., "Zara", ...]  # 500 Namen
if "Zara" in students:
    print("enrolled")
Was ist der beste Fall, der schlechteste Fall & die durchschnittliche Anzahl von Namen, die der Computer durchsuchen muss, um "Zara" zu finden? Welche Big-O-Klasse beschreibt diese Operation? Erkläre deine Überlegung.

Wie Mengen anders funktionieren

Mengen: Optimiert für Zugehörigkeitsfragen

Eine Menge speichert eindeutige Elemente mit einer Technik namens Hashing. Wenn du ein Element hinzufügst, berechnet der Computer einen Hash (einen numerischen Fingerabdruck), der ihm genau sagt, wo dieses Element gespeichert wird.

Wenn du die Zugehörigkeit prüfst, berechnet der Computer denselben Hash & springt direkt zur richtigen Stelle. Keine Suche.

Liste vs. Menge: wie Daten im Speicher leben — Liste durchsucht, Menge springt

# Eine Menge mit Haustieren
pets = {"cat", "dog", "cat", "fish", "dog"}

print(pets)      # {"cat", "dog", "fish"} — Duplikate wurden entfernt
print(len(pets)) # 3 — nur eindeutige Elemente

Beachte drei Unterschiede zu Listen:

1. Keine Duplikate. "cat" erschien zweimal in der Eingabe, aber die Menge behält nur eine Kopie.

2. Keine garantierte Reihenfolge. Die Menge könnte Elemente in jeder beliebigen Anordnung drucken.

3. Kein Indexzugriff. Du kannst nicht pets[0] schreiben — Mengen haben keine Positionen.

Der Kompromiss: Du verlierst Reihenfolge & Duplikate. Du gewinnst O(1) Zugehörigkeitsprüfung — die Überprüfung, ob ein Element existiert, dauert gleich lange, ob die Menge 5 Elemente oder 5 Millionen hat.

# Zugehörigkeitsprüfung — ist "fish" in der Menge?
if "fish" in pets:    # hash("fish") → springe zu Bucket → gefunden
    print("found!")   # hat genau 1 Stelle geprüft, nicht 4

Menge vs. Liste Zugehörigkeit

Du hast 500 Schülernamen in einer Menge statt einer Liste gespeichert.

students = {"Alex", "Maria", ..., "Zara", ...}  # 500 Namen
if "Zara" in students:
    print("enrolled")
Wie viele Elemente prüft der Computer, um zu bestimmen, ob "Zara" in einer Menge von 500 Namen existiert? Welche Big-O-Klasse beschreibt dies? Warum unterscheidet es sich von der Listenversion?

Nebeneinander-Vergleich

Operationen Spickzettel

Operationskosten: Liste vs. Menge — Zugehörigkeit, Hinzufügen, Index, Duplikat-Entfernung

Nutze eine Liste, wenn du brauchst:

- Elemente in einer bestimmten Reihenfolge (eine Wiedergabeliste, ein Rezept, eine Warteschlange)

- Zugriff nach Position (items[3])

- Duplikate bleiben erhalten ("cat" erscheint 3-mal = 3 Katzen)

Nutze eine Menge, wenn du brauchst:

- Schnelle Zugehörigkeitsprüfungen ("habe ich das schon mal gesehen?")

- Automatische Duplikat-Entfernung

- Reihenfolge spielt keine Rolle

Nutze beide, wenn du brauchst Reihenfolge UND schnelle Suche:

seen = set()     # O(1) Zugehörigkeitsprüfungen
result = []      # behält Einfügungsreihenfolge bei
for item in data:
    if item not in seen:  # O(1) Prüfung
        seen.add(item)    # O(1) zur Menge hinzufügen
        result.append(item)  # O(1) zu Liste hinzufügen
# Gesamt: O(N) — ein Durchlauf, jeder Schritt O(1)

Dieses "Menge + Liste"-Muster gibt dir das Beste von beiden: geordnete Ergebnisse mit schneller Duplikat-Erkennung.

Das richtige Werkzeug wählen

Drei Aufgaben. Für jede, entscheide: Liste, Menge, oder beide?

1. Du musst eine Wiedergabeliste von Liedern in der Reihenfolge speichern, wie der Nutzer sie hinzugefügt hat.

2. Du musst schnell überprüfen, ob ein Nutzername bereits vergeben ist.

3. Du musst doppelte E-Mails aus einer Mailingliste entfernen, aber sie in der Reihenfolge behalten, in der sie sich angemeldet haben.

Für jede der drei Aufgaben, welche Datenstruktur solltest du nutzen (Liste, Menge, oder beide)? Erkläre deine Begründung für jede.

Das Duplikat-Entfernungsproblem

Aufgabe: Duplikate entfernen, Reihenfolge bewahren

Du erhältst eine Liste von E-Mail-Anmeldungen. Einige Personen haben sich zweimal angemeldet. Du musst eine E-Mail pro Person verschicken, in der Reihenfolge, in der sie sich zuerst angemeldet haben.

Versuch 1: Nur Liste (langsam)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
    if email not in unique:  # O(N) Durchsuchung der unique Liste
        unique.append(email)
# Funktioniert! Aber: O(N) Prüfung × N Elemente = O(N²)

Bei 100 Anmeldungen macht das ~5000 Prüfungen. Bei 10000 Anmeldungen: ~50000000 Prüfungen.

Versuch 2: Menge + Liste (schnell)

signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set()
unique = []
for email in signups:
    if email not in seen:   # O(1) Hash-Lookup
        seen.add(email)     # O(1) zur Menge hinzufügen
        unique.append(email) # O(1) zur Liste hinzufügen
# O(1) Prüfung × N Elemente = O(N)

Gleiches Ergebnis. Gleiche Reihenfolge. Aber: 10000 Anmeldungen erfordern jetzt ~10000 Prüfungen statt ~50000000.

Berechne die Speedup

Die Listenversion läuft mit O(N²). Die Menge+Liste-Version läuft mit O(N).

Du hast 10000 E-Mail-Anmeldungen zum Deduplizieren.

Wie viele ungefähre Prüfungen führt jede Version bei N=10000 durch? Wie viel mal schneller ist die Menge+Liste-Version? Zeige deine Berechnung.

Gemeinsame Elemente finden

Aufgabe: Elemente in beiden Listen finden

Du hast zwei Klassenlisten. Du musst Schüler finden, die in beiden Klassen eingeschrieben sind.

Langsamer Ansatz: Verschachtelte Schleifen O(N × M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a:        # N Iterationen
    if student in class_b:     # M Durchsuchungen jedes Mal
        both.append(student)
# O(N × M) — jeder Schüler gegen alle class_b durchsucht

Schneller Ansatz: Konvertiere eine Liste zu einer Menge O(N + M)

class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b)     # O(M) zum Erstellen
both = []
for student in class_a:        # N Iterationen
    if student in class_b_set: # O(1) jedes Mal
        both.append(student)
# O(N + M) — einmal die Menge erstellen, N-mal mit O(1) überprüfen

Oder noch einfacher mit Pythons eingebautem Menge-Schnittpunkt:

both = set(class_a) & set(class_b)  # O(N + M)

Der & Operator findet alle Elemente, die in beiden Mengen erscheinen.

Umschreibe für Geschwindigkeit

Eine Schule hat zwei Clubs mit je 200 Mitgliedern. Dieser Code findet Schüler, die in beiden Clubs sind:

chess_club = [...]   # 200 Namen
art_club = [...]     # 200 Namen
both_clubs = []
for student in chess_club:
    if student in art_club:
        both_clubs.append(student)
Was ist das Big O dieser Code? Schreibe es um, um schneller mit einer Menge zu laufen. Was ist das Big O deiner verbesserten Version? Erkläre den Unterschied.

Dein Entscheidungs-Rahmen

Dein Entscheidungs-Rahmen für Datenstrukturen

Jedes Mal wenn du Code schreibst, der eine Sammlung von Elementen speichert, frage:

1. Brauch ich Elemente in Reihenfolge? → Liste

2. Brauch ich schnelle Zugehörigkeitsprüfungen? → Menge

3. Brauch ich Reihenfolge UND schnelle Prüfungen? → Menge + Liste zusammen

4. Brauch ich Duplikate? → Liste (Mengen entfernen Duplikate)

---

Der Kerngedanke dieser Lektion: Das gleiche Programm, das das gleiche Problem löst, kann 1000× bis 1000000× schneller laufen, nur durch die Wahl der richtigen Datenstruktur. Keine cleveren Tricks. Keine komplizierte Mathematik. Nur fragend: Welche Operationen macht mein Code am häufigsten? Dann wähle die Struktur, bei der diese Operationen O(1) statt O(N) kosten.

Diese Lektion behandelte Listen & Mengen. Andere Datenstrukturen existieren (Wörterbücher, Bäume, Heaps), die andere Probleme lösen. Der Entscheidungs-Rahmen bleibt gleich: Verstehe deine Operationen, dann wähle die Struktur, die sie billig macht.

---

Willst du tiefer gehen? Unsere Big-O Lektion behandelt Wachstumsraten & Skalierungsberechnungen im Detail. Sieh Big O: Wie schnell ist schnell genug?. Unser unhamming Kurs behandelt den echten Defekt (MOAD-0001) wobei genau diese Liste-zu-Menge-Korrektur einen 1000× Speedup in Produktionssoftware erzeugte. Sieh Das fehlende Kapitel: Algorithmische Komplexität.

Debugge diesen Code

Ein Schüler schrieb diesen Code, um zu zählen, wie viele eindeutige Wörter in einem Buch erscheinen:

words = book.split()           # Liste aller Wörter
unique_count = 0
checked = []
for word in words:             # N Wörter
    if word not in checked:    # durchsucht checked Liste
        checked.append(word)
        unique_count += 1
print(unique_count)

Das Buch hat 100000 Wörter.

Was ist das Big O dieses Code? Warum läuft es langsam bei einem großen Buch? Schreib ihn um, um das gleiche Problem in O(N) zu lösen. Erkläre dann: Könntest du das in einer Zeile mit einer Menge lösen? Wie würde das aussehen?