Algorithmenbegriff
Ein Algorithmus ist eine präzise (in einer festgelegten Sprache abgefasste) endliche Beschreibung eines allgemeinen Verfahrens unter Verwendung ausführbarer elementarer (Verarbeitungs-)Schritte.
In der Informatik:
- Berechnungsvorgänge statt Bearbeitungsvorgänge
- Schwerpunkt auf Ausführbarkeit durch (abstrakte) Maschinen
Eigenschaften von Algorithmen
- Abstraktion, Parameter: Algorithmus löst Klasse von Problemen
- Beschreibung hat endliche Länge: Algorithmus benötigt in jedem Schritt nur endlich viel Platz
- Terminierung: Resultat steht nach endlich vielen Schritten fest
- Determinismus: Zu jedem Zeitpunkt besteht höchstens eine Möglichkeit zur Fortsetzung (eindeutige Folge der auszuführenden Schritte)
- Nicht dringend nötig, solange Ergebnis determiniert ist
- Determiniertheit: gleiche Resultate bei gleichen Eingaben
- Normalerweise erwünscht, Probleme bei Zufallszahlen und Parallelität
Tipp aus Altklausuren
Diese Eigenschaften für Multiple-Choice-Fragen auswendig lernen!
Weitere Begriffe
Korrektheit: Algorithmen (Programme) sollen sich wie beabsichtigt verhalten
Berechenbarkeit: Nicht alles ist berechenbar / programmierbar
- z.B. Halteproblem, Programmäquivalenz (verhalten sich zwei Programme gleich?), Korrektheit von Algorithmen
3-Summen-Problem: Gegeben unterschiedliche Ganzzahlen, bestimme wie viele Tripels von Zahlen sich genau zu Null addieren.
Laufzeitklassen
Vereinfachtes Mathematische Modell
- hängt von Hardware und Compiler / Interpreter ab
- hängt von Algorithmus und Eingabedaten ab
- für uns zumeist interessant: Vergleiche und Zuweisungen
Tilde-Notation
Schätzung der Laufzeit als Funktion der Eingabelänge
- Dazu werden Ausdrücke niedriger Ordnung / Potenz (von ) ignoriert
- aber Vorfaktoren werden beibehalten (Unterscheid zur -Notation)
- Für zwei Funktionen und ist genau denn wenn
Wachstumsordnung
Eine Funktion hat die Wachstumsordnung , wenn es eine Konstante gibt, so dass .
Praktisch relevant:
- konstant:
- logarithmisch:
- linear:
- linearithmetisch:
- quadratisch:
- kubisch:
- exponentiell:
Notationen
- (Tilde): approximatives Laufzeitmodell (dominierender Ausdruck)
- (Big-Theta): Klassifikation von Algorithmen (Wachstumsordnung)
- (Big-O): Entwicklung von oberen Schranken entsprechendes oder kleiner
- (Big-Omega): Entwicklung von unteren Schranken entsprechendes oder größer
Bei einem optimalen Algorithmus gilt untere Schranke = obere Schranke
Datentypen
Abstrakte Datentypen
Beschreibung von Datenstrukturen unabhängig von ihrer späteren Implementierung in einer konkreten Programmiersprache (konkrete Datentypen aus Basisdatentypen und Klassen)
- Spezifikation der Schnittstelle (Operationen und Funktionalität) nach außen
- Kapselung: Darf nur über Schnittstelle benutzt werden
- Stabilität gegenüber Änderungen
- Auswahl einer geeigneten Implementierungsvariante
- Geheimnisprinzip: Interne Realisierung ist verborgen
- Grundlage des Prinzips der objektorientierten Programmierung
type Bool
operators
true: → Bool // Konstante ohne Parameter (null-wertiger Operator)
false: → Bool
∧: Bool×Bool → Bool // Konstruktor (mehr-wertiger Operator)
∨: Bool×Bool → Bool
¬: Bool → Bool
axioms
∀𝑥 ∈ Bool: false ∧ 𝑥 = false
∀𝑥 ∈ Bool: 𝑥 ∧ false = false
true ∧ true = true
∀𝑥 ∈ Bool: true ∨ 𝑥 = true
∀𝑥 ∈ Bool: 𝑥 ∨ true = true
false ∨ false = false
¬false = true
¬true = false
Implementierung in C++:
- Typen Klassen
- Null-wertige Operatoren Konstanten
- Mehr-wertige Operatoren Methoden
Stapel (Stack)
Last-In-First-Out (LIFO)
type Stack(T)
operators
empty: → Stack
is_empty: Stack → Bool
push: Stack × T → Stack
pop: Stack → Stack
top: Stack → T
axioms
∀𝑠 ∈ Stack, 𝑥 ∈ T: pop(push(𝑠, 𝑥)) = 𝑠
∀𝑠 ∈ Stack, 𝑥 ∈ T: top(push(𝑠, 𝑥)) = 𝑥
∀𝑠 ∈ Stack, 𝑥 ∈ T: is_empty(push(𝑠, 𝑥)) = false
is_empty empty = true
Implementierung:
- Effizient mit Arrays (Feldern) möglich, Pointer auf Ende des Stacks speichern
- Feste Größe: gegebenenfalls irgendwann kein Platz mehr
- Variable Größe: Wie soll das Feld wachsen oder schrumpfen?
- Nach jedem
pushdie Größe um 1 erhöhen (dabei wird ggf. gesamtes Feld kopiert) Worstcase-Laufzeit - Wenn das Feld voll ist, Größe verdoppeln Worstcase-Laufzeit
- Nach jedem
Warteschlangen (Queue)
First-In-First-Out (FIFO)
type Queue(T)
operators
empty: → Queue
is_empty: Queue → Bool
enqueue: Queue × T → Queue
dequeue: Queue → Queue
front: Queue → T
axioms ∀𝑞 ∈ Queue, 𝑥, 𝑦 ∈ T
dequeue(enqueue(empty, 𝑥)) = empty
dequeue(enqueue(enqueue(𝑞, 𝑥), 𝑦)) = enqueue(dequeue(enqueue(𝑞, 𝑥)), 𝑦)
front(enqueue(empty, 𝑥)) = 𝑥
front(enqueue(enqueue(𝑞, 𝑥), 𝑦)) = front(enqueue(𝑞, 𝑥))
is_empty(enqueue(𝑞, 𝑥)) = false
is_empty(empty) = true
Implementierung:
- Effizient mit Arrays (Feldern) möglich, Pointer auf Anfang und Ende des Stacks speichern
- Feste Größe: Queue verschiebt sich mit der Zeit nach hinten, dann einfach am Anfang des Arrays fortsetzen (möglich aufgrund der zwei Pointer)
- Queue kann allerdings weiterhin zu lang werden
- Variable Größe: Verdoppelung der Länge analog zum Stack
Listen (Bag)
Dynamische Datenstruktur, kann sich an tatsächlichen Speicherbedarf anpassen
type List(T)
operators
empty: → List
is_empty: List → Bool
add: List × T → List
head: List → T
tail: List → List
axioms
∀𝑙 ∈ List, 𝑥 ∈ T: head(add(𝑙, 𝑥)) = 𝑥
∀𝑙 ∈ List, 𝑥 ∈ T: tail(add(𝑙, 𝑥)) = 𝑙
∀𝑙 ∈ List, 𝑥 ∈ T: is_empty(add(𝑙, 𝑥)) = false
is_empty(empty) = true
Implementierung:
- Menge von Knoten mit Verweis auf Nachfolger (
next) sowie jeweiligem Element (data) - Listenkopf: spezieller Knoten
head - Listenende:
nullptraufnext