Kapitel 1 - Institut für Informatik - Humboldt

Werbung
HUMBOLDT-UNIVERSITÄT ZU BERLIN
MATHEMATISCH - NATURWISSENSCHAFTLICHE FAKULTÄT II
INSTITUT FÜR INFORMATIK
ARBEITSGRUPPE SPEZIFIKATION, VERIFIKATION UND TESTTHEORIE
PROF. DR. HOLGER SCHLINGLOFF
WS 2007/2008
Skriptum zur Vorlesung
Praktische Informatik 1
Inhalt
Kapitel 0: Einführung ................................................................................................................. 3
0.1 Inhalt der Vorlesung, Organisatorisches, Literatur .......................................................... 3
0.2 Einführungsbeispiel .......................................................................................................... 6
0.3 Was ist Informatik? ........................................................................................................ 13
Kapitel 1: Mathematische Grundlagen ..................................................................................... 17
1.1 Mengen, Multimengen, Tupel, Funktionen, Halbordnungen ......................................... 17
1.2 Induktive Definitionen und Beweise .............................................................................. 20
1.3 Alphabete, Wörter, Bäume, Graphen ............................................................................. 23
Kapitel 2: Informationsdarstellung........................................................................................... 28
2.1 Bits und Bytes, Zahl- und Zeichendarstellungen ........................................................... 28
2.2 Sprachen, Grammatiken, Syntaxdiagramme .................................................................. 35
2.3 Darstellung von Algorithmen ......................................................................................... 40
Kapitel 3: Rechenanlagen......................................................................................................... 45
3.1 Historische Entwicklung ................................................................................................ 45
3.2 von-Neumann-Architektur ............................................................................................. 53
3.3 Aufbau PC/embedded system, Speicher ........................................................................ 57
Kapitel 4: Programmiersprachen und –umgebungen ............................................................... 61
4.1 Programmierparadigmen ................................................................................................ 61
4.2 Historie und Klassifikation von Programmiersprachen ................................................. 64
4.3 Java ................................................................................................................................. 65
4.4 Programmierumgebungen am Beispiel Eclipse ............................................................. 66
Kapitel 5: Applikative Programmierung .................................................................................. 66
5.1 einfache, geschachtelte Rekursion, Terminierung ......................................................... 66
5.2 rekursive Datenstrukturen .............................................................................................. 66
5.3 Methoden und Parameterübergabemechanismen ........................................................... 66
Kapitel 6: Konzepte imperativer Sprachen .............................................................................. 67
6.1 Variablen, Datentypen, Ausdrücke ................................................................................ 67
6.2 Anweisungen und Kontrollstrukturen ............................................................................ 71
6.3 Sichtbarkeit und Lebensdauer von Variablen ................................................................ 72
Hinweise zur Nutzung dieses Skriptes:
Achtung, drucken Sie das Skript (noch) nicht aus! Es wird parallel zur Vorlesung erstellt und
laufend aktualisiert. Wenn Sie jetzt schon ihren Drucker bemühen, ist das entweder eine
Verschwendung von Papier (weil Sie es später nochmal drucken) oder sie erhalten ein evtl.
inkonsistentes Dokument (wenn Sie verschiedene Teile zusammenheften).
Mit Abschluss des Semesters wird Ihnen das gesamte Skript zur Verfügung stehen!
Dieses Dokument enthält außerdem Hyperlinks zu weiteren Quellen im Internet, die in Word
oder mit dem Acrobat Reader durch Anklicken abrufbar sind. Da solche Hyperlinks schnell
veralten, sind sie nur mit einem (*) gekennzeichnet.
HS, 21.10.2007
Kapitel 0: Einführung
0.1 Inhalt der Vorlesung, Organisatorisches, Literatur
Die Vorlesung „Praktische Informatik 1“ hat laut der Studienordnung Diplom Informatik aus
dem Jahr 2003 folgende Lern- und Qualifikationsziele:
1. Grundlagen: Grammatiken; von-Neumann-Rechner; Klassifikation von
Programmiersprachen
2. Softwareentwicklung: SW-Qualitätsmerkmale; Phasen und Dokumente; Abstraktion
und Dekomposition; SWArchitektur: UML; Komponentenarten; Fallbeispiele
3. Konzepte (imperativer) Programmiersprachen: Variablen: Sichtbarkeit, Lebensdauer;
Datentypen (einfach, strukturiert); Ausdrücke; Prioritäten; Anweisungen (einfach,
strukturiert); Methoden als Abstraktion; Parameter: value~, reference~; Rekursion –
Iteration; Sprachvergleich +Kritik.
4. Datenstrukturen und Algorithmen: Listen, Bäume, Sortieren und Suchen, Komplexität
0(n) von Algorithmen.
5. Konzepte der Objektorientierung: ADT-Objekte-Klassen, Vererbung, Sichtbarkeit,
Klassenvariablen, ~methoden, abstrakte Klassen, Überladung, Polymorphie;
dynamisches Binden, Ausnahmebehandlung; Ereignisse, API (ausgewählte Klassen);
Applets
6. Programmierfertigkeiten: Typische Programmbeispiele
Daraus ergeben sich im kommentierten Vorlesungsverzeichnis dieses Semesters folgende
Inhalte der Vorlesung: Mathematische Grundlagen; Informationsdarstellung und
Informationsverarbeitung; Rechnerarchitekturen; Programmiersprachen und -paradigmen;
objektorientierte Programmierung; Datenstrukturen und Algorithmen; Softwaretechnik;
Modellierung und Systementwicklung
Die Vorlesung (4 SWS) ist nur mit begleitender Übung (2 SWS), Praktikum (2 SWS),
Selbststudium, Vorlesungsmitschrift, Hausaufgaben (in Gruppen bearbeitet, korrigiert
und bewertet, in der Übung besprochen) sinnvoll. Als Prüfung findet eine Abschlussklausur
statt; die Zulassung zur Klausur ist an die Erreichung einer bestimmten Punktzahl in Übungen
und Praktikum gebunden. Für die erfolgreiche Teilnahme gibt es 12 Studienpunkte nach dem
ECTS-System (European Credit Transfer System).
Literaturhinweise
Leider gibt es kein einzelnes Buch, welches genau den Stoff der Vorlesung enthält.
Als Literatur zur Vorlesung empfehlen ich das Buch von Gumm und Sommer. (Aktuell: 7.
Auflage; die 6. Auflage gibt es zum Teil im Sonderangebot)
Als Ergänzung dazu ist nachfolgend eine Liste relevanter Lehrbücher angegeben..
Lehrbücher: Einführung in die Informatik










M. Broy: Informatik, eine grundlegende Einführung. Band 1: Programmierung und
Rechnerstrukturen, Band 2: Systemstrukturen und theoretische Informatik. SpringerLehrbuch (+ Aufgabensammlung) (Monumentalwerk)
P. Levi, U. Rembold: Einführung in die Informatik für Naturwissenschaftler und
Ingenieure, Hanser, (konzise)
G. Goos: Vorlesungen über Informatik (vier Bände: Bd. 1: Grundlagen und
funktionales Programmieren, Bd. 2: Objektorientiertes Programmieren und
Algorithmen, Bd. 3: Berechenbarkeit, formale Sprachen, Spezifikationen, Bd. 4:
Paralleles Rechnen und nicht-analytische Lösungsverfahren)
F. L. Bauer, G. Goos: Informatik - Eine einführende Übersicht, 4. Auflage.
Bd. 1+2, Springer („der Klassiker“, etwas veraltet)
L. Goldschlager, A. Lister: Informatik, Eine moderne Einführung. Hanser
Studienbücher, (ebenfalls etwas veraltet; in der Bibliothek 40 Ex. vorhanden)
F. Kröger: Einführung in die Informatik – Algorithmenentwicklung. Springer
Lehrbuch(formale Grundlagen, keine OO-Konzepte, als Ergänzung empfohlen)
H. Balzert: Lehrbuch Grundlagen der Informatik (als Ergänzung der
Vorlesungsthemen)
H.-J. Appelrath, J. Ludewig: Skriptum Informatik - Eine konventionelle Einfürung.
Teubner/VdF (+ Aufgabensammlung) (Betonung auf „konventionell“!)
A. Aho, J. Ullman: Informatik - Datenstrukturen und Konzepte der Abstraktion.
(deutsche Fassung von: Foundations of Computer Science) Thomson Publishing /
Computer Science Press (für Fortgeschrittene)
R. Sedgewick: Algorithmen in Java (auch für Fortgeschrittene)
Lehrbücher: Einführung in Java
In der Vorlesung „praktische Informatik 1“ und besonders im zugehörigen Praktikum
sollen auch Programmierkenntnisse unterrichtet werden. Trotzdem ist diese Veranstaltung
auf keinen Fall ein Programmierkurs; es wird erwartet, dass sich die Studierenden
selbständig in programmiersprachliche Details an Hand geeigneter Lehrmaterialien
(Bücher oder Online-Handbücher) einarbeiten. Von der nachstehenden
Einführungsliteratur empfehle ich das Buch von Barnes und Kölling über die Java-BlueJProgrammierumgebung. Als Ergänzung dazu kann entweder das Buch von Bell und Parr
oder das von Bishop dienen. Wer sich intensiver mit Java auseinander setzen möchte, dem
sei das Buch von Gosling als ultimative Referenz empfohlen.







J. Bishop: Java lernen (dt. Ausgabe von Java Gently), Addison Wesley / Pearson, 2.
Aufl. 2001 (südafrikanisches Flair)
K. Arnold, J. Gosling, D. Holmes: Die Programmiersprache Java (dt. Ausgabe von
The Java Programming Language). Addison-Wesley 1996 („die Referenz“)
D. Barnes, M. Kölling: Objektorientierte Programmierung mit Java (deutsche Ausgabe
von: Objects First with Java - A Practical Introduction using BlueJ). Prentice Hall /
Pearson, Sept. 2003 (für Vorlesung empfohlen)
D. Bell, M. Parr: Java für Studenten – Grundlagen der Programmierung. Prentice Hall
/ Pearson, 3. Aufl. 2003 (systematischer Java-Lehrgang)
E.-E. Doberkat, S. Dißmann: Einführung in die objektorientierte Programmierung mit
Java. Oldenbourg-Verlag, 2. Auflage 2002 (Wiener Hofzwerge betreiben Informatik)
H. W. Lang: Algorithmen in Java. Oldenbourg-Verlag 2003
Küchlin, Weber: Einführung in die Informatik – Objektorientiert mit Java
0.2 Einführungsbeispiel
Um sich der Frage zu nähern, was eigentlich praktische Informatik ist, betrachtet man am
besten ein Beispiel. Ein bekanntes Problem der Informatik ist das Problem des
Handlungsreisenden (Travelling Salesman Problem, TSP). Ein Handlungsreisender muss bei
seiner Arbeit Kunden besuchen, die in verschiedenen Orten wohnen. Aufgabe der Sekretärin
ist es nun, eine Tour zu planen, die ihn zu jedem Kunden genau einmal führt und am Schluss
wieder zum Ausgangspunkt zurück bringt. Natürlich sollte die Tour optimal sein, d.h.,
möglichst wenig Ressourcen verbrauchen. Abhängig davon, welchen Begriff von Ressource
man zu Grunde legt, gibt es verschiedene Varianten der Aufgabenstellung. Beim allgemeinen
TSP gehen wir davon aus, dass der Handlungsreisende z.B. mit dem Flugzeug unterwegs ist:
da es nicht immer von jedem beliebigen Punkt zu jedem anderen eine direkte Flugverbindung
gibt, muss die Sekretärin das Streckennetz der Fluggesellschaften und die jeweiligen
Flugzeiten (oder Ticketpreise) berücksichtigen. Beim euklidischen TSP bewegt sich der
Handlungsreisende zu Fuß oder mit dem Auto, d.h. er kommt überall hin und die Kosten sind
proportional zu den Entfernungen zwischen den Punkten auf der Landkarte. Das metrische
TSP ist eine Verallgemeinerung des euklidischen und ein Spezialfall des allgemeinen TSP:
zwischen je zwei Punkten besteht eine Verbindung, und die Dreiecksungleichung gilt (die
Summe der Kosten von A nach B und der Kosten von B nach C ist größer gleich der Kosten
von A nach C).
Hier ist ein Beispiel für das allgemeine TSP:
Die beiden angegebenen Lösungen haben die Länge 33 und 22. Welches ist die optimale
Tour?
Hier ist ein Beispiel für das euklidische TSP (nach *):
Dieses euklidische TSP hat 250 Punkte. Angegeben sind eine Näherungslösung (Weglänge
12,91) und eine optimale Lösung (Weglänge 12,48).
Was ist nun eine geeignete Methode, um das Problem zu lösen? Eine häufige
Herangehensweise der Informatik ist es, ein Problem auf ein einfacheres zurückzuführen. Wir
wissen, wie die Lösung eines TSP mit nur 2 Punkten (Firmensitz und ein Kunde) aussieht:
Der Handlungsreisende muss hin- zurückfahren. Angenommen, wir wissen, wie wir eine Tour
mit n Punkten löst. Dann kommen wir zu einer Lösung für (n+1) Punkten, indem wir einen
beliebigen Punkt zunächst weglassen, eine optimale Tour für n Punkte konstruieren, und den
weggelassenen Punkt dann als erstes Ziel in die Tour einfügen. Das ist natürlich noch nicht
die optimale Lösung, aber wenn man das für alle Punkte der Reihe nach macht und die Tour
mit der minimalen Länge wählt, bekommt man dadurch das Optimum.
Diesen Algorithmus könnte man etwa wie folgt notieren:
Wir nehmen an, der Startpunkt (Firmensitz) sei fest gegeben und notieren eine Tour durch die
Folge der zu besuchenden Punkte (ohne den Startpunkt S) und durch die zugehörige Länge.
Tour minTour (Punktmenge M) {
Wenn M einelementig (M={A}) dann
Rückgabe ((A, 2* |SA|))
Sonst {
// Berechne die kürzeste Tour rekursiv
Sei minT eine neue Tour, wobei zunächst
Punkte (minT) = undefiniert, Länge(minT)=unendlich;
Für jedes x aus M {
Tour rek = minTour (M-{x});
Sei A der erste Ort von rek;
Sei Tour try gegeben durch
Punkte (try) = append(x, Punkte(rek)));
Länge (try) = Länge(rek) - |SA| + |Sx| + |xA|;
Wenn Länge(try) < Länge(minT) {minT=try};
}
Rückgabe minT;
}
}
Wenn wir den Ablauf dieser rekursiven Funktion z.B. für die Punkte {ABC} betrachten,
stellen wir folgende Aufrufe fest:
minTour ({ABC}) ruft
minTour({BC}) ruft
minTour({C}) und
minTour({B}).
minTour({AC}) ruft
minTour({C}) und
minTour({A}).
minTour({AB}) ruft
minTour({B}) und
minTour({A}).
Das bedeutet, ein Aufruf mit n Punkten stützt sich auf n Aufrufe mit (n-1) Punkten ab, jeder
davon stützt sich auf (n-1) Aufrufe mit (n-2) Punkten ab usw. Insgesamt gibt es
n
n * (n - 1) * (n - 2) *  * 3 * 2 *1   i  n!
i 1
Aufrufe. Im Wesentlichen konstruiert der Algorithmus sämtliche Permutationen der Folge der
Punkte. Die Anzahl der Permutationen von n Elementen ist n!. Wir sagen, der Algorithmus
hat die Komplexität O(n!).
Nachfolgende Tabelle gibt einen Überblick über das Wachstum verschiedener Funktionen.
n
1
2
3
10
50
256
1000
10000
log(n) 256*n
n2
17*n3
2n
n!
nn
0,0
256
1
17
2
1
1
0,3
512
4
136
4
2
4
0,5
768
9
459
8
6
27
1,0
2560
100
17000
1024
3628800 10000000000
1,7
12800
2500
2125000 1,1259E+15 3,04141E+64 8,88178E+84
2,4
65536
65536 285212672 1,15792E+77
#ZAHL!
#ZAHL!
3,0 256000
1000000
1,7E+10 1,0715E+301
#ZAHL!
#ZAHL!
4,0 2560000 100000000
1,7E+13
#ZAHL!
#ZAHL!
#ZAHL!
Wenn wir den Algorithmus auf einem schnellen Pentium DualCore ausführen, der bis zu 4
Milliarden Operationen pro Sekunde ausführt, dann müssen wir für n=10 etwa eine
Millisekunde warten. Für n=50 erhöht sich unsere Wartezeit allerdings auf etwa
1028=3.000.000.000.000.000.000.000.000.000 oder 3 Quatrilliarden Jahre. Das heisst, für
große Werte von n ist der Algorithmus praktisch nicht verwendbar. Auf der anderen Seite ist
es ein offenes Problem, ob es tatsächlich einen substantiell besseren Algorithmus gibt! Wer
einen Algorithmus entdeckt, welcher das TSP mit einer polynomialen Anzahl von Schritten
löst (abhängig von n), wird mit Sicherheit weltberühmt werden.
Natürlich gibt es einige Tricks, um die Laufzeit des Algorithmus zu verbessern. Zum Beispiel
fällt auf, das während der Rekursion der gleiche Aufruf mehrmals stattfindet. Hier kann man
sich mit dem Abspeichern von Zwischenergebnissen behelfen. Dann kann man die Suche
stark parallelisieren. Auch hängt es sehr stark von der Implementierung ab, ob man den
rekursiven Aufruf naiv oder raffiniert implementiert. Mit solchen Tricks ist es vor kurzem (im
Jahr 2004) gelungen, ein TSP mit 24.978 schwedischen Städten zu lösen(*)! Der Weltrekord
liegt laut Wikipedia (*) bei der Lösung eines Planungsproblems für das Layout integrierter
Schaltkreise mit 33.810 Knoten. Zur Lösung solcher Probleme werden Netze von über
hundert Hochleistungscomputern mit einer Gesamtsumme von über 20 Jahren Rechenzeit
verwendet. Zum Vergleich: Im Jahr 1977 lag der Rekord noch bei 120 Städten!
Was macht man aber nun, wenn man das Problem in der Praxis (zum Beispiel in einer
mittelständischen Reisebüro-Software) lösen will, wo man kein Hochleistungs-Rechnernetz
zur Verfügung hat? Die Antwort der praktischen Informatik heisst Heuristik. Das Wort
„Heuristik“ kommt aus der Seefahrt und bezeichnete früher Verfahren, um seine Position
annähernd zu bestimmen. Heute bezeichnen wir damit Näherungsverfahren, die zwar nicht die
optimale Lösung, aber eine hinreichend genaue Annäherung liefern.
Heuristik 1: nächster Nachbar
Bei dieser Heuristik sucht der Reisende, ausgehend von einem beliebigen Punkt, zunächst den
nächsten Nachbarn des aktuellen Punktes auf der Landkarte. Dann bewegt er sich zu diesem
und wendet die Heuristik erneut an. Wenn er alle Kunden besucht hat, fährt er nach Hause
zurück. Obwohl diese Heuristik sehr häufig im täglichen Leben angewendet wird, liefert sie
schlechte Resultate, da der Heimweg und andere unterwegs „vergessene“ Punkte meist hohe
Kosten verursachen.
Heuristik 2: gierige Dreieckstour-Erweiterung
Die Sekretärin wählt zunächst zwei Kunden willkürlich aus und startet mit einer einfachen
„Dreieckstour“. Diese wird dann nach und nach erweitert, und zwar wird jede Stadt da in die
Tour eingefügt, wo sie am besten „passt“, d.h., wo sie die gegebene Tour am wenigsten
erweitert. Solche Strategien nennt man „gierig“, da sie nur auf lokale und Optimierung
ausgerichtet sind und das Gesamtergebnis nicht berücksichtigen. In der geschilderten Form
sind noch zwei Zufallselemente enthalten: Die Wahl der ursprünglichen Dreieckstour, und die
Reihenfolge, in der die Knoten hinzugefügt werden.
Heuristik 3: Hüllen-Erweiterung
Eine andere Variante dieser Heuristik startet mit der konvexen Hülle aller Punkte (d.h., den
Punkten, die am weitesten außen liegen), und fügt der Reihe nach diejenigen Knoten hinzu,
die die wenigsten Kosten verursachen. Das heisst, es werden der Reihe nach die Knoten
hinzugefügt, die den geringsten Abstand von der bisher konstruierten Tour haben. Das ist
wieder ein „gieriger“ Algorithmus, und zwar in doppelter Hinsicht: bei der Auswahl der
Punkte und bei der Bestimmung der Einfügestelle.
Heuristik 4: inverse Hüllen-Erweiterung
Wieder starten wir mit der konvexen Hülle aller Punkte und fügen der Reihe nach diejenigen
Knoten hinzu, die die meisten Zusatzkosten verursachen. Das heisst, es werden die Knoten
hinzugefügt, die am weitesten von der bisherigen Tour entfernt liegen. Dadurch wird das
Gesamtergebnis (die Form der Tour) frühzeitig stabilisiert.
Heuristik 5: Lin-Kernighan
Hier versucht man, eine bestehende Tour zu verbessern, indem man zwei Kantenpaare AB
und CD sucht und durch die Kanten AC und BD ersetzt (so genannte 2-opt-Strategie). Im
endgültigen Lin-Kernighan-Verfahren werden nicht nur Kantenpaare, sondern Mengen von
Kanten ersetzt.
Wie wir sehen, führen konträre Ideen manchmal beide zu guten Ergebnissen. Es ist sehr
schwer, die „Güte“ von Heuristiken abzuschätzen, da die Leistung immer vom verwendeten
Beispiel abhängt. Für jede der genannten Heuristiken lassen sich Beispiele konstruieren, so
dass das Ergebnis schlecht ist (die doppelte Länge der optimalen Tour oder noch mehr hat).
Ist es nicht möglich, eine Heuristik zu finden, die „auf jeden Fall“ ein akzeptabeles Resultat
liefert?
Auch hier hat die praktische Informatik Beiträge zu liefern. S. Arora konstruierte 1996 einen
„nahezu linearen“ randomisierten Algorithmus für das euklidische TSP, der für eine beliebige
Konstante c eine (1+1/c)-Approximation in O(n log(n)O(c)) Zeit konstruiert (*). Das heisst,
z.B. für c=10 bekommt man eine Tour, die höchstens 10% schlechter als die optimale ist,
indem wir jeden Knoten durchschnittlich log(n)10 mal betrachten. Wie wir oben gesehen
haben, ist log(n) „fast“ eine Konstante (in allen praktischen Fällen kleiner als 5). Daher ist
dies „fast“ ein linearer Algorithmus. Der Algorithmus basiert auf raffinierten geometrischen
Überlegungen, nämlich einer baumartigen Zerlegung der Ebene in Quadrate und einer
Normierung der Schnittkanten der Verbindungslinien zwischen den Punkten.
0.3 Was ist Informatik?
Das Wort „Informatik“ ist ein Kunstwort, welches aus den Bestandteilen „Information“ und
„Automatik“ zusammengesetzt ist. Der Begriff „Informatique“ wurde 1962 in Frankreich von
P. Dreyfuss geprägt und 1968 vom Forschungsminister Stoltenberg in Berlin bei der
Eröffnung einer Tagung übernommen.(*, *). Da die Informatik also eine vergleichsweise
junge Wissenschaft ist, die eine stürmische Entwicklung durchläuft, gibt es die
verschiedensten Definitionen davon, was unter Informatik zu verstehen ist.
Als Beispiel sei hier die Studienordnung der HU von 2003 angeführt:
(1) Die Informatik erforscht die grundsätzlichen Verfahrensweisen der Informationsverarbeitung und die allgemeinen Methoden der Anwendung solcher Verfahren in den
verschiedensten Bereichen. Ihre Aufgabe ist es, durch Abstraktion und Modellbildung von
speziellen
Gegebenheiten
sowohl
der
technischen
Realisierung
existierender
Datenverarbeitungsanlagen als auch von Besonderheiten spezieller Anwendungen abzusehen
und dadurch zu den allgemeinen Gesetzen, die der Informationsverarbeitung zugrunde liegen,
vorzustoßen sowie Standardlösungen für Aufgaben der Praxis zu entwickeln. Die Informatik
befasst sich deshalb mit
— der Struktur, der Wirkungsweise, den Fähigkeiten und den Konstruktionsprinzipien von
Informations- und Kommunikationssystemen und ihrer technischen Realisierung.
— Strukturen, Eigenschaften und BeschreibungsmögIichkeiten von Informationen und von
Informationsprozessen,
— Möglichkeiten der Strukturierung, Formalisierung und Mathematisierung von
Anwendungsgebieten sowie der Modellbildung und Simulation.
Dabei spielen Untersuchungen über die Effizienz der Verfahren und über Sinn und Nutzen
ihrer Anwendung in der Praxis eine wichtige Rolle.
Andere Fachbereiche haben ähnliche Festlegungen des Studieninhaltes. Als minimaler
Konsens für den Begriff „Informatik“ kann dabei die Definition angesehen werden, welche
sich aus der Wortbedeutung ergibt:
Informatik ist die Wissenschaft
der automatischen Verarbeitung von Informationen.
(Im Buch von Gumm/Sommer: „Informatik ist die Wissenschaft von der maschinellen
Informationsverarbeitung“.) In dieser Definition sind ein paar weitere undefinierte
Grundbegriffe enthalten: „Information“, „Verarbeitung“, „automatisch“ oder „maschinell“.
Der Begriff „Information“ ist ein metaphysischer Grundbegriff, mit dem wir uns noch näher
beschäftigen werden. An dieser Stelle sei nur bemerkt, dass wir unter „Information“ eine
Beschreibung irgendeines Sachverhaltes der uns umgebenden (materiellen oder ideellen) Welt
verstehen wollen. Vom Wortstamm her ist eine „Information“ etwas, was in eine bestimmte
Form gebracht worden ist, also auf eine bestimmte Weise repräsentiert wird (wenn wir eine
Information zur Kenntnis nehmen, bringen wir unseren Verstand in einen bestimmten
Zustand). Sehr einfache, wenig strukturierte Informationen bezeichnen wir als Daten (daher
der altertümliche Begriff „EDV“), eine Menge komplexer Informationen über ein
zusammenhängendes Gebiet bezeichnen wir als Wissen.
Unter der „Verarbeitung“ von Informationen verstehen wir den Prozess der Umformung, d.h.
der Veränderung von Informationen aus einer Form in eine andere. Da Informatik sich als
Wissenschaft mit der Verarbeitung von Informationen beschäftigt, sind ihr Gegenstand die
Verfahren, mit denen diese Umformung bewerkstelligt wird: solche Verfahren nennt man
Algorithmen. Häufig erfolgt heutzutage der räumliche Transport von Informationen dadurch,
dass sie beim Absender in eine einfachere Form gebracht (codiert) werden, durch einen
elementaren physikalischen Prozess (elektrische Ströme, Funkwellen etc.) übermittelt und
beim Empfänger wieder in die ursprüngliche Form zurückübersetzt (decodiert) werden. Daher
betrachtet man heute auch die Erforschung von Techniken zur Übertragung von
Informationen als Teilgebiet der Informatik.
Der dritte undefinierte Grundbegriff aus der obigen Definition ist „automatisch“. Damit soll
ausgedrückt werden, dass sich die Informatik nicht mit der Informationsverarbeitung durch
Menschen oder andere Lebewesen beschäftigt, sondern durch Automaten, d.h. vom Menschen
konstruierte Maschinen. Daher gehört zur Informatik auch das Wissen um den Aufbau und
die Entwicklung von Technologien zur Konstruktion informationsverarbeitender Geräte. Aus
historischen Gründen bezeichnet man diese Geräte oft auch als „Rechner“ (numerische
Berechnungen waren die ersten automatisierten informationsverarbeitenden Prozesse) oder
„Computer“. Daher hat sich im Englischen der Begriff „computer science“ für die Informatik
durchgesetzt. Dieser Begriff ist allerdings etwas missverständlich, da er suggerieren könnte,
dass Informatik die „Wissenschaft von den informationsverarbeitenden Geräten“ ist. Einige
Leute verwenden daher die Bezeichnung „computing science“.
Aus der Bestimmung des Gegenstands der Informatik ergibt sich unmittelbar, dass die
Informatik viele Bezüge zu anderen Disziplinen hat: Der Gleichklang zum Wort
„Mathematik“ ergibt sich nicht von ungefähr. Man kann mit Fug und Recht behaupten, dass
die Informatik aus der Mathematik erwachsen ist, so wie die Mathematik ihre Wurzeln in der
philosophischen Logik hat. Abgesehen davon, dass die Automatisierung numerischer
Berechnungen schon immer ein ureigenstes Interesse der Mathematik war, ist auch die
Beschäftigung mit abstrakten Begriffen wie „Berechnungsverfahren“ oder „Umformung“ ein
Gegenstand der Mathematik. Viele Pioniere der Informatik (Pascal, Leibniz, Babbage, Turing,
von Neumann, …) waren Mathematiker oder Logiker und haben sich mit den theoretischen
Grundlagen automatischer Berechnungsverfahren beschäftigt, bevor es überhaupt Computer
gab.
Die zweite Wurzel der Informatik ist die Elektrotechnik. Erst durch den Einsatz elektrischer
Schaltungen und Verfahren nach dem zweiten Weltkrieg (durch Zuse, Aiken und andere)
wurde gegenüber den davor existierenden mechanischen Rechnern (Hollerith) ein so großer
Durchbruch erzielt, dass man über numerische Rechnungen hinausgehen konnte. Da praktisch
alle heute existierenden informationsverarbeitenden Prozesse in Automaten auf der Bewegung
von elektrischen Ladungen beruhen, ist klar, dass auch heute noch eine enge Verwandschaft
zwischen Informatik und Elektrotechnik besteht. Auf der anderen Seite gehören Computer
heute mit zu den wichtigsten strombetriebenen Geräten, weshalb sich die Elektrotechnik heute
gerne auch als „Informationstechnik“ bezeichnet,
Durch die Inhalte der Informatik ergeben sich weiterhin eine ganze Reihe von Querbezügen
zu anderen Disziplinen. Wenn Informationsverarbeitung zur Steuerung mechanischer Geräte,
etwa von Robotern oder Fertigungsstraßen, genützt wird, müssen Informatiker mit
Maschinenbauern und Produktionstechnikern zusammenarbeiten. Durch den Einsatz von
Computern zur Übertragung von Informationen per Funkwellen ist eine enge Beziehung zur
Nachrichtentechnik gegeben. Wegen der Notwendigkeit der Interaktion von Automaten und
Menschen (auf Benutzungs- und Konstruktionsebene) muss die Informatik auf Grundlagen
und Ergebnisse der Psychologie, Linguistik, Kommunikationswissenschaften und anderer
Fächer zurückgreifen. Da in fast allen Fächern informationsverarbeitende Prozesse
vorkommen, die bislang entweder von Menschen durchgeführt wurden oder wegen des hohen
Arbeitsaufwandes gar nicht durchgeführt werden konnten, werden Methoden der Informatik
in diesen Fächern für die Automatisierung der Verarbeitung von Informationen angewendet.
Dadurch haben sich eine Reihe spezialisierter Studiengänge gebildet, die so genannten
Bindestrich-Informatiken, die die Anwendung der Informatik in anderen Fächern betonen. Zu
nennen sind hier Wirtschafts-Informatik, Bio-Informatik, Medien-Informatik, Geo-
Informatik, Umwelt-, Rechts- oder Medizininformatik, und viele mehr. Wichtig: Mit einer
Ausbildung als Diplom-Informatiker kann man sich später für jede dieser Disziplinen weiter
qualifizieren!
Aus den geschilderten Wurzeln ergibt sich die heute übliche Struktur der Informatik: Man
teilt sie ein in
 theoretische Informatik
 praktische Informatik
 technische Informatik, und
 angewandte Informatik.
Die theoretische Informatik beschäftigt sich mit den formalen Grundlagen, die praktische
Informatik mit den Verfahren, die technische Informatik mit den Maschinen zur Verarbeitung
von Informationen. In der angewandten Informatik werden die Anwendungen der
Informationsverarbeitung für andere Fächer (z.B. Robotik, Bioinformatik, medizinische
Bildverarbeitung) untersucht. Oftmals wird die angewandte Informatik als Teil der
praktischen Informatik betrachtet; an einigen Universitäten studiert man im Grundstudium
zunächst ein beliebiges Nebenfach und spezialisiert sich dann im Hauptstudium auf die
angewandte Informatik in diesem Nebenfach.
Geschichte der Informatik
Informatik im eigentlichen Sinne gibt es erst seit dem Endes des zweiten Weltkrieges.
Die Wurzeln der Informatik reichen dagegen bis ins Mittelalter bzw. ins Altertum zurück:















300 v. Chr:
Euklid entwickelt sein Verfahren zur Bestimmung des größten gemeinsamen
Teilers (ggT)
um 820:
Al-Chwarizmi fasst in einem Buch Lösungen zu bekannten mathematischen
Problemen zusammen.
1524:
A. Riese veröffentlicht ein Buch über die Grundrechenarten
17-18.Jh.:
G. W. Leibniz (1646-1714) entwickelt das Dualsystem (1679) und baut eine
Rechenmaschine (1673/1694), Pascal, Schickard u.a. entwickeln ebenfalls mechanische
Rechenmaschinen, Babbage konzipiert „difference engine“, Ada Lovelace die erste
Programmiersprache dafür
Ende 19./ Anf. 20. Jh.: Formalisierung der logischen und mathematischen Grundlagen durch
Frege (“Begriffschrift”, 1879), Russell u. Whitehead (“Prinicipia mathematica", 1910-13),
Peano u.a.
Ende 19./ Mitte 20. Jh.: Perfektionierung mechanischer Rechenmaschinen;
1930-40:
Theorie der Berechenbarkeit, Vollständigkeits- und Entscheid-barkeitssätze
(Gödel, Turing, Tarski, Church, Kleene, Post, Markov u.a.)
1930-40:
erste elektromechanische Computer: Zuses Z1 (1936), Z3 (1943), Aikens
Mark1 (1944), Eckert+Mauchlys ENIAC (1946)
1948-49:
Konrad Zuse entwickelt seinen “Plankalkül”, C. Shannon seine
“Informationstheorie”, J. v. Neumann entwickelt den nach ihm benannten Rechnertyp: Daten
und Befehle werden gemeinsam im Rechner gespeichert und ähnlich behandelt.
1955:
Erfindung des Transistors
1959-60:
erste “höhere Programmiersprachen”: J. McCarthy entwickelt die funktionale
Programmiersprache LISP und begründet die “Artificial Intelligence”, Fortran (Masch.bau),
Cobol (BWL) und Algol (Math) werden definiert.
1969-70:
Entwicklung universaler Programmiersprachen wie Algol68 und PL/I
ab 1970:
Informatikstudium in Deutschland
ab 1980:
Objektorientierte Sprachen und Systeme
ab 1990:
Internet (Gopher, Mosaic), Mobilfunk (1991 D-Netz, 1994 E-Netz), WLAN
(ca. 1995), PDAs (1997), Java (1995), Java2 (2002)
Die Informatik ist heute in fast alle Aspekte unseres Lebens vorgedrungen:






Einen Großteil ihres Studiums werden Sie mit „e-Learning“ verbringen, die
notwendigen Informationen beschaffen Sie sich im Internet. Vielleicht bestellen Sie
hier auch Waren oder vergleichen zumindest bei eBay die Preise.
Dokumente (Briefe, Steuererklärungen usw.) verfassen Sie natürlich am Computer;
mit Ihren Kommilitonen nebenan und dem Onkel in Amerika tauschen Sie e-Mails
aus, die den Empfänger in wenigen Sekunden erreichen.
Wahrscheinlich haben Sie auch ein Mobiltelefon, vielleicht sogar ein Notebook mit
WLAN, oder einen elektronischen Organizer.
Ihr Bankkonto wird von einem Computer geführt, Bargeld holen Sie am
Geldautomaten, vielleicht habe Sie auch eine elektronische Brieftasche.
Wenn Sie mit dem Auto nach Hause fahren, begleiten Sie bis zu 80 eingebaute
Steuergeräte, das Auto ist weitgehend von Robotern gebaut worden. Sogar die
Schnittmuster Ihrer Kleidung wurden vom Computer optimiert.
Ihre Armbanduhr, Foto- oder Filmapparat, Ton- und Bildwiedergabegeräte sind schon
längst nicht mehr mechanisch, ganz zu schweigen von der Türschließanlage,
Fahrstuhlsteuerung, Kühlschrank, Mikrowelle, Waschmaschine, und anderen Geräten
zu Hause.
Das ist aber keinesfalls das Ende der Entwicklung. In ein paar Jahren wird Sie die
Türschließanlage an Ihrem Aussehen und Fingerabdruck erkennen, Sie werden sich vielleicht
wie im Roman „per Anhalter durch die Galaxis“ mit dem Fahrstuhl unterhalten, der
Kühlschrank wird Vorräte selbsttätig nachbestellen, der Herd wird sich Rezepte aus dem
Internet holen und die Waschmaschine wird wissen, wie heiß die Wäsche gewaschen werden
muss.
Alle diese „Wunder der Technik“ werden möglich durch systematische Vorschriften für die
Verarbeitung von Informationen (Algorithmen, Programme) und Maschinen, die diese
Vorschriften ausführen können (Computer, Prozessoren). Natürlich können wir uns in einer
Vorlesung nicht mit allen oben genannten Anwendungen beschäftigen, aber die zentralen
Gesichtspunkte die in allen gleichermaßen vorhanden sind, bilden den Gegenstand der
Vorlesung: Algorithmen und ihre Ausführung auf Rechenanlagen.
Das zentrale Ziel der Vorlesung ist es, eine „algorithmische Denkweise“ zu vermitteln: Ein
Verständnis dafür, wann und wie ein (informationsbezogenes) Problem mit welchem
Aufwand durch eine Maschine gelöst werden kann. Inhaltlich gibt die Vorlesung einen
Überblick über das Gebiet der praktischen Informatik. Dazu gehören unter anderem folgende
Themen:
 Repräsentation von Informationen in Rechenanlagen
 programmiersprachliche Konzepte
 Methoden der Softwareentwicklung
 Algorithmen und Datenstrukturen
 Korrektheit und Komplexität von Programmen
In Teil 2 der Vorlesung (im Sommersemester) werden diese Themen ergänzt und vertieft.
Kapitel 1: Mathematische Grundlagen
1.1 Mengen, Multimengen, Tupel, Funktionen, Halbordnungen
Eine Menge ist eine Zusammenfassung von (endlich oder unendlich vielen) verschiedenen
Dingen unserer Umwelt oder Vorstellungswelt, welche Elemente dieser Menge genannt
werden. Wir schreiben x  M , um auszusagen, dass das Ding x Element der Menge M ist.
Andere Sprechweisen: x ist in M enthalten, oder M enthält x .
Um auszudrücken, dass x nicht in M enthalten ist, schreiben wir x  M . Die Schreibweise
x1 ,..., xn  M steht für x1  M und … und xn  M , und {x1 ,..., xn } ist die Menge, die genau
die Elemente x1 ,..., xn enthält.
Beispiele für Mengen sind:

: Die Menge der natürlichen Zahlen 1,2,3,…

0:
Die Menge der Kardinalzahlen (natürlichen Zahlen einschließlich der Null):
0,1,2,3,…

: Menge der ganzen Zahlen (integer) …, -3, -2, -1, 0, 1, 2, 3, …

,





: Menge der rationalen bzw. reellen Zahlen
 oder { }: leere Menge
oder boolean = {true, false} oder {1,0} oder {tt, ff} oder {w, f} oder
{L,O}: Menge der Wahrheitswerte
byte = {-128,…,127}: Menge der ganzen Zahlen zwischen -128 und 127
{Adam, Eva}: Menge der ersten Menschen
{A, B, C, …, Z}: Menge der Großbuchstaben im lateinischen Alphabet
Die Menge M 1 ist eine Teilmenge von M 2 ( M 1  M 2 ), wenn jedes Element von M 1 auch
Element von M 2 ist. Die Mengen M 1 und M 2 sind gleich ( M 1  M 2 ), wenn sie die gleichen
Elemente enthalten (Extensionalitätsaxiom). Für jede (endliche) Menge M bezeichnet | M | die
Anzahl ihrer Elemente. Neben der Aufzählung ihrer Elemente können Mengen durch eine
charakterisierende Eigenschaft gebildet werden (Komprehensionsaxiom). Beispiel: byte =
{x | -128x und x127}. Auf diese Weise können auch unendliche Mengen gebildet werden.
Die unbeschränkte Verwendung der Mengenkomprehension kann zu Schwierigkeiten führen
(Russells Paradox: „die Menge aller Mengen, die sich nicht selbst enthalten“), daher erlaubt
man in der axiomatischen Mengenlehre nur gewisse Eigenschaften.
Auf Mengen sind folgende Operationen definiert:
Durchschnitt: M 1  M 2  {x | x  M1  x  M 2 } .
Vereinigung: M 1  M 2  {x | x  M1  x  M 2 } .
Differenz: M1  M 2  {x | x  M1  x  M 2 } .
Beispiele:  0= 0 ,  0= , – 0 =, 0 – =0. Durchschnitt und Vereinigung sind
kommutativ und assoziativ. Daher kann man diese Operationen auf beliebige Mengen von
Mengen ausweiten:
{M1 , M 2 ,..., M n }  {x | x  M1  x  M 2  ...  x  M n } .
-

iI
M i  {x | x  M i für ein i  I } .
Von Mengen kommt man zu „Mengen höherer Ordnung“ durch die Potenzmengenbildung:
Wenn M eine Menge ist, so bezeichnet (M ) oder 2 M die Menge aller Teilmengen von M .
Cantor bewies, dass die Potenzmenge einer Menge immer mehr Elemente enthält als die
Menge selbst. Speziell gilt für jede endliche Menge M : Wenn | M | n , so ist | ( M ) | 2 n .
Beispiel: ()  {} , ({})  {,{}} und
({ A, B, C})  {,{ A},{B},{C},{ A, B},{ A, C},{B, C},{ A, B, C}} .
Multimengen
Während Mengen die grundlegenden Daten der Mathematik sind, hat man es in der
Informatik oft mit Multimengen zu tun, bei denen Elemente „mehrfach“ vorkommen können.
Beispiele sind
 eine Tüte mit roten, gelben und grünen Gummibärchen,
 die Vornamen der Studierenden dieser Vorlesung,
 die Multimenge der Buchstaben eines bestimmten Wortes, usw.
Multimengen können notiert werden, indem man zu jedem Element die entsprechende
Vielfachheit angibt, z.B. ist {A:3, B:1, N:2} die Multimenge der Buchstaben im Wort
BANANA. Formal können Multimengen definiert werden als Funktionen von einer
Grundmenge in die Menge
0
der Kardinalzahlen, siehe unten.
Folgen
Aus Mengen lassen sich durch Konkatenation Tupel und Folgen bilden. Der einfachste Fall ist
dabei die Paarbildung mit dem kartesischen Produkt. Wenn M 1 und M 2 Mengen sind, so
bezeichnet M1  M 2  {( x, y) | x  M1  y  M 2 } die Menge aller Paare von Elementen,
deren erster Bestandteil ein Element aus M 1 und deren zweiter eines aus M 2 ist. Da die
runden Klammern für vielerlei Zwecke verwendet werden, verwendet man manchmal zur
Kennzeichnung von Paaren auch spitze oder, besonders in Programmiersprachen, eckige
Klammern.
Beispiele:  ={(1,tt),(1,ff),(2,tt),(2,ff), (3,tt),…}, =,
{0}={(1,0), (2,0), (3,0), …},  0={(1,0), (1,1), (1,2), …, (2,0), (2,1), …}
Eine Verallgemeinerung ist das n-stellige kartesische Produkt, mit dem n-Tupel gebildet
werden:
M 1  M 2      M n  {( x1 , x2 ,..., xn ) | x1  M 1  x2  M 2  ...  xn  M n }
2-Tupel sind also Paare, statt 3-, 4- oder 5-Tupel sagt man auch Tripel, Quadrupel, Quintupel
usw. Die zur Produktbildung umgekehrten Operationen, mit denen man aus einem Produkt
die einzelnen Bestandteile wieder erhält, bezeichnet man als Projektionen:
 i ( x1 , x2 ,..., xn ) | xi
Falls alle M i gleich sind (M 1  M 2      M n  M ) , so schreiben wir statt
M  M      M auch M n und nennen ( x1 , x2 ,..., xn ) eine Folge oder Sequenz oder Liste der
Länge n über M . Wichtige Spezialfälle sind n  1 und n  0 . Im ersten Fall ist die
einelementige Folge (x) etwas anderes als das Element x. Im zweiten Fall ist die leere Folge ()
unabhängig von der verwendeten Grundmenge. Achtung: Die leere Folge ist nicht zu
verwechseln mit der leeren Menge!
M n enthält nur Sequenzen einer bestimmten fest vorgegebenen Länge n. Unter M * verstehen
wir die Menge, die alle beliebig langen Folgen über M enthält:
M *  {M i | i  N} .
Wenn wir nur nichtleere Folgen betrachten wollen, schreiben wir M  :
M  M* M0 .
Relationen und Funktionen
Eine Relation zwischen zwei Mengen M 1 und M 2 ist eine Teilmenge von M 1  M 2 . Wenn
zum Beispiel M={Anna, Beate} und J={Claus, Dirk, Erich}, so ist liebt={(Anna, Claus),
(Beate, Dirk), (Beate, Erich)} eine Relation. Relationen schreibt man meist in Infixnotation,
d.h., statt (Beate, Dirk)liebt schreibt man (Beate liebt Dirk). Falls M 1  M 2  M , so sagen
wir, dass die Relation über M definiert ist. Typische Beispiele sind die Relationen  und =
über den natürlichen Zahlen, oder die Verbindungsrelation zwischen Städten im Streckennetz
der deutschen Lufthansa. Eine Relation R heisst (links-)total, wenn es zu jedem x  M1 ein
y  M 2 mit (xRy) gibt. Sie heisst (rechts-)eindeutig, wenn es zu jedem x  M1 höchstens ein
y  M 2 mit (xRy) gibt. Eine eindeutige Relation nennt man Abbildung oder partielle Funktion,
eine totale und eindeutige Relation heißt Funktion. Bei Funktionen schreiben wir
f : M1 
 M 2 und f ( x)  y für f  M 1  M 2 und ( x, y )  f . Die Menge der x  M1 , für
die es ein y  M 2 mit (xRy) gibt, heißt der Definitionsbereich oder Urbildbereich (domain)
der Abbildung; die Menge der y  M 2 , für die es ein x  M1 mit (xRy) gibt, heißt der
Wertebereich oder Bildbereich (range) der Funktion oder Abbildung.
Genau wie oben lassen sich auch die Begriffe Relation und Funktion verallgemeinern. Eine nstellige Relation zwischen den Mengen M 1 , M 2 ,  , M n ist eine Teilmenge von
M 1  M 2      M n . Einstellige Relationen heißen auch Prädikate. Auch für Prädikate
schreiben wir (Px) anstatt von (x)  P. Eine n-stellige Funktion f von M 1 , M 2 ,  , M n nach
M ist eine (n+1)-stellige Relation zwischen M 1 , M 2 ,  , M n und M , so dass für jedes nTupel ( x1 , x2 ,..., xn )  M 1  M 2      M n genau ein y  M existiert mit ( x1 , x2 ,..., xn , y)  f .
Eine Funktion f : M  M      M 
 M heißt (n-stellige) Operation auf M . Beispiele
für zweistellige Operationen sind + und * auf , , und . Die Differenz – ist auf ,
und eine Operation, auf ist sie nur partiell (nicht total). Die Division ist in jedem Fall nur
partiell. Typische Prädikate auf natürlichen Zahlen sind prim oder even.
Wir haben Funktionen als spezielle Relationen definiert, Es gibt auch die Auffassung, dass
der Begriff „Funktion“ grundlegender sei als der Begriff „Relation“, und dass Relationen eine
spezielle Art von Funktionen sind. Sei M 1  M 2 . Dann ist die charakteristische Funktion
 : M2 
 von M 1 in M 2 definiert durch  (x)  true falls x  M1 und  (x)  false
falls x  M1 . Mit Hilfe der charakteristischen Funktion kann jede Relation zwischen den
Mengen M 1 , M 2 ,  , M n als Funktion von M 1 , M 2 ,  , M n nach aufgefasst werden. Diese
Auffassung findet man häufig in Programmiersprachen, bei denen Prädikate als boolesche
Funktionen realisiert werden.
Ordnungen
Die Relation R über M heißt

reflexiv, wenn für alle x  M gilt, dass xRx .

irreflexiv, wenn für kein x  M gilt dass xRx .

transitiv, wenn für alle x, y, z  M mit xRy und yRz gilt, dass xRz .

symmetrisch, wenn für alle x, y  M mit xRy gilt, dass yRx .
 antisymmetrisch, wenn für alle x, y  M mit xRy und yRx gilt x  y .
Eine reflexive, transitive, symmetrische Relation heißt Äquivalenzrelation.
Eine reflexive, transitive, antisymmetrische Relation heißt Halbordnung oder partielle
Ordnung. Eine irreflexive, transitive, antisymmetrische Relation heißt strikte Halbordnung.
Eine partielle Ordnung heißt totale oder lineare Ordnung, wenn für alle x, y  M gilt,
dass xRy oder yRx . Bei einer totalen Ordnung lassen sich alle Elemente „der Reihe nach“
anordnen. Die Relation  ist eine totale Ordnung auf natürlichen und reellen Zahlen, nicht
aber auf komplexen Zahlen: dort ist sie nur eine Halbordnung. Ein einfacheres Beispiel für
eine Halbordnung über Zahlen ist die Relation „ist Teiler von“.
Beispiel für reflexive, aber nicht antisymmetrische Relation:
Beispiel für eine antisymmetrische, aber nicht reflexive Relation:
1.2 Induktive Definitionen und Beweise
Für fast alle in der Informatik wichtigen Datentypen besteht ein direkter Zusammenhang
zwischen ihrer rekursiven Definition (ihrem rekursiven Aufbau) und induktiven Beweisen
von Eigenschaften dieser Daten. Wir wollen uns diese Dualität am Beispiel der natürlichen
Zahlen betrachten.
Die natürlichen Zahlen lassen sich durch die folgenden so genannten Peano-Axiome
definieren:
 1 ist eine natürliche Zahl.
 Für jede natürliche Zahl gibt es genau eine natürliche Zahl als Nachfolger.
 Verschiedene natürliche Zahlen haben auch verschiedene Nachfolger.
 1 ist nicht der Nachfolger irgendeiner natürlichen Zahl.
 Sei P eine Menge natürlicher Zahlen mit folgenden Eigenschaften:
o 1 ist in P
o Für jede Zahl in T ist auch ihr Nachfolger in P.
Dann enthält T alle natürlichen Zahlen.
Das letzte dieser Axiome ist das so genannte Induktionsaxiom. Es wird oft in der Form
gebraucht, dass P eine Eigenschaft natürlicher Zahlen ist:
 Sei P eine Eigenschaft natürlicher Zahlen, so dass P(1) gilt und aus P(i) folgt P(i+1).
Dann gilt P für alle natürlichen Zahlen.
Der erste Mathematiker, der einen formalen Beweis durch vollständige Induktion angab, war
der italienische Geistliche Franciscus Maurolicus (1494 -1575). In seinem 1575
veröffentlichten Buch „Arithmetik“ benutzte Maurolicus die vollständige Induktion unter
anderem dazu, zu zeigen, dass alle Quadratzahlen sich als Summe der ungeraden Zahlen bis
zum doppelten ihrer Wurzel ergeben:
1 + 3 + 5 + ... + (2n-1)=n*n
Beweis: Sei P die Menge natürlicher Zahlen, die diese Gleichung erfüllen. Um zu beweisen,
dass P alle natürlichen Zahlen enthält, müssen wir zeigen
1=1*1
Wenn 1 + 3 + 5 + ... + (2n-1)=n*n,
dann 1 + 3 + 5 + ... + (2n-1)+(2(n+1)-1)=(n+1)*(n+1)
Die Wahrheit dieser Aussagen ergibt sich durch einfaches Ausrechnen.
Hier ist ein geringfügig komplizierteres Beispiel zum selber machen: Die Summe der
Kubikzahlen bis n ist das Quadrat der Summe der Zahlen bis n.
2
 n 
i

 i 

i 1
 i 1 
Ein wichtiger Gesichtspunkt beim Induktionsaxiom ist, dass die natürlichen Zahlen als
„induktiv aufgebaut“ dargestellt werden gemäß den folgenden Regeln:
n
3
 1 .
 Wenn i , dann auch i+1 .
 Außer den so erzeugten Objekten enthält keine weiteren Zahlen.
Mit anderen Worten, jede Zahl wird erzeugt durch die endlich-oft-malige Anwendung der
Operation (+1) auf die Zahl 1.
Das Induktionsaxiom erlaubt es, Funktionen über den natürlichen Zahlen rekursiv zu
definieren. Eine rekursive Definition nimmt dabei auf sich selbst Bezug. Solche Definitionen
können leicht schief gehen („das Gehalt berechnet sich immer aus dem Gehalt des letzten
Jahres plus 3%“ oder „Freiheit ist immer die Freiheit der Andersdenkenden“ oder „GNU is
short for »GNU is Not Unix«“). Ein Begriff, der durch solch eine zirkuläre Definition erklärt
wird, ist nicht wohldefiniert. Eine Funktion ist nur dann wohldefiniert, wenn sich der
Funktionswert eindeutig aus den Argumenten ergibt. Das Prinzip der vollständigen Induktion
erlaubt es nun, eine Funktion über den natürlichen Zahlen dadurch zu deklarieren, dass man
den Funktionswert für n=1 angibt, und indem man zeigt, wie sich der Funktionswert für (n+1)
aus dem Funktionswert für n berechnen lässt. Wenn man nämlich für P die Aussage „der
Funktionswert ist eindeutig bestimmt“ einsetzt, so besagt dass Induktionsprinzip, dass dann
der Funktionswert für alle natürlichen Zahlen eindeutig bestimmt ist. Zum Beispiel lässt sich
die Fakultätsfunktion n!=1*2*…*n ohne „Pünktchen“ dadurch definieren, dass wir festlegen
 1! = 1
 Wenn n!=x, dann ist (n+1)!=(n+1)*x
Eine andere Schreibweise der zweiten Zeile ist
 (n+1)!=(n+1)*n!
Da in dieser Formel „n“ nur ein Stellvertreter für eine beliebige Zahl ist, können wir auch
schreiben
 Wenn n>1, dann ist n!=n*(n-1)!
Diese Schreibweise ist sehr nahe an der Schreibweise in Programmiersprachen, beispielsweise
(in der elementaren Sprache des Programms bc):
 define fac(n){ if (n==1) return(1) else return(n*fac(n-1)) }
In der vorgestellten Fassung erlaubt es das Induktionsprinzip nur, bei der Definition von f(n)
auf den jeweils vorherigen Wert f(n-1) zurückzugreifen. Eine etwas allgemeinere Fassung ist
das Prinzip der transfiniten Induktion:
 Sei P eine Eigenschaft natürlicher Zahlen, so dass für alle x gilt:
Falls P(y) für alle y<x, so auch P(x). Dann gilt P für alle natürlichen Zahlen.
Dieses Prinzip gilt nicht nur für die natürlichen Zahlen, sondern für beliebige fundierte
Ordnungen (in denen es keine unendlich langen absteigenden Ketten gibt). Der
Induktionsanfang ergibt sich dadurch, dass es keine kleinere natürliche Zahl als 1 gibt und für
die 1 daher nichts vorausgesetzt werden kann. Im Induktionsschritt erlaubt uns dieses Prinzip,
auf beliebige vorher behandelte kleinere Zahlen zurückzugreifen. Das Standardbeispiel sind
hier die Fibonacci-Zahlen (nach Leonardo di Pisa, filius Bonacci, 1175-1250, der das
Dezimalsystem in Europa einführte)
1, falls n  2

fib(n)  
 fib(n  1)  fib(n  2), sonst
oder, in der programmiersprachlichen Fassung,
define fib(n){
if(n<=2) return(1) else return(fib(n-1)+fib(n-2))}
Die Werte dieser Funktion sind, der Reihe nach, 1,1,2,3,5,8,13,21,… und sollen das
Bevölkerungswachstum von Kaninchenpaaren nachbilden. Als Beispiel für einen Beweis, der
auf mehrere Vorgänger zurückgreift, zeigen wir die Formel von Binet:
n
n
1  1  5   1  5  
 
 .

fib(n) 
5  2   2  


1 5
1 5
Als Lemma benötigen wir, dass für  
gilt   1   2 , und ebenso für  
.
2
2
Das sieht man durch einfaches Ausrechnen, ebenso die Gültigkeit der Aussage für n=1,2.
1 n 1
Damit können wir als Induktionsannahme voraussetzen, dass fib(n  1) 
   n1 und
5
1 n2
fib(n  2) 
   n 2 . Mit fib(n)  fib(n  1)  fib(n  2) ergibt sich
5
1 n 1
fib(n) 
   n2   n1   n2 , d.h.,
5
1
1 2 n2
1 n
fib(n) 
(  1) n2  (  1) n 2 
    2 n  2 
   n , was zu zeigen
5
5
5
war.












Die Berechnung der Fibonacci-Zahlen mit der Formel von Binet geht im Allgemeinen
erheblich schneller als mittels der rekursiven Definition:
define binet(n){
return((((1+sqrt(5))/2)^n - ((1-sqrt(5))/2)^n)/sqrt(5))}
binet(50) ergibt sofort 12586269025
Im Allgemeinen ist es nicht immer möglich, solch eine geschlossene (nichtrekursive) Formel
für eine rekursiv definierte Funktion zu finden.
Wir haben das Induktionsprinzip für natürliche Zahlen und der fundierten Ordnungsrelation <
angewendet. Dies ist nur ein Spezialfall des folgenden allgemeinen Prinzips für induktiv
erzeugte Mengen.
Das sind Mengen, die definiert werden durch
 die explizite Angabe gewisser Elemente der Menge,
 Regeln zur Erzeugung weiterer Elemente aus schon vorhandenen Elementen der
Menge
sowie der expliziten oder impliziten Annahme, dass die Menge nur die so erzeugten
Elemente enthält.
Für induktiv erzeugte Mengen gilt folgendes allgemeine Induktionsprinzip:
Sei P eine Eigenschaft, die Elemente der Menge haben können oder nicht, so dass
 P für alle explizit angegebenen Elemente der Menge gilt, und

P für alle gemäß den Bildungsregeln erzeugten Elemente gilt, falls es für die bei der
Erzeugung verwendeten Elemente gilt.
Dann gilt P für alle Elemente der Menge.
Beispiele für dieses Erzeugungsprinzip werden später betrachtet.
1.3 Alphabete, Wörter, Bäume, Graphen
Unter einem Alphabet A versteht man eine endliche Menge von Zeichen A={a1, …, an}. Das
bekannteste Beispiel ist sicher das lateinische Alphabet mit den Zeichen A, B, C, …, Z. Aber
bereits davon gibt es verschiedene Varianten, man denke nur an das deutsche Alphabet mit
Umlauten ä, ö, ü und der Ligatur ß. Im Laufe der Zeit haben sich bei den Völkern Hunderte
von Alphabeten gebildet, von Keilschriften und Hieroglyphen bis hin zu Runen- und
Geheimschriften (*). Das chinesische Alphabet umfasst etwa 56000 Zeichen, im Alltag kann
man mit 6.000 Schriftzeichen schon relativ gut auskommen; der chinesische Zeichensatz für
Computer enthält 7.445 Schriftzeichen. Der ASCII-Zeichensatz enthält 128 bzw. (in der
erweiterten Form) 256 Druckzeichen, siehe Tabelle. Man beachte, dass in manchen
Alphabeten das Leerzeichen als ein Zeichen enthalten ist; als Ersatzdarstellung wählt man
häufig eine Unterstrich-Variante. Ein für die Informatik wichtiges Alphabet ist die Menge
der Wahrheitswerte.
Eine (endliche) Folge wA* von Zeichen über einem Alphabet A heißt Wort oder
Zeichenreihe (string) über A. Normalerweise schreibt man, wenn es sich um Wörter handelt,
statt w = (a1,a2,…,an) kurz w = "a1a2…an", manchmal werden die Anführungszeichen auch
weggelassen. Die leere Zeichenreihe wird mit dem Symbol  oder mit "" bezeichnet. Über
Wörtern ist die Konkatenation (Hintereinanderschreibung) als Operation definiert:
Wenn v = a1a2…an und w = b1b2…bm, dann ist v°w = a1a2…anb1b2…bm. Da die Operation °
assoziativ ist ((u ° v) ° w = u ° (v ° w)), wird das Operationssymbol ° manchmal auch einfach
weggelassen. Die leere Zeichenreihe  ist bezüglich + ein neutrales Element (w °  =  ° w =
w). Eine Menge mit einer assoziativen Operation und einem neutralem Element nennt man
auch Monoid; da die Menge A* (mit der Operation ° und dem neutralen Element  ) keinen
weiteren Einschränkungen unterliegt, heißt sie auch der freie Monoid über A (wenn a1a2…an
= b1b2…bm, so ist n=m und a1= b1 und… und an= bn).
Die Menge der Wörter über einem gegebenen Alphabet lässt sich auch induktiv erzeugen:
  A*
 Wenn aA und wA*, so ist (a°w)A*.
Hierbei bezeichnet (a°w) diejenige Zeichenreihe, die als erstes Zeichen a enthält und danach
das Wort w. Alternativ dazu hätten wir Wörter induktiv durch das Anfügen (append) von
Zeichen an Zeichenreihen erzeugen können. Diese Charakterisierung der Menge der
Zeichenreihen erlaubt es, induktive Beweise zu führen und rekursive Funktionen über
Wörtern zu definieren.
Sei first(w) die partielle Funktion, die zu einem nichtleeren Wort dessen erstes Zeichen liefert,
und rest(w) die Funktion, die das Wort ohne das erste Zeichen liefert.
Hier sind ein paar rekursiv definierte Funktionen über Wörtern.
define length(w){
if(w==epsilon) return(0) else return(1+lenght(rest(w)))}
liefert die Länge eines Wortes. Die Konkatenation kann rekursiv wie folgt definiert werden:
define conc(v,w){
if (w==epsilon) return(v) else
return(conc(append(v,first(w)),rest(w)))}
define inverse(w){
if(w==epsilon) return(w) else return(inverse(rest(w))°first(w)))}
liefert das umgedrehte Wort, also etwa EEFFAC zu CAFFEE.
define replace(w,a,b){
if(w==epsilon) return(w) else
if(first(w)==“a”) return(“b”°replace_a_b(rest(w))) else
return(first(w)°replace(rest(w),a,b)}
ersetzt jedes a in w durch b. Wir werden später noch ähnliche solche Funktionen kennen
lernen.
Wenn w=u°v, so sagen wir, dass u ein Anfangswort von w ist. Wenn w=v1°u°v2, so nennen
wir u ein Teilwort von w. Auch die Anfangswortrelation lässt sich leicht induktiv definieren:
define anfangswort(w,u){
if(u==epsilon) return(true)
else if(first(w)!= first(u)) return(false)
else return(anfangswort(rest(w), rest(u)))}
Auf Alphabeten ist häufig eine totale Ordnungsrelation erklärt; meist wird diese durch die
Reihenfolge der Aufschreibung der Zeichen unterstellt. Wenn A ein Alphabet mit einer
totalen Ordnungsrelation  ist, so kann  zur lexikographischen Ordnung auf A* ausgeweitet
werden:
Sei x= x1x2…xn und y= y1y2…ym. Dann gilt x y, wenn
 x ein Anfangswort von y ist, oder
 es gibt ein Anfangswort z von x und y (x=z°x’, y=z°y’) und first(x’) <first(y’).
(a < b. bedeutet a  b. und nicht a = b)
Beispiele für lexikographisch geordnete Wörter über dem lateinischen Alphabet sind
"ANTON" < "BERTA", "AACHEN" < "AAL", "AAL" < "AALBORG" und  < "A".
Es ist nicht schwer zu sehen, dass die lexikographische Ordnung eine totale Ordnung ist. Die
Buchstaben des deutschen Alphabets sind nicht linear geordnet (a und ä stehen
nebeneinander, ß ist nicht eingeordnet), daher entspricht die Reihenfolge der Wörter in einem
deutschen Lexikon nicht der lexikographischen Ordnung.
Bäume
Bäume sind – neben Tupeln, Folgen und Wörtern – eine weitere in der Informatik sehr
wichtige Datenstruktur. In der induktiven Definition von Zeichenreihen besteht ein Wort w
aus der Konkatenation von first(w) mit rest(w). Ein Binärbaum ist dadurch gekennzeichnet,
dass es zwei verschiedene „Reste“ gibt: den linken und den rechten Unterbaum. Daraus ergibt
sich folgende induktive Definition der Menge der Binärbäume über einem gegebenen
Alphabet A:
  A^
 Wenn aA und lA^ und rA^, so ist (a,l,r)A^.
a heißt Wurzel, l und r sind Unterbäume des Baumes (a,l,r). Die Wurzeln von l und r heißen
die Kinder oder Nachfolger von a. Ein Baum y ist Teilbaum eines Baumes x, wenn x=y oder y
Teilbaum eines Unterbaumes von x ist. Wenn y nichtleerer Teilbaum von x ist, so sagen wir,
die Wurzel von y ist ein Knoten von x. Ein Knoten ohne Nachfolger (d.h. ein Teilbaum der
Gestalt (a,  ,  ) ) heißt Blatt.
Als Beispiel für Bäume betrachten wir Formelbäume über dem Alphabet (x,y,z,+,*). Die
Formel x*y + x*z (mit „Punkt-vor-Strich-Regelung“) kann durch den Baum
(+,(*,(x,  ,  ),(y,  ,  )),(*,(x,  ,  ),(z,  ,  ))) repräsentiert werden. Übersichtlicher ist eine
graphische Darstellung:
+
*
x
*
y
x
z
Wir werden später verschiedene Algorithmen, die auf Bäumen basieren, kennen lernen.
Es ist klar, dass sich die obige Definition direkt auf Binärbäume über einer beliebigen
Grundmenge verallgemeinern lässt. Eine weitere nahe liegende Erweiterung sind n-äre
Bäume, bei denen jeder Knoten entweder keinen oder n Nachfolger hat. Wenn wir erlauben,
dass jeder Knoten eine beliebige (endliche) Zahl von Nachfolgern haben kann, sprechen wir
von endlich verzweigten Bäumen.
Aufrufbäume
Eine spezielle Art von (endlich verzweigten) Bäumen sind die Aufrufbäume einer rekursiven
Funktion. Die Wurzel eines Aufrufbaumes ist der Name der Funktion mit den Eingabewerten.
Die Nachfolger jeden Knotens sind die bei der Auswertung aufgerufenen Funktionen mit
ihren Eingabewerten.
Beispiel:
inverse(„ABC“)
)
rest(„ABC“)
inverse(„BC“)
first(„ABC“)
+
rest(„BC“)
inverse(„C“)
first(„BC“)
+
rest(„C“)
inverse(„“)
first(„C“)
+
(In diesem Baum haben wir die Funktionen „==“ und „if“ nicht weiter berücksichtigt.)
Beim verkürzten Aufrufbaum lässt man alle Knoten weg außer denen, die die rekursive
Funktion selbst betreffen.
Beispiel:
fib(5)
fib(3)
fib(4)
fib(3)
fib(2)
fib(2)
fib(2)
fib(1)
fib(1)
Unter der Aufrufkomplexität einer rekursiven Funktion verstehen wir die Anzahl der Knoten
im verkürzten Aufrufbaum. Die Zeit, die benötigt wird, um eine rekursive Funktion zu
berechnen, hängt im Wesentlichen von der Aufrufkomplexität ab. Als Beispiel betrachten wir
die Aufrufkomplexität der Fibonacci-Funktion. Aus obigem Beispiel ist sofort klar:
1, falls n  2

fibComp(n)  
1  fibComp(n  1)  fibComp(n  2), sonst
Die rekursive Formulierung hilft leider noch nicht, die Aufrufkomplexität abzuschätzen. Per
Induktion nach n zeigen wir: fibComp(n)  2 * fib(n)  1 . Für n=1,2 ist dies klar, für n>2 gilt
fibComp(n)  1  fibComp(n  1)  fibComp(n  2)  I .V . 1  2 * fib(n  1)  1  2 * fib(n  2)  1
 2 * ( fib(n  1)  fib(n  2))  1  2 * fib(n)  1
Mit der früher bewiesenen Gleichung von Binet erhalten wir
n
n
2  1  5   1  5  
 
  1.

fibComp(n) 
5  2   2  


Eine Wertetabelle für einige Zahlenwerte ist nachfolgend angegeben. Daraus folgt: wenn in
einer Sekunde 10.000 Aufrufe erfolgen, benötigt die Rechnung für n=100 etwa 2,2 Milliarden
Jahre! (üblicherweise ist vorher der Speicher erschöpft).
n
3
5
10
20
30
40
50
60
70
80
90
100
fibComp(n)
3
9
109
13529
1.6*106
2*108
2.5*1010
3*1012
3.8*1014
4.6*1016
5.7*1018
7*1020
Graphen
Während ein Wort in der Informatik nur eine spezielle Art von Folgen ist, versteht man unter
einem Graphen nur eine spezielle Art von Relationen: Ein Graph ist die bildliche Darstellung
einer binären Relationen über einer endlichen Grundmenge. Die Elemente der Grundmenge
werden dabei in Kreisen (Knoten) gezeigt. Zwischen je zwei Knoten zeichnet man einen Pfeil
(eine Kante), falls das betreffende Paar von Elementen in der Relation enthalten ist. Beispiel:
B
A
C
D
Dies ist die Relation {(A,B),(B,C),(C,B),(C,A),(B,D),(C,D)}. Für symmetrische Relationen
weisen die Pfeile immer in beide Richtungen; man spricht hier von ungerichteten Graphen.
Eine Alternative zur obigen Definition besteht darin, einen Graphen als Tupel (V,E) zu
definieren, wobei V eine endliche Menge von Knoten (vertices) und E eine endliche Menge
von Kanten (edges) ist, so dass zu jeder Kante genau ein Anfangs- und ein Endknoten gehört.
Knoten, die nicht Endknoten sind, heißen Quelle, Knoten, die nicht Anfangsknoten sind,
heißen Senke im Graphen. Knoten, die weder Anfangs- noch Endknoten sind, heißen isoliert.
Eine dritte Art der Definition von Graphen ist durch die so genannte Adjazenzmatrix: Nach
dieser Auffassung ist ein Graph eine endliche Matrix (Tabelle) mit booleschen Werten. Die
Zeilen und Spalten der Tabelle sind dabei mit der Grundmenge beschriftet; ein Eintrag gibt
an, ob das entsprechende Paar (Zeile, Spalte) in der Relation enthalten ist oder nicht.
A
B
C
D
A
B
X
X
X
C
D
X
X
X
Im Gegensatz zu Bäumen können Graphen Zyklen enthalten, daher existiert keine einfache
induktive Definition. Umgekehrt können endlich verzweigte Bäume als zyklenfreie Graphen
mit nur einer Quelle betrachtet werden.
Kapitel 2: Informationsdarstellung
2.1 Bits und Bytes, Zahl- und Zeichendarstellungen
(siehe Gumm/Sommer * Kap.1.2/1.3)
Damit Informationen von einer Maschine verarbeitet werden können, müssen sie in der
Maschine repräsentiert werden. Üblich sind dabei Repräsentationsformen, die auf Tupeln
oder Folgen über der Menge aufbauen. Ein Bit (binary digit) ist die kleinste Einheit der
Informationsdarstellung: es kann genau zwei Werte annehmen, z. B. 0 oder 1. Genau wie es
viele verschiedene Notationen der Menge gibt, gibt es viele verschiedene
Realisierungsmöglichkeiten eines Bits: an/aus, geladen/ungeladen, weiss/schwarz,
magnetisiert/entmagnetisiert, reflektierend/lichtdurchlässig, …
Lässt eine Frage mehrere Antworten zu, so lassen sich diese durch eine Bitfolge (mehrere
Bits) codieren.
Beispiel: Die Frage, aus welcher Himmelsrichtung der Wind weht, lässt 8 mögliche
Antworten zu. Diese lassen sich durch Bitfolgen der Länge 3 codieren:
000 = Nord
001 = Nordost
010 = Ost
011 = Südwest
100 = Süd
101 = Südost
110 = West
111 = Nordwest
Offensichtlich verdoppelt jedes zusätzliche Bit die Anzahl der möglichen Bitfolgen, so dass es
genau 2n mögliche Bitfolgen der Länge n gibt (| |=2  | n|=2n)
Ein Byte ist ein Oktett von Bits: 8 Bits = 1 Byte. Oft betrachtet man Bytefolgen anstatt von
Bitfolgen. Ein Byte kann verwendet werden, um z.B. folgendes zu speichern:
ein codiertes Zeichen (falls das Alphabet weniger als 28 Zeichen enthält)
eine Zahl zwischen 0 und 255,
eine Zahl zwischen -128 und +127,
die Farbcodierung eines Punkts in einer Graphik, genannt „Pixel“ (picture element)
Gruppen von 16 Bits, 32 Bits, 64 Bits bzw. 128 Bits werden häufig als Halbwort, Wort,
Doppelwort bzw. Quadwort bezeichnet. Leider gibt es dafür unterschiedliche Konventionen.
Zwischen 2-er und 10-er Potenzen besteht (näherungsweise) der Zusammenhang:
210 = 1024  1000 = 103
Für Größenangaben von Dateien, Disketten, Speicherbausteinen, Festplatten etc. benutzt man
daher folgende Präfixe:
 k = 1024 = 210
 103
(k = Kilo)
2
20
 M = 1024 = 1048576=2  106
(M = Mega)
3
30
9
 G = 1024 = 2
 10
(G = Giga)
4
40
12
 T = 1024 = 2
 10
(T = Tera)
 P = 10245 = 250
 1015
(P = Peta)
6
60
18
 E = 1024 = 2
 10
(E = Exa)
Die Ungenauigkeit der obigen Näherungsformel nimmt man dabei in Kauf.
Mit 1 GByte können also entweder 230 = 10243 = 1.073.741.824 oder 109 = 1.000.000.000
Bytes gemeint sein.
Anhaltspunkte für gängige Größenordnungen von Dateien und Geräten:
 eine Notiz: ~200 B
 ein Brief: ~3 kB
 ein Dos-Programm: ~300 kB
 Diskettenkapazität: 1,44 MB
 ein Windows-Programm: ~ 1 MB
 ein Musiktitel: ~40 MB (im MP3-Format ~4 MB)
 Zip-Diskette: ~120 MB
 Hauptspeichergröße: 128-512 MB
 CD-ROM Kapazität: ~ 680 MB
 DVD (Digital Versatile Disk): ~ 4,7 bzw. ~ 9 GB
 Festplatte: ~10-80 GB.
Für Längen- und Zeiteinheiten werden auch in der Informatik die gebräuchlichen Vielfachen
von 10 benutzt. So ist z.B. ein 400 MHz Prozessor mit 400106 = 400.000.000 Hertz
(Schwingungen pro Sekunde) getaktet.
Das entspricht einer Schwingungsdauer von 2,510-9 sec, d.h. 2,5 ns. Der Präfix n steht
hierbei für nano, d.h. den Faktor 10-9. Weitere Präfixe für Faktoren kleiner als 1 sind:





m = 1/1000 = 10-3
µ = 1/1000000 = 10-6
n = 1/1000000000 = 10-9
p = ... = 10-12
f = ... = 10-15
(m = Milli)
(µ = Mikro)
(n = Nano)
(p = Pico)
(f = Femto)
Beispiele: 1mm = 1 Millimeter; 1 ms = 1 Millisekunde.
Für Längenangaben wird neben den metrischen Maßen eine im Amerikanischen immer noch
weit verbreitete Einheit verwendet: 1" = 1 in = 1 inch = 1 Zoll = 2,54 cm = 25,4 mm. Teile
eines Zolls werden als Bruch angegeben. Beispiel - Diskettengröße: 3 1/2".
Darstellung natürlicher Zahlen, Stellenwertsysteme
Die älteste Form der Darstellung von Zahlen ist die Strichdarstellung, bei der jedes
Individuum durch einen Strich oder ein Steinchen repräsentiert wird (calculi=Kalksteinchen,
vgl. kalkulieren). Bei dieser Darstellung ist die Addition besonders einfach (Zusammen- oder
Hintereinanderschreibung von zwei Strichzahlen), allerdings wird sie für große Zahlen schnell
unübersichtlich. Die Ägypter führten deshalb für Gruppen von Strichen Abkürzungen ein
(*,*).
Daraus entstand dann das römische Zahlensystem:
Aus Übersichtlichkeitsgründen werden die großen Zahlen dabei zuerst geschrieben;
prinzipiell spielt in solchen direkten Zahlensystemen die Position einer Ziffer keine Rolle. Die
Schreibweise IV = 5-1 ist erst viel später entstanden!
Direkte Zahlensysteme haben einige Nachteile: Die Darstellung großer Zahlen kann sehr lang
werden, und arithmetische Operationen lassen sich in solchen Systemen nur schlecht
durchführen. In Indien (und bei den Majas) wurde ein System mit nur zehn verschiedenen
Ziffernsymbolen verwendet, bei der die Position jeder Ziffer (von rechts nach links) ihre
Wertigkeit angibt. Die wesentliche Neuerung ist dabei die Erfindung der Zahl Null (ein leerer
Kreis). Der schon genannte Muhammed ibn Musa al-Khwarizmi verwendete das
Dezimalsystem in seinem Arithmetikbuch, das er im 8. Jahrhundert schrieb. Bereits im 10.
Jahrhundert wurde das System in Europa eingeführt, durchsetzen konnte es sich jedoch erst
im 12. Jahrhundert mit der Übersetzung des genannten Arithmetikbuchs ins Lateinische
(durch Fibonacci, siehe oben).(*)
Wir haben schon erwähnt, dass das Dualzahlen- oder Binärsystem mit nur zwei Ziffern in
Europa 1673 von Gottfried Wilhelm Leibnitz (wieder-)erfunden wurde. Allgemein gilt: In
Stellenwertsystemen wird jede Zahl als Ziffernfolgen xn-1... x0 repräsentiert, wobei - bezogen
auf eine gegebene Basis b - jede Ziffer xi einen Stellenwert (xi*bi) bekommt:
n 1
xn1  x0 b   xi  b i
i 0
Werden dabei nur Ziffern xi mit Werten zwischen 0 und b-1 benutzt, so ergibt sich eine
eindeutige Darstellung; dafür werden offenbar genau b Ziffern benötigt. Zu je zwei Basen b
und b' gibt es eine umkehrbar eindeutige Abbildung, die [xn-1... x0]b und [ yn'-1... y0]b' mit 0  xi
 b und 0  yi  b' ineinander überführt.
Beispiele:
b = 2:
b = 3:
b = 8:
b = 10:
[10010011]2 = 1*27 + 1*24 + 1*21 + 1*20 = [147]10
[12110]3 = 1*34 + 2*33 + 1*32 + 1*31 = [147]10
[223]8 = 2*82 + 2*81 + 3*80 = [147]10
[147]10 = 1*102 + 4*101 + 7*100=[10010011]2
Wichtige Spezialfälle sind b=10, 2, 8 und 16. Zahlen mit b=8 bezeichnet man als Oktalzahlen.
Unter Sedezimalzahlen (oft auch Hexadezimalzahlen genannt) versteht man
Zifferndarstellungen von Zahlen zur Basis 16. Sie dienen dazu, Dualzahlen in komprimierter
(und damit leichter überschaubarer) Form darzustellen und lassen sich besonders leicht
umrechnen. Je 4 Dualziffern werden zu einer „Hex-ziffer“ zusammengefasst. Da man zur
Hexadezimaldarstellung 16 Ziffern benötigt, nimmt man zu den Dezimalziffern 0 ... 9 die
ersten Buchstaben A ... F hinzu.
Beispiele: Umwandlung von Dezimal - in Oktal- / Hexadezimalzahlen und umgekehrt:
[1]10=[1]8=[1]16=[1]2
…
[7]10=[7]8=[7]16=[111]2
[8]10=[10]8=[8]16=[1000]2
[9]10=[11]8=[9]16=[1001]2
[10]10=[12]8=[A]16=[1010]2
[11]10=[13]8=[B]16=[1011]2
[12]10=[14]8=[C]16=[1100]2
[13]10=[15]8=[D]16=[1101]2
[14]10=[16]8=[E]16=[1110]2
[15]10=[17]8=[F]16=[1111]2
[16]10=[20]8=[10]16
[17]10=[21]8=[11]16
…
[32]10=[40]8=[20]16
[33]10=[41]8=[21]16
…
[80]10=[100]8=[50]16
…
[160]10=[240]8=[A0]16
…
[255]10=[377]8=[FF]16
[256]10=[400]8=[100]16
[1000]10=[3E8]16
…
[4096]10=[10000]8=[1000]16
…
[10000]10=[2710]16
…
[45054]10=[AFFE]16
[65535]10=[177777]8=[FFFF]16
[106]10=[F4240]16
[4294967295]10=[FFFFFFFF]16
Diese Umrechnung ist so gebräuchlich, dass sie von vielen Taschenrechnern bereit gestellt
wird.
Um mit beliebig großen natürlichen oder ganzen Zahlen rechnen zu können, werden diese als
Folgen über dem Alphabet der Ziffern repräsentiert. Diese Darstellung wird beispielsweise im
bc verwendet. Numerische Algorithmen mit solchen Repräsentationen sind allerdings häufig
komplex, und in vielen Anwendungen wird diese Allgemeinheit nicht benötigt. Daher werden
Zahlen oft als Dual- oder Binärwörter einer festen Länge n repräsentiert. Mit Hilfe von n Bits
lassen sich 2n Zahlenwerte darstellen:
 die natürlichen Zahlen von 0 bis 2n - 1 oder
 die ganzen Zahlen zwischen -2n-1 und 2n-1 - 1 oder
 ein Intervall der reellen Zahlen (mit begrenzter Genauigkeit)
Beispiel:
Länge
4
8
16
32
darstellbare Zahlen
0 .. 15
0 .. 255
0 .. 65535
0 .. 4 294 967 295
Darstellung ganzer Zahlen
Für die Darstellung ganzer Zahlen wird ein zusätzliches Bit (das "Vorzeichen-Bit")
benötigt. Mit Bitfolgen der Länge n kann also (ungefähr) der Bereich [-2n-1 .. 2n-1] dargestellt
werden. Nahe liegend ist die dabei Vorzeichendarstellung: Das erste Bit repräsentiert das
Vorzeichen (0 für '+' und 1 für '-') und der Rest den Absolutwert.
Nachteile dieser Darstellung:
• Die Darstellung der 0 ist nicht mehr eindeutig.
• Beim Rechnen "über die 0" müssen umständliche Fallunterscheidungen gemacht
werden, Betrag und Vorzeichen sind zu behandeln.
Beispiel:3 + (-5) = 0011+1101=1010
Eine geringfügige Verbesserung bringt die Einserkomplement-Darstellung, bei der jedes Bit
der Absolutdarstellung umgedreht wird. Meist wird jedoch die so genannte
Zweierkomplement-Darstellung (kurz: 2c) benutzt. Sie vereinfacht die arithmetischen
Operationen und erlaubt eine eindeutige Darstellung der 0. Bei der
Zweierkomplementdarstellung gibt das erste Bit das Vorzeichen an, das 2. bis n-te Bit ist das
Komplement der um eins verringerten positiven Zahl. Die definierende Gleichung für die
Zweierkomplementdarstellung ist:
[-xn-1... x0]2c + [xn-1...x0]2c = [10...0]2c = 2n+1
Um zu einer gegebenen positiven Zweierkomplement-Zahl die entsprechende negative zu
bilden, invertiert man alle Bits und addiert 1. Die Addition kann mit den üblichen Verfahren
berechnet werden (Beweis: eigene Übung!).
Beispiele: n = 4: +5  [0101]2c , also -5  (1010 + 0001)2c = [1011]2c
3 + (-5) =[0011]2c +[1011]2c =[1110]2c
4 + 5 =[0100]2c +[0101]2c  [1001]2c = -7 Achtung!!!
In Java gibt es, abhängig von der Länge n, vier verschiedene Datentypen für ganze Zahlen:
 Datentyp byte mit dem Wertebereich -27 = -128 ...127= 27-1
 Datentyp short mit dem Wertebereich -215 = -32768 ...32767= 215-1
 Datentyp int mit dem Wertebereich -231 = - 2147483648 ... 2147483647= 231-1
 Datentyp long mit dem Wertebereich
-263 =- 9223372036854775808 ... 9223372036854775807= 263-1
Beachte: Ein Zahlbereichsüberlauf wird nicht abgefangen und kann beispielsweise zur Folge
haben, dass die nagelneue Rakete abstürzt!
Darstellung rationaler und reeller Zahlen
Prinzipiell kann man rationale Zahlen als Paare ganzer Zahlen (Zähler und Nenner)
darstellen. Ein Problem ist hier die Identifikation gleicher Zahlen: (7,-3)=(-14,6). Hier müsste
man nach jeder Rechenoperation mit dem ggT normieren; das wäre sehr unpraktisch. Daher
werden in der Praxis rationale Zahlen meist wie reelle Zahlen behandelt.
Für relle Zahlen gilt:
 Es gibt überabzählbar viele reelle Zahlen . Also gibt es auch reelle Zahlen, die sich
nicht in irgend einer endlichen Form aufschreiben lassen, weder als endlicher oder
periodischer Dezimalbruch noch als arithmetischer Ausdruck oder Ergebnis eines
Algorithmus. Echte reelle Zahlen lassen sich also nie genau in einem Computer
speichern, da es für sie definitionsgemäß keine endliche Darstellung gibt.
 Die so genannten „reals“ im Computer sind mathematisch gesehen immer
Näherungswerte für reelle Zahlen mit endlicher Genauigkeit.
Für reelle Zahlen gibt es die Festkomma- und die Gleitkommadarstellung. Bei der
Festkommadarstellung steht das Komma an einer beliebigen, festen Stelle. Für
x=[xn-1xn-2…x1x0x-1x-2…x-m]2 ist x 
n 1
x
i m
i
 2 i . Diese Darstellung gestattet nur einen kleinen
Wertebereich und hat auch sonst einige Nachteile (Normierung von Zahlen erforderlich).
Daher verwendet man sie in elektronischen Rechenmaschinen nur in Ausnahmefällen. Ziel
der Gleitkommadarstellung (IEEE754 Standard) ist es,
 ein möglichst großes Intervall reeller Zahlen zu umfassen,
 die Genauigkeit der Darstellung an die Größenordnung der Zahl anzupassen: bei
kleinen Zahlen sehr hoch, bei großen Zahlen niedrig.
Daher speichert man neben dem Vorzeichen und dem reinen Zahlenwert - der so genannten
Mantisse - auch einen Exponenten (in der Regel zur Basis 2 oder 10), der die Kommaposition
in der Zahl angibt.
- Das Vorzeichenbit v gibt an, ob die vorliegende Zahl positiv oder negativ ist.
- Die Mantisse m besteht aus einer n–stelligen Binärzahl m1....mn
- Der Exponent e ist eine L-stellige ganze Zahl (zum Beispiel im Bereich -128 bis
+127), die angibt, mit welcher Potenz einer Basis b die vorliegende Zahl zu
multiplizieren ist.
Das Tripel (v, m, e) wird als (-1)v * m * be-n interpretiert. Bei gegebener Wortlänge von 32 Bit
verwendet man beispielsweise 24 Bit für Vorzeichen und Mantisse (n=23) sowie L=8 Bit für
den Exponenten. Beispiele mit n=L=4: (0, 1001, 0000) = 1 * 9 * 2-4 = [0,1001]2=0,5625 und
(1, 1001, 0110) = -1 * 9 * 26-4 = [-100100]2 = -36 und (0, 1001, 1010) = 1 * 9 * 2-6-4 =
0,00878906… Anstatt wie in diesen Beispielen e in Zweierkomplementdarstellung
abzuspeichern, verwendet man die sogenannte biased-Notation: E=e+e’, wobei e’=2L-1-1.
Damit ist 0E(2L-1) positiv, und (v, m, E) wird als (-1)v * m * bE -(e’+n) interpretiert. Zum
Beispiel ist für L=4 der Wert von e’=7, also ein Exponent E=1010 entspricht e=3. Damit
ergibt sich (0, 1001, 1010) = 9 * 23-4 = 4,5. Bei 32-Bit Gleitkommazahl mit 8-Bit Exponent
gilt: e’=127, bei 64-Bit Gleitkommazahlen ist e’=1023.
In Java gibt es zwei Datentypen für Gleitpunktzahlen:
 Datentyp float (32 Bit) mit dem Wertebereich (+/-)1.4*10-45..3.4*1038.(7 relevante
Dezimalstellen: 23 Bit Mantisse, 8 Bit Exponent)
 Datentyp double (64 Bit) mit dem Wertebereich (+/-)4.9*10-324..1.79*10308.(15
relevante Stellen: 52 Bit Mantisse, 11 Bit Exponent)
Zeichendarstellung
Zeichen über einem gegebenen Alphabet A werden meist als Bitfolgen einer festen Länge
nlog(|A|) codiert. Oft wird n so gewählt, dass es ein Vielfaches von 4 oder von 8 ist.
Beispiel: Um Texte in einem Buch darzustellen, benötigt man ein Alphabet von 26
Kleinbuchstaben, ebenso vielen Großbuchstaben, einigen Satzzeichen wie etwa Punkt,
Komma und Semikolon und Spezialzeichen wie "+", "&", "%". Daher hat eine normale
Schreibmaschinentastatur eine Auswahl von knapp hundert Zeichen, für die 7 Bit ausreichen.
Bereits in den 1950-ern wurden die Codes ASCII und EBCDIC hierfür entwickelt. (siehe
ASCII-Tabelle oben).
Mit einem Byte lassen sich 256 Zeichen codieren. Viele PCs benutzen den Code-Bereich [128
.. 255] zur Darstellung von sprachspezifischen Zeichen wie z.B. "ä" (ASCII 132), "ö" (ASCII
148) "ü" (ASCII 129) und einigen Sonderzeichen anderer Sprachen. Leider ist die Auswahl
der sprachspezifischen Sonderzeichen eher zufällig und bei weitem nicht ausreichend für die
vielfältigen Symbole fremder Schriften. Daher wurden von der „International Standardisation
Organisation“ (ISO) verschiedene ASCII-Erweiterungen normiert. In Europa ist dazu die 8Bit ASCII-Erweiterung „Latin-1“ nützlich, die durch die Norm ISO8859-1 beschrieben wird.
Mit steigender Speicherverfügbarkeit geht man heutzutage von 8-Bit Codes zu 16-Bit Codes
über. Hier gibt es zwei häufig genannte Standards: UCS und Unicode.
- [ISO10646]: "Information Technology -- Universal Multiple-Octet Coded Character
Set (UCS) -- Part 1: Architecture and Basic Multilingual Plane", ISO/IEC 106461:1993.
- [UNICODE]: "The Unicode Standard: Version 2.0", The Unicode Consortium,
Addison-Wesley Developers Press, 1996. Siehe auch http://www.unicode.org
In den aktuellen Spezifikationen sind beide Codes zeichengleich. Die ersten 128 Zeichen
dieser Codes sind ASCII-kompatibel, die ersten 256 Zeichen sind kompatibel zu dem oben
genannten Code ISO-Latin-1. Darüber hinaus codieren sie alle gängigen Zeichen dieser Welt.
Herkömmliche Programmiersprachen lassen meist keine Zeichen aus ASCII-Erweiterungen
zu. Java erlaubt als erste der weit verbreiteten Sprachen die Verwendung beliebiger UnicodeZeichen. Allerdings heißt dies noch lange nicht, dass jede Java-Implementierung einen Editor
zur Eingabe von Unicode mitliefern würde.
Zeichenketten (strings) werden üblicherweise durch Aneinanderfügen einzelner codierter
Zeichen repräsentiert.
Beispiel:
Dem Text "Hallo Welt" entspricht die Zeichenfolge
"H", "a", "l", "l", "o", " ", "W", "e", "l", "t"
Diese wird in ASCII folgendermaßen codiert:
072 097 108 108 111 032 087 101 108 116.
In Hexadezimal-Schreibweise lautet diese Folge:
48 61 6C 6C 6F 20 57 65 6C 74.
Dem entspricht die Bitfolge:
01001000 01100001 01101100 01101100 01101111
00100000 01010111 01100101 01101100 01110100.
Obwohl diese Repräsentation sicher nicht die speichereffizienteste ist, ist sie weit verbreitet;
bei Speichermangel greift man eher auf eine nachträgliche Komprimierung als auf andere
Codes zurück.
Darstellung sonstiger Informationen
Natürlich lassen sich in einem Computer nicht nur Bits, Zahlen und Zeichen repräsentieren,
sondern z.B. auch visuelle und akustische Informationen. Für Bilder gibt es dabei prinzipiell
zwei verschiedene Verfahren, nämlich als Vektor- und als Pixelgrafik. Auch für Töne gibt es
verschiedene Repräsentationsformen: als Folge von Noten (Midi), Schwingungsamplituden
(wav, au) oder komprimiert (mp3). Diese Repräsentationsformen lassen sich jedoch auffassen
als strukturierte Zusammensetzungen einfacher Datentypen; wir werden später noch detailliert
auf verschiedene Strukturierungsmöglichkeiten für Daten eingehen.
Wichtig: Der Bitfolge sieht man nicht an, ob sie die Repräsentation einer Zeichenreihe, einer
Folge von ganzen Zahlen oder reellen Zahlen in einer bestimmten Genauigkeit ist. Ohne
Decodierungsregel ist eine codierte Nachricht wertlos!
2.2 Sprachen, Grammatiken, Syntaxdiagramme
Die obigen Darstellungsformen sind geeignet zur Repräsentation einzelner Objekte. Häufig
steht man vor dem Problem, Mengen von gleichartigen Objekten repräsentieren zu müssen.
Für endliche Mengen kann dies durch die Folge der Elemente geschehen; für unendliche
Mengen geht das im Allgemeinen nicht. Daher muss man sich andere (symbolische)
Darstellungsformen überlegen.
Darstellung von Sprachen
Ein besonders wichtiger Fall ist die Darstellung von unendlichen Mengen von Wörtern, die
einem bestimmten Bildungsgesetz unterliegen; zum Beispiel die Menge der syntaktisch
korrekten Eingaben in einem Eingabefeld, oder die Menge der Programme einer
Programmiersprache.
Eine Sprache ist eine Menge von Wörtern über einem Alphabet A.
- z.B. {a, aab, aac}
- z.B. Menge der grammatisch korrekten Sätze der dt. Sprache
- z.B. Menge der JAVA-Programme
Man unterscheidet zwischen natürlichen Sprachen wie z.B. deutsch und englisch und
formalen Sprachen wie z.B. JAVA, C++ oder der Menge der Primzahlen in
Hexadezimaldarstellung. Da Leerzeichen in der Lehre von den formalen Sprachen genau wie
andere Zeichen behandelt werden, gibt es hier keinen Unterschied zwischen Wörtern und
Sätzen.
Unter Syntax versteht man die Lehre von der Struktur einer Sprache
 Welche Wörter gehören zur Sprache?
 Wie sind sie intern strukturiert?
z.B. Attribut, Prädikatverbund, Adverbialkonstruktion
Unter Semantik versteht man die Lehre von der Bedeutung der Sätze
 Welche Information transportiert ein Wort der Sprache?
In der Linguistik betrachtet man manchmal noch den Begriff Pragmatik, darunter versteht
man die Lehre von der Absicht von sprachlichen Äußerungen
 Welchen Zweck verfolgt der Sprecher mit einem Wort?
Berühmtes Beispiel für den Unterschied zwischen Semantik und Pragmatik ist die BeifahrerAussage „Die Ampel ist grün“.
Grammatiken
Grammatiken sind Werkzeuge zur Beschreibung der Syntax einer Sprache. Eine Grammatik
stellt bereit
 A : Alphabet oder „Terminalzeichen“
 H : Hilfssymbole = syntaktische Einheiten (<Objekt<, <Attribut>,..) oder
„Nonterminalzeichen“
Aus A und H bildet man Satzformen (Schemata korrekter Sätze)
z.B. “<Subjekt> <Prädikat> <Objekt>”, “ Heute <Prädikat> < Subjekt> <Objekt> “
 R: Ableitungsregeln – erlaubte Transformationen auf Satzformen
 s: Ein ausgezeichnetes Hilfssymbol („Gesamtsatz”, „Axiom“)
Formal ist eine Grammatik ein Tupel G = [A, H, R, s], wobei
 A und H Alphabete sind mit A  H = Ø
 R  (A  H)+  (A  H)*
 sH
Die Relation R wird meist mit dem Symbol  oder ::= (in Infixschreibweise) notiert.
Zwischen Satzformen definieren wir eine Relation –> (direkte Ableitungsrelation)
w–>w’, falls w = w1°u°w2, w’ = w1°v°w2 und (u,v)  R
Die Ableitungsrelation => ist die reflexiv-transitive Hülle der direkten Ableitungsrelation:
w=>w’, falls w=w’ oder es gibt ein v  (A  H)* mit w–>v und v=>w’
Die von der Grammatik G beschriebene Sprache LG ist definiert durch
LG = { w | s => w und w  A* }
Beispiel
A={“große“, “gute“, “jagen“, “lieben”, “Katzen”, “Mäuse”}
H={<attribut>,<objekt>,<prädikatsverband>,<satz>,<subjekt>,<substantiv>,<verb>}
s=<satz>
R:
<satz>  <subjekt> <prädikatsverband> “.”
<subjekt>  <substantiv>
<subjekt>  <attribut> <substantiv>
<attribut>“gute”
<attribut>“große”
<substantiv>“Katzen”
<substantiv>“Mäuse”
<prädikatsverband><verb> <objekt>
<verb>“lieben”
<verb>“jagen”
<objekt><substantiv>
<objekt><attribut> <substantiv>
Beispiel für Generierung:
<satz> –> <subjekt> <prädikatverband> “.”
–> <attribut> <substantiv> <verb> <objekt> “.”
–> “gute” “Katzen” “jagen” “Mäuse” “.”
Beispiel für Akzeptierung:
“Katzen
lieben
große
Mäuse
<–
<substantiv> <verb>
<attribut>
<substantiv>
<–
<subjekt>
<verb>
<objekt>
<–
<subjekt>
<prädikatverband>
<–
<satz>
.”
“.”
“.”
“.”
Noch ein Beispiel
<S>  <E>
<S>  <S> “+” <E>
<E>  <T>
<E>  <E> “•” <T>
<T>  <F>
<T>  “-” <F>
<F>  “x”
<F>  “0”
<F>  “1”
<F>  “(“ <S> “)”
Ableitung aus dem Beispiel: <S> => “1•(1+x)+0•-1”
<S> –>
<S> “+” <E>
–>
<E> “+”<E> “•” <T>
–>
<E>“•” <T> “+”<T>“ •” “-” <F>
–>
<T>“•” <F> “+” <F>“•” “-” “1”
–>
<F>“•” “ (“ <S> “)” “+” “0” “•” “-” “1”
–>
“1” “•” “(“ <S>“+” <E> “)” “+” “0” “•” “-” “1”
–>
“1” “•” “(“ <E>“+” <T> “)” “+” “0” “•” “-” “1”
–>
“1” “•” “(“ <T>“+” <F> “)” “+” “0” “•” “-” “1”
–>
“1” “•” “(“ <F>“+” “x” “)” “+” “0” “•” “-” “1”
–>
“1” “•” “(“ “1” “+” “x” “)” “+” “0” “•” “-” “1”
Grammatiktypen – Die Chomsky-Hierarchie
Die Lehre von den Grammatiken wurde vom amerikanischen Linguisten Noam Chomsky (*)
(„America's most prominent political dissident“, (*)) in den späten 1950-ern entwickelt.
Chomsky unterscheidet vier Typen von Grammatiken:
 Typ 0 - beliebige Grammatiken
 Typ 1 - kontextsensitive Grammatiken
o Ersetzt wird ein einziges Hilfssymbol, nichtverkürzend
o Alle Regeln haben die Form u+h+w  u+v+w mit h  H
o u , w heißen linker bzw. rechter Kontext
o Sonderregel für das leere Wort
 Typ 2 – kontextfreie Grammatiken
o Ersetzt wird ein einziges Hilfssymbol, egal in welchem Kontext
o Alle Regeln haben die Form h  w mit h  H
 Typ 3 – reguläre Grammatiken
o Wie Typ 2, in w kommt aber max. ein neues Hilfssymbol vor, und zwar ganz
rechts
Abhängig vom Typ spricht man auch von einer Chomsky-i-Grammatik.
Beispiele (A={a, b, c}, H={s, x, y, z, …}):
Chomsky-0-Regeln sind z.B. die folgenden: xyz ::= zyx, abc ::= x, axby ::= abbxy
Eine Chomsky-0-Grammatik für {anbncn | n>0} erhält man durch folgende Regeln:
s::= xs’z; s’ ::= s’s’; s’ ::= abc; ba ::= ab; ca ::= ac; cb ::= bc;
xa ::= ax; x ::= y; yb ::= by; y ::= z; zc ::= cz; zz ::=
xyz ::= xyxyz (Chomsky-1-Regel) Anwendung: axyzxa -> axyxyza -> axyxyxyza -> …
y ::= yxy (Chomsky-2-Regel)
x::= ax; x::= b (Chomsky-3-Sprache a*b )
Eine Sprache heißt Chomsky-i-Sprache, wenn es eine Chomski-i-Grammatik für sie gibt,
aber keine Chomsky-(i-1)-Grammatik. Die vier Typen bilden eine echte Hierarchie, d.h., für
i=0,1,2 kann man jeweils eine Sprache finden, die durch eine Chomsky-i-Grammatik, nicht
aber durch eine Chomsky-i+1-Grammatik beschreibbar ist. Es gilt:
- Mit beliebigen Grammatiken lassen sich alle berechenbaren Sprachen beschreiben
- Kontextsensitive Grammatiken sind algorithmisch beherrschbar
- Für die meisten Programmiersprachen wird die Syntax in Form kontextfreier
Grammatiken angegeben.
- Einfache Konstrukte innerhalb von Programmiersprachen (z.B. Namen, Zahlen)
werden durch reguläre Grammatiken beschrieben.
Aufschreibkonventionen für kontextfreie Grammatiken
Backus-Naur-Form (BNF) verwendet u ::= v | w als Abkürzung für {u ::= v, u ::= w}
Beispiel:
<S> ::= <Expression> | <S> + <Expression>
<Expression> ::= <Term> | <Expression> • <Term>
<Term> ::= <Factor> | - <Factor>
<Factor> ::= x | 0 | 1 | ( <S> )
Erweiterte Backus-Naur-Form (EBNF) löst direkte Rekursion durch beliebigmalige
Wiederholungsklammern {} auf
Beispiel:
S = Expression { “+” Expression }
Expression = Term { “•” Term }
Term = [ “-” ] Factor
Factor = “x” | “0” | “1” | “(” S “)”
Manchmal verwendet man auch zählende Wiederholungen ...i mit der Bedeutung
„…mindestens i und höchstens j mal“, wobei j auch durch einen Stern ersetzt werden kann
*
1
(für „beliebig mal“). Es gilt {x}  x 0 und x   x 0 .
j
Beispiele:
a30  { , a, aa, aaa} ; [a]*2
= {aa, aaa, aaaa, …}
Syntaxdiagramme
Syntaxdiagramme werden zur anschaulichen Notation von Programmiersprachen verwendet.
Reguläre Ausdrücke
Für reguläre Sprachen gibt es die Möglichkeit, sie durch reguläre Ausdrücke aufzuschreiben.
Das sind Ausdrücke, die gebildet sind aus
 der leeren Sprache, die keine Wörter enthält
 den Sprachen bestehend aus einem Wort bestehend aus einem Zeichen des Alphabets
 der Vereinigung von Sprachen (gekennzeichnet durch +)
 Konkatenation (gekennzeichnet durch Hintereinanderschreibung,  oder ;)
 beliebiger Wiederholung (gekennzeichnet durch *)
Beispiele:
(aa)* (gerade Anzahl von a)
aa* (mindestens ein a, auch a+ geschrieben)
((a + b)b)* (jedes zweite Zeichen ist b)
Reguläre Ausdrücke werden z.B. in Editoren wie dem Emacs verwendet, um bestimmte zu
suchende Zeichenreihenmengen zu repräsentieren. Beispiel für eine Suche ist: „Suche eine
Zeichenreihe, die mit c beginnt und mit c endet und dazwischen eine gerade Anzahl von a
enthält.“ Das Kommando replace-regexp \(aa\)+ b ersetzt jedes Paar „aa“ durch „b“
Endliche Automaten sind Syntaxdiagramme ohne Abkürzungen, d.h. es gibt keine
„Kästchen“. Ein endlicher Automat hat genau einen Eingang und mehrere mögliche
Ausgänge.
a
Beispiele:
b
a
a
a
a
b
Üblicherweise werden bei Automaten die Kreuzungen als Kreise gemalt und Zustände (states)
genannt, die Zustände werden mit Pfeilen verbunden (sogenannten Transitionen, transitions),
die mit Terminalsymbolen beschriftet sind.
a
a
a
a
b
a
b
Ein fundamentaler Satz der Theorie der formalen Sprachen besagt, dass mit regulären
Ausdrücken genau die durch reguläre Grammatiken definierbaren Sprachen beschrieben
werden können, welches wiederum genau die Sprachen sind, die durch endliche Automaten
beschrieben werden können.
2.3 Darstellung von Algorithmen
Ein Algorithmus ist ein präzises, schrittweises und endliches Verfahren zur Lösung eines
Problems oder einer Aufgabenstellung (insbesondere zur Verarbeitung von Informationen,
vgl. Kap. 0). Das bedeutet, an einen Algorithmus sind folgende Anforderungen zu stellen:
 Präzise Beschreibung (relativ zu den Kommunikationspartnern) der zu bearbeitenden
Informationen und ihrer Repräsentation
 Explizite, eindeutige und detaillierte Beschreibung der einzelnen Schritte (relativ zu
der Person oder Maschine, die den Algorithmus ausführen soll)
 Endliche Aufschreibung des Algorithmus, jeder Einzelschritt ist in endlicher Zeit
ausführbar, und jedes Ergebnis wird nach endlich vielen Schritten erzielt.
Um Algorithmen von einem Computer ausführen zu lassen, müssen sie (genau wie andere
Informationen) in einer für die jeweilige Maschine verständlichen Form repräsentiert werden.
Eine solche Repräsentation nennt man Programm. Zum Beispiel kann das eine Folge von
Maschinenbefehlen sein, die ein bestimmter Rechner ausführen kann. Die tatsächliche
Ausführung eines Algorithmus bzw. Programms nennt man einen Prozess. Sie findet auf
einem (menschlichen oder maschinellen) Prozessor statt. Ein Algorithmus bzw. Programm
terminiert, wenn seine Ausführung nach einer endlichen Zahl von Schritten (Befehlen)
abbricht. Ein Algorithmus (bzw. ein Programm) heißt deterministisch, wenn für jeden Schritt
der nächste auszuführende Schritt eindeutig definiert ist. Er bzw. es heißt determiniert, wenn
die Berechnung nur ein mögliches Ergebnis hat.
Beispiel: Wechselgeldbestimmung
Aufgabe: Bestimmung des Wechselgeldes w eines Fahrscheinautomaten auf 5 Euro bei einem
Preis von p Euro (zur Vereinfachung fordern wir, p sei ganzzahliges Vielfaches von 10 Cent;
es werden nur 10-, 20- und 50-Cent Münzen zurückgegeben).
Der Algorithmus ist nicht determiniert! Damit das Ergebnis eindeutig bestimmt ist, legen wir
zusätzlich fest: „es sollen möglichst wenige Münzen zurückgegeben werden“.
Als erstes muss die Repräsentation der Schnittstellen festgelegt werden: p könnte zum
Beispiel als natürliche Zahl in Cent, als rationale Zahl oder als Tupel (Euro, Cent)
repräsentiert werden, und w als Multimenge oder Folge von Münzen. Dann erfolgt die
Aufschreibung des Algorithmus.
1. Darstellung in natürlicher Sprache: „Die Rückgabe enthält maximal eine 10-Cent-Münze,
zwei 20-Cent-Münzen, und den Rest in 50-Cent-Münzen. 10-Cent werden zurückgegeben,
falls dies nötig ist, um im Nachkommabereich auf 0, 30, 50, oder 80 Cent zu kommen. Eine
oder zwei 20-Cent-Münzen werden zurückgegeben, um auf 0, 40, 50 oder 90 Cent zu
kommen. Wenn der Nachkommaanteil 0 oder 50 ist, werden nur 50-Cent-Münzen
zurückgegeben.“
Ein Vorteil der natürlichen Sprache ist, dass sie sehr flexibel ist und man damit (fast) alle
möglichen Ideen aufschreiben kann. Nachteile bei der Darstellung von Algorithmen in
natürlicher Sprache sind, dass keine vollständige formale Syntax bekannt ist, d.h. es ist nicht
immer leicht festzustellen, ob eine Beschreibung syntaktisch korrekt ist oder nicht, und dass
die Semantik weitgehend der Intuition überlassen bleibt. Dadurch bleibt immer eine gewisse
Interpretationsfreiheit, verschiedene Prozessoren können zu verschiedenen Ergebnissen
gelangen.
2. Darstellung als nichtdeterministischer Pseudo-Code:
Sei w={} Multimenge;
Solange p<5 tue
[] erste Nachkommastelle von p {2,4,7,9}:
p = p + 0.10, w = w  {10c}
[] erste Nachkommastelle von p {1,2,3,6,7,8}:
p = p + 0.20, w = w  {20c}
[] erste Nachkommastelle von p {0,1,2,3,4,5}:
p = p + 0.50, w = w  {50c}
Ergebnis w
Auch Pseudo-Code verwendet keine feste Syntax, es dürfen jedoch nur solche
Sprachelemente verwendet werden, deren Bedeutung (Semantik) klar definiert ist. Damit ist
die Menge der möglichen Ergebnisse zu einer Eingabe eindeutig bestimmt.
Berechnungsbeispiel:
p=3.20, w={}  p=3.70, w={50c}  p=3.80, w={50c, 10c}  p=4.30, w={50c, 10c, 50c}
 p=4.50, w={50c, 50c, 10c, 20c}  p=5.00, w={50c, 50c, 50c, 20c, 10c}
3. Darstellung mathematisch-rekursiv:
Wechselgeld (p) =







{}, falls p=5
0,50+Wechselgeld (p+0.50), falls p5 und 5-p  0.50
0,20+Wechselgeld (p+0.20), falls p5, 5-p < 0.50 und 5-p  0.20
0,10+Wechselgeld (p+0.10), sonst
Beispiel zum Berechnungsablauf:
Wechselgeld(3.20) =0.50+Wechselgeld(3.70) =0.50+0.50+Wechselgeld(4.20)
=0.50+0.50+0.50+Wechselgeld(4.70) =0.50+0.50+0.50+0.20+Wechselgeld(4.90)
=0.50+0.50+0.50+0.20+0.10+Wechselgeld(5.00) =0.50+0.50+0.50+0.20+0.10
Die Syntax und Semantik in dieser Aufschreibung folgt den üblichen Regeln der Mathematik
bzw. Logik, ist fest definiert, aber jederzeit erweiterbar. In der Schreibweise der Informatik
könnte man dasselbe etwa wie folgt aufschreiben:
Wechselgeld (p) =
Falls 5-p=0 dann Rückgabe({}) sonst
falls 5-p  0.50 dann Rückgabe(0.50+Wechselgeld(p+0.50)) sonst
falls 5-p  0.20 dann Rückgabe(0.20+Wechselgeld(p+0.20)) sonst
Rückgabe(0.10+Wechselgeld(p+0.10))
Diese Notation lässt sich unmittelbar in funktionale Programmiersprachen übertragen. Als
BC-Programm aufgeschrieben sieht das etwa so aus:
define wg(p){
if (5-p==0) return(0) else
if (5-p>=0.50) return(100+wg(p+0.50)) else
if (5-p>=0.20) return(10+wg(p+0.20)) else
return(1+wg(p+0.10))}
Hier wählen wir zur Darstellung des Ergebnisses eine dreistellige natürliche Zahl, deren erste
Stelle die Anzahl der 50-Cent-Münzen, die zweite die Anzahl der 20-Cent-Münzen und die
letzte die Anzahl der 10-Cent-Münzen angibt.
wg(2.70)
411
wg(3.10)
320
4. Darstellung als Ablaufdiagramm oder Flussdiagramm.
Start
Int(Fract(p)*10)
 {1, 6}?
w={}
Int(Fract(p)*10)
 {2,4,7,9}?
n
n
y
w+={20c}
p +=0.2
y
w+={10c}
p +=0.1
p<5?
y
w+={50c}
p +=0.5
Int(Fract(p)*10)
 {1, 6}?
n
y
w+={20c}
p +=0.2
Stop
Ein Ablaufdiagramm ist prinzipiell ein endlicher Automat! Zusätzlich dürfen jedoch
Variablen, Bedingungen, Zuweisungen, Unterprogramme und andere Erweiterungen
verwendet werden.
Ausführungsbeispiel:
p=3.20, w={}  p=3.30, w={10c}  p=3.50, w={10c, 20c}  p=4.00, w={10c, 20c, 50c}
 p=4.50, w={10c, 20c, 50c, 50c}  p=5.00, w={10c, 20c, 50c, 50c, 50c}
5. Darstellung als Java-Programm (mit heftigem Gebrauch von Mathematik)
static int fünfzigCentStücke(double p) {
return (int)((5 - p) * 10 / 5) ;}
static int zehnCentStücke(double p) {
int cent = (int)((p-(int)(p)) * 100);
return((cent==20 || cent==40 || cent==70 || cent==90) ? 1 : 0) ;}
static int zwanzigCentStücke(double p) {
int tencent = (int)((p-(int)(p)) * 10);
int[] coins = {0,2,1,1,0,0,2,1,1,0};
return coins[tencent] ;}
Syntax und Semantik sind eindeutig definiert, jedes in Java aufschreibbare Programm ist
determiniert und deterministisch (Ausnahme: Verwendung paralleler Threads). Wie man in
diesem Fall auch leicht sieht, terminiert das Programm auch für jede Eingabe. Eine wichtige
Frage ist die nach der Korrektheit, d.h. berechnet das Programm wirklich das in der
Aufgabenstellung verlangte?
n
6. Darstellung als Assemblerprogramm
In der Frühzeit der Informatik wurden Rechenmaschinen programmiert, indem eine Folge von
Befehlen angegeben wurde, die direkt von der Maschine ausgeführt werden konnte.
Assemblersprachen sind Varianten solcher Maschinensprachen, bei denen die
Maschinenbefehle in mnemotechnischer Form niedergeschrieben sind. Häufig verwendete
Adressen (Nummern von Speicherzellen) können mit einem Namen versehen werden und
dadurch referenziert werden. Viele Assemblersprachen erlauben auch indirekte Adressierung,
d.h. der Inhalt einer Speicherzelle kann die Adresse einer anderen Speicherzelle. Die in einer
Assemblersprache verfügbaren Befehle hängen stark von der Art der zu programmierenden
Maschine ab. Typische Assemblerbefehle sind z.B. mov rx ry (Bedeutung: transportiere /
kopiere den Inhalt von rx nach ry) oder jgz rx lbl (Bedeutung: wenn der Inhalt von rx größer
als Null ist, gehe zur Sprungmarke lbl)
// Eingabe: Preis in Register p in Cent (wird zerstört)
// Ergebnisse in fc, zc, tc (fünfzigCent, zwanzigCent, tenCent)
mov 0, fc
mov 0, zc
mov 0, tc
loop:
mov p, ac // lade p in den Akkumulator
sub ac, 450 // subtrahiere 450
jgz hugo // jump if greater zero to hugo
add ac, 500 // addiere 500
mov ac, p // speichere Akkumulator nach p
mov fc, ac
add ac, 1
mov ac, fc // fc := fc + 1
goto loop
hugo:
mov p, ac // wie oben
sub ac, 480
jgz erna
add ac, 500
mov ac, p
mov zc, ac
add ac, 1
mov ac, zc
goto hugo
erna:
mov p, ac
sub ac, 490
jgz fertig
mov 1, tc
fertig:
7. Darstellung als Maschinenprogramm
Ein Maschinenprogramm ist eine Folge von Befehlen, die direkt von einer Maschine eines
dafür bestimmten Typs ausgeführt werden kann. Jedem Assemblerbefehl entspricht dabei eine
bestimmte Anzahl von Bytes im Maschinenprogramm.
Programmiersprachen
Ein Programm ist, wie oben definiert wurde, die Repräsentation eines Algorithmus in einer
Programmiersprache. Die Menge der syntaktisch korrekten Programme einer bestimmten
Programmiersprache (JAVA, C, Delphi, …) wird im Allgemeinen durch eine kontextfreie
Grammatik beschrieben. Maschinen- und Assemblersprachen sind dabei sehr einfache
Sprachen (die sogar durch reguläre Grammatiken definiert werden könnten). Das
Programmieren in einer Maschinensprache oder Assembler ist außerordentlich mühsam und
sehr fehleranfällig. Höhere Programmiersprachen sind nicht an Maschinen orientiert,
sondern an den Problemen. Programme, die in einer höheren Programmiersprache
geschrieben sind, können nicht unmittelbar auf einem Rechner ausgeführt werden. Sie werden
entweder von einem speziellen Programm interpretiert (d.h., direkt ausgeführt) oder von
einem Compiler in eine Folge von Maschinenbefehlen übersetzt und erst dann ausgeführt.
Bei einigen Prgrammiersprachen (Java, C#, USCD-Pascal) erfolgt die Übersetzung zunächst
in die Maschinensprache einer virtuellen Maschine, d.h. einer nicht in Hardware realisierten
Maschine, welche daher unabhängig von einer speziellen Hardwaretechnologie ist. Die
Ausführung von Maschinenprogrammen der virtuellen auf einer realen Maschine erfolgt von
speziellen Interpretern (der natürlich für jede reale Maschine neu entwickelt werden muss).
Die Sprachen, die einer maschinellen Behandlung nicht zugänglich sind und von Menschen in
Programme überführt werden müssen, nennen wir Spezifikationssprachen.
Kapitel 3: Rechenanlagen
Um Programme auszuführen, ist ein Prozessor erforderlich, der die einzelnen Schritte tätigt.
Das kann ein Mensch oder eine Maschine (auf mechanischer, elektronischer oder
biochemischer Basis) sein, oder sogar ein anderes Programm, welches eine
Ausführungsmaschine nur simuliert.
3.1 Historische Entwicklung
Die Entwicklung und den Aufbau moderner Rechner begreift man besser, wenn man sich ihre
historischen Wurzeln betrachtet.





1842 Charles Babbage / Ada Lovelace: „Die analytische Maschine“; Konzept einer
programmierbaren mechanischen Rechenanlage zur Lösung von
Differentialgleichungen. Dieser „erste Computer der Weltgeschichte“ wurde jedoch
nie realisiert, da Kosten, Machbarkeit und Haltbarkeit nicht einschätzbar waren
1936: Alonzo Church (1903-1995): „lambda-Kalkül“, Begriff der berechenbaren
Funktion
1936: Alan Turing: Computer als universelle Maschine; Äquivalenz von Programm
und Daten („Turing-Maschine“)
1941 Konrad Zuse: Z3: vollautomatischer, programmierbarer, in binärer
Gleitkommarechnung arbeitender Rechner mit Speicher und einer
Zentralrecheneinheit aus Telefonrelais
1946 Johan von Neumann (EDVAC-Report): konkrete Vorschläge für Aufbau („vonNeumann-Computer“)
Programmierparadigmen:
- Analytische Maschine: Rechnen als Durchführung arithmetischer Operationen
- lambda-Kalkül, Lisp-Maschine: Rechnen als Termersetzung
- Turing-Maschine (vgl. ThI): Rechnen als Schreiben von Zeichen auf ein Band
- von Neumann: Rechnen als Modifikation von Wörtern im Speicher.
Die analytische Maschine
Babbage’s „analytische Maschine“
(*) war das „Nachfolgemodell“ der
„Differenzmaschine“ (1821-1833),
die arithmetische Berechnungen
durchführen können sollte, aber nie
funktionierte. Zitat eines
Zeitgenossen (L. F. Menabrea, *):
“Mr. Babbage has devoted some
years to the realization of a
gigantic idea. He proposed to
himself nothing less than the
construction of a machine capable
of executing not merely
arithmetical calculations, but even
all those of analysis, if their laws
are known.” Historisches Vorbild
waren mit so genannten JaquardLochkarten „programmierbare“
Webstühle (mit bis zu 24000 Karten). Die Rechenmaschine sollte ein „Mill“ genanntes
Rechenwerk, ein „Store“ genanntes Speicherwerk (1000 fünfzigstellige Zahlen),
Lochkartenleser und -stanzer als Ein- und Ausgabe und einen Drucker als Ausgabe enthalten.
Die Maschine sollte mit Dampf angetrieben und frei programmierbar sein. Ein Programm
sollte drei Kartentypen enthalten:
 Operationskarten
enthalten mögliche Operationen: Addition, Subtraktion, Multiplikation und Division.
Die Maschine hat einen Schalter für den auszuführenden Operationstyp, der in seiner
Stellung bleibt bis er durch eine Operationskarte umgestellt wird.
 Zahlenkarten
enthalten numerische Konstanten und dienen als externer Speicher, damit nicht alle

benötigten Zahlen im (teuren) Speicherwerk bereit gehalten werden müssen. Auf einer
Zahlenkarte steht jeweils neben dem Wert auch die Nummer des Speichers, in
welchen dieser Wert geschrieben werden soll. Zwischenergebnisse können von der
Maschine auf Karten gestanzt und später wieder eingelesen werden.
Variablenkarten
steuern den Transfer von Werten aus dem Store zur Mill und zurück („Adressierung“).
Die Maschine besitzt zwei Operandenregister („Ingress-Achsen“, je zweimal 50
Stellen: I1 und I1´, I2 und I2´) und ein Resultatregister („Egress-Achse“, zweimal 50
Stellen: E und E´); es gibt Karten zum Transport einer Variable (eines Speicherwerts)
in die Ingress-Achsen und zum Transport der Egress-Achse in den Speicher.
Spezielle Karten sind kombinatorische und Indexkarten, die im Kartenstapel vor- und
zurückblättern können und somit Sprünge realisieren. Für Verzweigungen gibt es einen
„Alarmhebel“, der hochgesetzt wird, falls
 bei einer arithmetischen Operation ein Überlauf oder eine Division durch Null auftritt
 das Ergebnis einer arithmetischen Operation ein anderes Vorzeichen hat als das erste
Argument (d.h. Egress-Achse E hat ein anderes Vorzeichen als die Ingress-Achse I1 )
Ferner gibt es Kontrollkarten wie „Stopp“ und „Pause“.
Aus den vorhandenen Dokumenten lässt sich rekonstruieren, dass für die analytischen
Maschine folgende Befehle vorgesehen waren (Ausschnitt):
<Programm> ::= {Karte}
<Karte> ::= <Zahlkarte> | <Opkarte> | <Varkarte>
<Zahlkarte> ::= “N”[z ]13 _ [“+|“-][z ]50
0
Die Zahl wird an der bezeichneten Stelle in den Speicher eingetragen
<Opkarte> ::= “+” | “-” | “*” | “/”
Die Operation wird für nachfolgende Befehle eingestellt
<Varkarte> ::= <Transferkarte> | <Kartenkarte> | <Atkarte>
<Transferkarte> ::= (“L”|“Z”|“S”)[z ]13 [“´”]
Lzzz: Transfer des Inhalts der Variable zzz in die Mill Ingress Achse
Zzzz: Wie Lzzz, wobei Variable zzz gleichzeitig auf Null gesetzt wird
Szzz: Transfer der Egress-Achse in Variable zzz
Falls ein ´ nach der Adresse steht, sind die zweiten 50 Stellen betroffen
Transfer in I2 löst die Ausführung der eingestellten Operation aus
<Kartenkarte> ::= “C”(“F”|“B”)(“+”|“?”)[z ]50
0
blättert die angegebene Zahl von Karten vor (F) oder zurück (B)
+ bedeutes unbedingtes, ? bedingtes Blättern (falls Alarmhebel hochgesetzt)
<Atkarte> ::= “B” | “H” | “P”
B läutet eine Glocke um den Operateur zu verständigen
H hält die Maschine an (keine weiteren Karten werden gelesen)
P druckt den Wert der Egress-Achse auf dem Druckapparat
Beispiel: drucke 17 + 4
N001
+00000000000000000000000000000000000000000000000017
N002
+00000000000000000000000000000000000000000000000004
+
L001
L002
P
Beispiel: Variable in Speicher 003 := 10000 div 28, Variable 004 := 10000 mod 28,
N1 10000
N2 28
/
L1
L2
S3'
S4
Beispiel: Fakultätsfunktion S2 := S1!
N1 6
N2 1
N3 1
*
L2
L1
S2
L1
L3
S1
L3
L1
CB?11
Beispiel: drucke Tabelle für f(x) = x2 + 6x + 6, x=1..10
V1 = x
V2 = x2
V3 = 6
V4 = x2, x2+6x, x2+6x+6
V5 = 6x
N1 10
N3 6
*
L1
L1
S4
L1
L3
S5
+
L4
L5
S4
L4
L3
S4
…
Umformung als f(x) = (x+3)2 – 3 bringt eine Verbesserung:
V1 = x
V2 = 3
V3 = x+3, (x+3)2, (x+3)2 – 3
N1 10
N2 3
+
L1
L2
S3
*
L3
L3
S3
L3
L2
S3
Ada Lovelace schlägt etliche solcher Verbesserungs-Transformationen vor.
Ähnliche (einfachere) Optimierungen heute im Code-Generator guter Compiler enthalten.
Ada Lovelace schreibt: “the Analytical Engine does
not occupy common ground with mere `calculating
machines´”… “on the contrary, (it) is not merely
adapted for tabulating the results of one particular
function and of no other, but for developing and
tabulating any function whatever. In fact the engine
may be described as being the material expression
of any indefinite function of any degree of
generality and complexity.“ …
“It may be desirable to explain, that by the word
operation, we mean any process which alters the
mutual relation of two or more things, be this
relation of what kind it may. This is the most
general definition, and would include all subjects in
the universe.” …
“Supposing, for instance, that the fundamental
relations of pitched sounds in the science of
harmony and of musical composition were
susceptible of such expression and adaptations, the
engine might compose elaborate and scientific
pieces of music of any degree of complexity or
extent.“ …
Gedanken zur Universalität mathematischer Funktionen
Die Turingmaschine
Nahezu 100 Jahre später (1936) untersuchte Alan Turing die Grenzen des Berechenbaren. Die
Arbeit „On Computable Numbers, with an Application to the Entscheidungsproblem“ kann
als der Beginn der modernen Informatik betrachtet werden. Turing definierte darin eine
hypothetische Maschine, die die „Essenz des Rechnens“ durchführen können soll. Eine
„berechenbare“ reelle Zahl ist eine, deren (unendliche Folge von) Nachkommastellen endliche
Mittel (d.h. durch einen Algorithmus) berechnet werden kann. Turing schreibt:
„Computing is normally done by writing certain symbols on paper. We may suppose this
paper is divided into squares like a child's arithmetic book. In elementary arithmetic the twodimensional character of the paper is sometimes used. But such a use is always avoidable, and
I think that it will be agreed that the two-dimensional character of paper is no essential of
computation. I assume then that the computation is carried out on one-dimensional paper, i.e.
on a tape divided into squares. I shall also suppose that the number of symbols which may be
printed is finite. If we were to allow an infinity of symbols, then there would be symbols
differing to an arbitrarily small extent. …
The behaviour of the computer at any moment is determined by the symbols which he is
observing, and his "state of mind" at that moment. We may suppose that there is a bound B to
the number of symbols or squares which the computer can observe at one moment. If he
wishes to observe more, he must use successive observations. We will also suppose that the
number of states of mind which need be taken into account is finite. The reasons for this are
of the same character as those which restrict the number of symbols. If we admitted an
infinity of states of mind, some of them will be "arbitrarily close" and will be confused.“
Eine Turingmaschine ist also gegeben durch
 einen endlichen Automaten zur Programmkontrolle, und
 ein (potentiell unbegrenztes) Band auf dem die Maschine Zeichen über einem
gegebenen Alphabet notieren kann.
Zu jedem Zeitpunkt kann die Maschine genau eines der Felder des Bandes lesen (abtasten,
„scannen“), und, ggf.. abhängig von der Inschrift dieses Feldes, das Feld neu beschreiben,
zum linken oder rechten Nachbarfeld übergehen, und einen neuen Zustand einstellen.
Hier ist eine Syntax, die Turings Originalschreibweise nahe kommt.
<Turingtabelle> ::= {<Zeile>}
<Zeile> ::= <Zustand> <Abtastzeichen> <Operation> <Zustand>
<Zustand> ::= <Identifier>
<Abtastzeichen> ::= <Zeichen>
<Operation> ::= {R | L | P<Symbol>}
Dabei wird angenommen, dass für jeden Zustand und jedes Abtastzeichen des Alphabets
genau eine Zeile der Tabelle existiert, welche Operation und Nachfolgezustand festlegt.
(Turing nennt solche Maschinen “automatisch”, wir nennen sie heute “deterministisch”. In
Turings Worten: „If at any stage the motion of a machine is completely determined by the
configuration, we shall call the machine an ‚automatic machine’ ... For some purposes we
might use machines (choice machines or c-machimes) whose motion is only partially
determined by the configuration… When such a machine reaches one of these ambiguous
configurations, it cannot go on until some arbitrary choice has been made by an external
operator… In this paper I deal only with automatic machines.’’)
Da die Tabellen für deterministische Turingmaschinen oft sehr groß werden, darf man Zeilen,
die nicht benötigte werden, weglassen, und Zeilen, die sich nur durch das Abtastzeichen
unterscheiden, zusammenfassen. In der entsprechenden Zeile sind beliebige Mengen von
Abtastzeichen zugelassen. „any“ steht dann für ein beliebiges Abtastzeichen, d.h. für das
gesamte Alphabet. Ferner fordert Turing, dass das Alphabet immer ein spezielles Leerzeichen
„none“ enthält, und „E“ eine Abkürzung für „P none“ ist..
Beispiel für eine Maschine, die das Muster „0 11 0 11 0 11…“ auf ein leeres Band schreibt:
s0
s1
any
any
P0,R,R s1
P1,R,P1,R,R
s0
Ein äquivalentes Programm mit nur einem Zustand s0 ist
s0 none P0 s0
s0 0 R,R,P1,R,P1 s0
s0 1 R,R,P0 s0
Beispiel für eine Maschine, die die Sequenz 0 01 011 0111 01111 … erzeugt:
Arbeitsweise dieser Maschine:
Die von Turing verwendete Tabellenschreibweise für Programme betrachten wir heute als
unleserlich. Eine moderne Variante („Turing-Assembler“) wäre etwa:
<Turingprogram> ::= {<statement>}
<statement> ::= <label>“:” | “print” <symbol> “;” |
“left;” | “right;” | “goto” <label>“;”
<label> ::= <Identifier>
In dieser Notation sähe unser Beispielprogramm etwa so aus:
label0:
print 0;
right;
right;
print 1;
right;
print 1;
right;
right;
goto s0;
Im Internet sind viele Turingmaschinen-Simulatoren verfügbar, versuchen Sie z.B.
http://math.hws.edu/TMCM/java/labs/xTuringMachineLab.html
Andere empfohlene Beispiele:
http://www.matheprisma.uni-wuppertal.de/Module/Turing/
http://ais.informatik.uni-freiburg.de/turing-applet/turing/TuringMachineHtml.html
Zuse Z3
erster voll funktionsfähiger programmierbarer Digitalrechner
viele Merkmale moderner Rechner:
 Relais-Gleitkommaarithmetikeinheit für Arithmetik
 einem Relais-Speicher aus 64 Wörtern, je 22 bit
 einem Lochstreifenleser für Programme auf Filmstreifen
 eine Tastatur mit Lampenfeld für Ein- und Ausgabe von Zahlen und der manuellen
Steuerung von Berechnungen.
 Taktung durch Elektromotor, der Taktwalze antreibt (5rps)
Programmiersprache: Plankalkül
http://www.zib.de/zuse/Inhalt/Programme/Plankalkuel/Compiler/plankalk.html
Von-Neumann-Rechner, EDVAC & ENIAC
John von Neumann wurde vor hundert Jahren (im Dezember 1903) in
Budapest geboren. 1929 wurde er als jüngster Privatdozent in der
Geschichte der Berliner Universität habilitiert. Von Neumann wurde
binnen kurzer Zeit weltberühmt durch seine vielfältigen Interessen auf
dem Gebieten Mathematik, Physik und Ökonomie. 1930 emigrierte er
wegen der Nazis nach USA. In Princeton schuf von Neumann dann mit
dem von ihm erdachten Rechnerkonzept die Grundlagen für den Aufbau
elektronischer Rechenanlagen, die noch bis heute gültig sind. Er gilt
daher als einer der Begründer der Informatik
EDVAC, ENIAC (Electronic Numerical Integrator and Computer)
 Elektronenröhren zur Repräsentation von Zahlen
 elektrische Pulse für deren Übertragung
 Dezimalsystem
 Anwendung: H-Bomben-Entwicklung
 Programmierung durch Kabel und Drehschalter
3.2 von-Neumann-Architektur
1945 First Draft of a Report on the EDVAC (Electronic Discrete Variable Automatic
Computer): Befehle des Programms werden wie zu verarbeitenden Daten behandelt, binär
kodiert und im internen Speicher verarbeitet (vgl. Zuse, Turing)
Ein von-Neumann-Computer enthält mindestens die folgenden fünf Bestandteile:
1. Input unit (kommuniziert mit der Umgebung)
2. Main memory (Speicher für Programme und Daten)
3. Control unit (führt die Programme aus)
4. Arithmetic logical unit (für arithmetische Berechnungsschritte)
5. Output unit (kommuniziert mit der Umgebung)
Rechenwerk (central processing unit, CPU)
Steuerwerk
(control unit)
Rechenwerk
(arithmetic logical unit, ALU)
Hauptspeicher (Main memory)
Eingabe (input)
Ausgabe (output)
Prinzipien:
 Der Rechner enthält zumindest Speicher, Rechenwerk, Steuerwerk und
Ein/Ausgabegeräte. „EVA-Prinzip“: Eingabe – Verarbeitung – Ausgabe
 Der Rechner ist frei programierbar, d.h., nicht speziell auf ein zu bearbeitendes
Problem zugeschnitten; zur Lösung eines Problems wird ein Programm im
Speicher ablegt. Dadurch ist jede nach der Theorie der Berechenbarkeit mögliche
Berechnung programmierbar.
o Programmbefehle und Datenworte liegen im selben Speicher und werden je
nach Bedarf gelesen oder geschrieben.
o Der Speicher ist unstrukturiert; alle Daten und Befehle sind binär codiert.
o Der Speicher wird linear (fortlaufend) adressiert. Er besteht aus einer Folge
von Plätzen fester Wortlänge, die über eine bestimmte Adresse einzeln
angesprochen werden können und bit-parallel verarbeitet werden.
o Die Interpretation eines SpeicherinhaltsSteuerwerk
hängt nur vom aktuellen Kontext
(control
unit)
des laufenden Programms ab. Insbesondere:
Befehle
können Operanden
anderer Befehle sein (Selbstmodifikation)!
 Der Befehlsablauf wird vom Steuerwerk bestimmt. Er folgt einer sequentiellen
Befehlsfolge, streng seriell und taktgesteuert.
o Zu jedem Zeitpunkt führt die CPU nur einen einzigen Befehl aus, und
dieser kann (höchstens) einen Datenwert verändern (single-instructionsingle-data).
o Die normale Verarbeitung der Programmbefehle geschieht fortlaufend in
der Reihenfolge der Speicherung der Programmbefehle. Diese sequentielle
Programmabarbeitung kann durch Sprungbefehle oder datenbedingte
Verzweigungen verändert werden.
 Die ALU führt arithmetische Berechnungen durch, indem sie ein oder zwei
Datenwerte gemäß eines Befehls verknüpft und das Ergebnis in ein vorgegebenes
Register schreibt. Zwei-Phasen-Konzept der Befehlsverarbeitung:
o In der Befehlsbereitstellungs- und Decodierphase-Phase wird, adressiert
durch den Befehlszähler, der Inhalt einer Speicherzelle geholt und als
Befehl interpretiert.
o In der Ausführungs-Phase werden die Inhalte von einer oder zwei
Speicherzellen bereitgestellt und entsprechend den Opcode als Datenwerte
verarbeitet.
 Datenbreite, Adressierungsbreite, Registeranzahl und Befehlssatz als Parameter
der Architektur
 Ein- und Ausgabegeräte sind z.B. Schalter und Lämpchen, aber auch entfernte
Speichermedien (Magnetbänder, Lochkarten, Platten, …). Sie sind mit der CPU
prinzipiell auf die selbe Art wie der Hauptspeicher verbunden.
Zentrale Befehlsschleife (aus *):
Vergleiche: Assemblersprache, Ausführung eines Befehles
Vor- und Nachteile der von-Neumann-Architektur:
 minimaler Hardware-Aufwand, Wiederverwendung von Speicher
 Konzentration auf wesentliche Kennzahlen: Speichergröße, Taktfrequenz
- Verbindungseinrichtung CPU – Speicher stellt einen Engpass dar („von-NeumannFlaschenhals“)
- keine Strukturierung der Daten, Maschinenbefehl bestimmt Operandentyp
„von-Neumann-bottleneck“ John Backus, Turing-Award-Vorlesung 1978:
When von Neumann and others conceived it [the von Neumann computer] over thirty years ago, it was
an elegant, practical, and unifying idea that simplified a number of engineering and programming
problems that existed then. Although the conditions that produced its architecture have changed
radically, we nevertheless still identify the notion of "computer" with this thirty [jetzt fast sechzig] year
old concept.
In its simplest form, a von Neumann computer has three parts" a central processing unit (or CPU), a
store, and a connecting tube that can transmit a single word between the CPU and the store (and send an
address to the store). I propose to call this tube the von Neumann bottleneck. The task of a program is to
change the store in a major way; when one considers that this task must be accomplished entirely by
pumping single words back and forth through the von Neumann bottleneck, the reason for its name
becomes clear.
Wie oben erwähnt, sind auch heute noch die meisten Computer nach der von-NeumannArchitektur konstruiert. Beispiel: Architektur des Intel Pentium-Prozessors (*):
Realisierung der einzelnen Baugruppen durch Mengen von Halbleiterschaltern; z.B. eines
Halbaddierers (Summe= E1 XOR E2, Übertrag=E1 AND E2):
Beschreibung der Hardware auf verschiedenen Ebenen: Physikalische Ebene, TransistorEbene, Gatter-Ebene (s.o.), Register-Ebene, Funktionsebene (siehe TI)
Abweichungen und Varianten der von-Neumann-Architektur:
 Spezialisierte Eingabe-Ausgabe-Prozessoren
o z.B. Grafikkarte mit 3D-Rendering
o z.B. Modem oder Soundkarte zur Erzeugung von Tönen
o z.B. Tastatur-, Netzwerk- oder USB-Controller, die auf externe Signale warten
 Parallelität zwischen/innerhalb von Funktionseinheiten, z.B.
o Blocktransfer von Daten
o Pipelining in CPU
 Duplikation von Funktionseinheiten
o z.B. Mehrprozessorrechner, Mehrkern-Architektur: Zwei oder mehr CPU
auf einem Chip, Kopplung durch speziellen Memory-Control-Bus; z.B.
Pentium Extreme Edition 840 (April 2005), Preis 999 Dollar, „dürfte allerdings
nur wenig Käufer finden“; Aktuell: Quadcore, Octocore (Sun UltraSparc, Intel
Nehalem 2008);
 komplexere Speicherstrukturen, z.B.
o Register (einzelne Speicherwörter direkt in der CPU)
o Caches (schnelle Pufferspeicher) und Bus-Hierarchien für Verbreiterung des
Flaschenhalses
o Harvard-Architektur (Trennung von Daten- und Befehlsspeicher)
 komplexere Verbindungsstrukturen
o externe Standardschnittstellen: IDE, SCSI, USB, IEEE1394/Firewire/iLink
o ISA/PCI/AGP: hierarchischer bzw. spezialisierter Aufbau des Bussystems
 komplexere Befehle, z.B.
o mehrere Operanden
o indirekte Adressierung („Adresse von Adresse“)
o CISC versus RISC
 Programmunterbrechung durch externe Signale
o Interrupt-Konzept
o Mehrbenutzer-Prozesskonzept
3.3 Aufbau PC/embedded system, Speicher
Heutige PCs sind meist prinzipiell nach der von-Neumann-Architektur , mit o.g.
Erweiterungen, konstruiert. Beispiel (Gumm/Sommer p55)
Prinzipiell ist dieser Aufbau auch in den meisten eingebetteten Steuergeräten zu finden.
Beispiel: ein Steuergerät im Audi quattro, welches zwei separate Prozessoren für Zündung
und Ladung enthält, und ein seriell einstellbares Drehzahlsteuergerät für Elektromotoren.
Unterschiede zur „normalen“ Rechnerarchitektur:
 Ein- und Ausgabegeräte sind Sensoren und Aktuatoren
 Wandlung analoger in digitale Signale und umgekehrt auf dem Chip (A/D D/A)
 Der Speicher ist oft nichtflüchtig und manchmal mit dem Prozessor integriert
 Meist wird nur wenig Datenspeicher benötigt, der Programmspeicher wird nur bei der
Produktion oder Wartung neu beschrieben ( andere Speicherkonzepte)
 Hohe Stückzahlen verursachen Ressourcenprobleme (Speicherplatz)
 Oft komplizierte Berechnungen mit reellen Zahlen (DSP, digitale Signalprozessoren),
spezialisierte ALUs für bestimmte numerische Algorithmen.
Speicher
Speicher dienen zur temporären oder permanenten Aufbewahrung von (binär codierten)
Daten. Sie können nach verschiedenen Kriterien klassifiziert werden
 Permanenz: flüchtig – nichtflüchtig (bei Ausfall der elektrischen Spannung)
o Halbleiterspeicher sind meist flüchtig, magnetische / optische Speichermedien
nicht.
o So genannte Flash Speicher sind nichtflüchtige Halbleitermedien, die
elektrisch beschrieben und gelöscht werden können. Bei Flash-Speichern ist
nicht jedes einzelne Bit adressierbar, die Zugriffe erfolgen auf Sektorebene
ähnlich wie bei Festplatten. Vorteil: keine mechanisch bewegten Teile.
 Geschwindigkeit: Zugriffszeit (Taktzyklen oder ns) pro Wort (mittlere, maximale)
o Gängige Zugriffszeiten liegen bei 1-2 Taktzyklen für Register in der ALU, 250 ns für einen schnellen Cache, 100-300 für einen Hauptspeicherzugriff, 1050ms für einen Plattenzugriff, 90 ms für eine CD-ROM, Sekundenbereich für
Floppy Disk, Minutenbereich für Magnetbänder
 Preis: Cent pro Byte
o Ein 512 MB Speicherbaustein oder CompactFlash kostet 100 Euro (2*10-5
c/B=20c/MB), eine 120GB Festplatte etwa genauso viel (8.3*10-8 c/B
=83c/GB), ein 700MB CD-Rohling 25 cent (3.5*10-8c/B=35c/GB)
 Größe: gemessen in cm2 oder cm3 pro Bit
o Papier: 6000 Zeichen/630cm²=10B/cm²; Floppy: 1.44MB/ 80cm2=18 KB/cm2;
Festplatte: 10-20 GB/cm2, physikalisch machbar 1000GB/cm2 m
o für mechanisch bewegte Speicher muss der Platz für Motor und
Bewegungsraum mit berücksichtigt werden.
Wie man sieht, sind Geschwindigkeit und Preis umgekehrt proportional. Üblicherweise wird
der verfügbare Speicherplatz daher hierarchisch strukturiert. Das Vorhalten von Teilen einer
niedrigeren Hierarchieebene in einer höheren nennt man Caching.
Cache („Geheimlager“): kleiner, schneller Vordergrundspeicher, der Teile der Daten des
großen, langsamen Hintergrundspeichers abbildet („spiegelt“).
Konzept: Hauptspeicher = Folge von Tupeln (Adresse, Inhalt)
Cache-Speicher = Folge von Quadrupeln Cache-Zeilen:
(Index, Statusbit, Adresse, Inhalt)
Index: Adresse im Cache
Statusbits: modifiziert?, gültig?, exklusiv?, …
Adresse: Speicherzelle die gespiegelt wird
Falls die CPU ein Datum dat einer bestimmten Adresse adr benötigt, wird zunächst geprüft,
ob es im Cache ist (d.h. (idx, sbt, adr, dat) im Cache). Zwei Fälle:
Cache Hit: D.h. (idx, sbt, adr, dat) im Cache gespiegelt: dat wird
Cache Miss: kein idx mit (idx, adr, …) im Cache. Die Speicherzelle adr mit Inhalt dat
muss aus dem Hauptspeicher nachgeladen werden; ggf. muss dafür ein bereits belegter
Platz im Cache geräumt werden (Verdrängungsstrategie)
SPEICHER
3786:
3787:
3788:
3789:
3790:
3791:
3792:
3793:
…
17
"c"
3.1415
3786
"x"
123456
3790
NIL
…
CACHE
c1: 0
c2: 1
c3: 0
1
1
1
0
0
0
3787
3788
3792
"c"
3.1415
3790
c1: 0
c2: 1
c3: 0
1
1
1
0
0
0
3790
3788
3792
"x"
3.1415
3790
Falls jede Hintergrundadresse prinzipiell in jede Cache-Zelle geladen werden kann, ist der
Cache assoziativ (fully associative).
Vorteil: Flexibilität.
Nachteil: gesamter Cache muss durchsucht werden, ob Hit oder Miss.
Falls eine Cache-Zelle nur bestimmte Hintergrundadressen abbilden kann, sagen wir der
Cache ist direkt abgebildet (direct mapped)
Beispiel: C0 für H00, H10, H20, …, C1 für H01, H11, H21, …Suche H63 nur in C3!
Vorteil: schnelle Bestimmung ob Hit oder Miss; Nachteil: Unflexibilität
Hitrate ist entscheidend für Leistungssteigerung durch Cache; Verdrängungsstrategie
beeinflusst Hitrate entscheidend
 LRU: Least recently used
 FIFO: First-In, First-Out
 LFU: Least frequently used
Cache Coherency Problem: Mehrere Prozesse greifen auf Speicher zu, jeder Prozess hat einen
eigenen Cache: Wie wird die Konsistenz sichergestellt?
Kapitel 4: Programmiersprachen und –umgebungen
Zur Wiederholung: Informatik ist die Wissenschaft von der automatischen Verarbeitung von
Informationen; ein Algorithmus ist ein präzises, schrittweises und endliches Verfahren zur
Verarbeitung (Umformung) von Informationen, und ein Programm ist die Repräsentation
eines Algorithmus zur Ausführung auf einer Maschine
Während v. Neumann noch der Ansicht war, dass „Rechenzeit zu wertvoll für niedere
Aufgaben wie Übersetzung“ ist, sind heute dagegen die Personalkosten der Hauptfaktor bei
den Softwarekosten. Darüber hinaus ist die Codierung in Maschinensprache oder Assembler
sehr fehleranfällig (wie jeder, der die Haufgaben bearbeitet hat, wohl gemerkt haben dürfte).
Daher sucht man nach problemorientierten statt maschinenorientierten Beschreibungsformen.
Die Verständlichkeit eines Programms ist oftmals wichtiger als die optimale Effizienz.
Vorteile höherer Programmiersprachen zeigen sich
 bei der Erstellung von Programmen:
o schnellere Programmerstellung
o sichere Programmierung.
 bei der Wartung von Programmen:
o bessere Lesbarkeit
o besseres Verständnis der Algorithmen.
 wenn Programme wiederverwendet werden sollen:
o Verfügbarkeit auf vielen unterschiedlichen Rechnern; vom Zielrechner
unabhängige Entwicklungsrechner.
4.1 Programmierparadigmen
Unter einem Programmierparadigma versteht man ein Sprachkonzept, welches als Muster
prägend für die Programme einer bestimmten Gruppe oder Sprache ist. Gängige
Programmierparadigmen sind
o Funktional / applikativ
o Imperativ
o Objektorientiert
o Logikbasiert
o Deklarativ
o Visuell / datenflussorientiert
o funktionale oder applikative Sprachen betrachten ein Programm als mathematische
Funktion f, die eine Eingabe I in eine Ausgabe O überführt: O = f(I). Variablen sind
Platzhalter im Sinne der Mathematik (Parameter).
Ausführung = Berechnung des Wertes mittels Termersetzung.
Beispiele: Lisp, SML, Haskell, ...
SML:
fun fak (n) = if n < 1 then 1 else n * fak (n-1)
LISP:
(defun fak (n)
(cond (le n 1) 1 (* n (fak (– n 1)))))
o imperative Sprachen unterstützen das ablauforientierte Programmieren. Werte werden
sog. Variablen (= Speicherplätzen) zugewiesen. Diese können ihren Zustand ändern, d.h.
im Laufe der Zeit verschiedene Werte annehmen (vgl. von-Neumann-Konzept).
Ausführung = Folge von Variablenzuweisungen.
Beispiele: Algol, C, Delphi, ...
// Fakultätsfunktion in C:
#include <stdio.h>
int fakultaet(int n) {
int i, fak = 1;
for ( i=2; i<=n; i++ )
fak *= i;
return fak;
}
int main(void) {
int m, n;
for ( n=1; n<=17; n++ )
printf("n = %2d n! = %10d\n", n, fakultaet(n));
}
o objektorientierte Sprachen sind imperativ, legen aber ein anderes, in Objekten
strukturiertes Speichermodell zugrunde. Objekte können eigene lokale Daten und
Methoden speichern, voneinander erben und miteinander kommunizieren.
Ausführung = Interaktion von Agenten.
Beispiele: SmallTalk, C++, Java
//Java Polymorphie-Beispiel
import java.util.Vector;
abstract class Figur
{ abstract double getFlaeche();
}
class Rechteck extends Figur
{ private double a, b;
public Rechteck ( double a, double b )
{ this.a = a;
this.b = b; }
public double getFlaeche() {return a * b;}
}
class Kreis extends Figur
{ private double r;
public Kreis( double r )
{ this.r = r; }
public double getFlaeche()
{return Math.PI * r * r;
}
}
public static void main( String[] args )
{ Rechteck re1 = new Rechteck( 3, 4 );
Figur kr2 = new Kreis( 8 );
…
o logikbasierte Sprachen betrachten ein Programm als (mathematisch-) logischen Term t,
dessen Auflösung (im Erfolgsfall) zu gegebenen Eingabewerten I passende Ausgabewerte
O liefert: t(I,O)  true.
Ausführung = Lösen eines logischen Problems.
Beispiel: Prolog
fak(0,1).
fak(N,X):- N > 0, M is N - 1, fak(M,Y), X is N * Y.
mutter (eva, maria).
mutter (maria, uta).
grossmutter(X,Y) :- mutter(X,Z), mutter(Z,Y).
?- grossmutter (eva, uta)
yes.
?- grossmutter (uta, eva)
no.
o deklarative Sprachen betrachten ein Programm als eine Menge von Datendefinitionen
(Deklarationen) und darauf bezogenen Abfragen.
Ausführung = Suche in einem Datenbestand.
Beispiel: SQL
SELECT A_NR,
A_PREIS As Netto,
0.16 As MwSt,
A_PREIS * 1.16 As Brutto,
FROM ARTIKEL
WHERE A_PREIS <= 100
ORDER BY A_PREIS DESC
o datenflussorientierte Sprachen stellen ein Programm dar, indem sie den Fluss der Signale
oder Datenströme durch Operatoren (Addierer, Integratoren) beschreiben
Ausführung = Transformation von Datenströmen
Beispiel: Simulink, Microsoft Visual Programming Language (VPL)
4.2 Historie und Klassifikation von Programmiersprachen
Programmiersprachen lassen sich klassifizieren nach
 Anwendungsgebiet: Ingenieurwissenschaften (Fortran), kommerzieller Bereich (Cobol),
künstliche Intelligenz (Prolog, Lisp), Systemsoftware (Assembler, C), Steuerungssoftware
(C, Ada, Simulink), Robotik (C, NQC, VPL), Internet / Arbeitsplatzsoftware (Java, C++),
Datenbanken (SQL), …
 Verbreitungsgrad: (derzeit )industrierelevante, überlieferte und akademische Sprachen




Historischer Entwicklung: „Stammbaum“ der Programmiersprachen, siehe oben
Programmiersprachengeneration: Abstraktionsgrad;
o 1. Generation: Maschinensprachen
o 2. Generation: Assemblersprachen (x86-MASM, ASEM-51)
o 3. Generation: Algorithmische Sprachen (Algol, Delphi, …)
o 4. Generation: Anwendungsnahe Sprachen (VBA, SQL, …)
o 5. Generation: Problemorientierte Sprachen, KI-Sprachen (Prolog, Haskell, …)
Programmierparadigmen (siehe Kapitel 4.1)
Verfügbaren Sprachelementen
o Kontrollfluss, Datentypen
o Rekursion oder iterative Konstrukte
o Sequentielle oder parallele Ausführung
o Interpretiert oder compiliert
o Realzeitfähig oder nicht realzeitfähig
o Streng typisiert, schwach typisiert oder untypisiert
o Textuell oder graphisch
o …
Die „babylonische Sprachvielfalt“ der verschiedenen Programmiersprachenverursacht
Kosten: Portabilitätsprobleme, Schulungsmaßnahmen, Programmierfehler. Daher gab es
immer wieder Versuche, Sprachen zu vereinheitlichen (ADA, ANSI-C, …). Trotzdem kam es
in der Forschung immer wieder zu neuen Ideen, neuen Konzepten und in der Folge zu neuen
Programmiersprachen. Daher wird es auf absehbare Zeit viele verschiedene Sprachen und
Dialekte geben. Für Informatiker ist es deshalb wichtig, die verschiedenen Konzepte zu
kennen und sich schnell in neue Sprachen einarbeiten zu können.
4.3 Java
Java wurde seit 1995 bei der Firma Sun Microsystems entwickelt. Ursprünglich war die
Sprache zur Implementierung von sicheren, plattform-unabhängigen Anwendungen gedacht,
die über das Internet verbreitet und bezogen werden können. Ein wichtiges Konzept ist das
sog. Applet, eine Mini-Anwendung, die innerhalb einer Web-Seite läuft. Inzwischen sind
praktisch alle Internet-Browser „Java-fähig“ und können Applets ausführen. Künftige
Anwendungen von Java liegen im Bereich der kommunizierenden Geräte („Internet der
Dinge“). Vorzüge von Java sind:
o Java ist eine mächtige Programmiersprache, die die Sprachkonzepte herkömmlicher
Programmiersprachen wie C oder Pascal mit OO-Konzepten und Konzepten zur
Parallelverarbeitung und Netz-Verteilung verbindet.
o Java wurde auf der Basis bekannter Sprachen zur System-, zur modularen und OOProgrammierung entwickelt (C, C++, Modula, Smalltalk)
o Die Struktur der Sprache ist einfach, aber weniger systematisch als die von Pascal,
Modula oder Oberon.
o Java hat eine umfangreiche Klassenbibliothek.
o Java gilt als robuste Sprache, die viele Fehler vermeiden hilft, z.B. aufgrund ihres
Typkonzepts und der automatischen Speicher-bereinigung.
o Java-Compiler sind schnell und produzieren effizienten Code.
o Im Kern ist Java eine Programmiersprache mit imperativen und rekursiven Konzepten
ähnlich wie Pascal oder C.
 So weist Java die meisten aus diesen Sprachen bekannten Konzepte wie
Variablen, Zuweisungen, Datentypen, Ausdrücke, Anweisungen etc. auf - mit
einer keineswegs verbesserten Syntax

•
Viele syntaktische und strukturelle Mängel und Ungereimtheiten sind nur mit
der C / C++ Historie zu erklären
Verdienste von Java liegen
 in der Zielsetzung, eine möglichst schlanke Sprache zu schaffen,
 in dem Ansatz, das Klassenkonzept ins Zentrum der Sprache zu stellen (Java
ist eine wirklich “objektorientierte” Sprache)
 in der Offenheit und uneingeschränkten Portabilität, die Java z. Zt. zum
bestgeeigneten Werkzeug für das Programmieren von Netz-Anwendungen
macht
Java-Historie
ab 1991 Bei der Firma Sun wird von J. Gosling und Kollegen auf der Basis von C++ eine
Sprache namens Oak (Object Application Kernel) für den Einsatz im Bereich der
Haushalts- und Konsumelektronik entwickelt. Ziele: Plattform-Unabhängigkeit,
Erweiterbarkeit der Systeme und Austauschbarkeit von Komponenten.
1993
Oak wird im Green-Projekt zum Experimentieren mit graphischen BenutzerSchnittstellen eingesetzt und später (wegen rechtlicher Probleme) in Java
umbenannt. Zu diesem Namen wurden die Entwickler beim Kaffeetrinken
inspiriert.
1994
Das WWW beginnt sich durchzusetzen. Java wird wegen der Applet-Technologie
„die Sprache des Internets“
seit 1995 Sun bietet sein Java-Programmiersystem JDK (Java Development Kit) mit einem
Java-Compiler und Interpreter kostenlos an.
ab 1996 Unter dem Namen JavaBeans wird eine Komponenten-Architektur für JavaProgramme entwickelt und vertrieben.
ab 2001 Eclipse-Projekt: integrierte Entwicklungsumgebung für Java und (darauf
aufbauend) andere Sprachen und Systeme.
4.4 Programmierumgebungen am Beispiel Eclipse
Siehe Vorlesung am 10.12.2007 (J. Hänsel), Skript wird noch nachgetragen.
Kapitel 5: Applikative Programmierung
5.1 einfache, geschachtelte Rekursion, Terminierung
– entfällt -–
5.2 rekursive Datenstrukturen
– entfällt -–
5.3 Methoden und Parameterübergabemechanismen
– entfällt -–
Kapitel 6: Konzepte imperativer Sprachen
Imperative Sprachen waren historisch die ersten „höheren Programmiersprachen“ (3.
Generation). Da Paradigma imperativer Sprachen (Programmieren als strukturierte Folge von
Speicheränderungen) entspricht dem Konzept der von-Neumann-Architektur.
6.1 Variablen, Datentypen, Ausdrücke
Betrachten wir als Beispiel zwei Java-Implementierungen der Fakultätsfunktion:
static int fakRek(int x) {
return (x<=0) ? 1 : x * fakRek(x-1);
}
static int fakIt(int x) {
int result = 1;
while (x>0) { result *= x--; };
return result;
}
Typische Sprachelemente imperativer Sprachen, die hier auftreten, sind
Methodendeklarationen und Methodenaufrufe, Parameter und lokale Variablen, Terme mit
arithmetischen und logischen Operatoren, Zuweisungen und Dekremente, bedingte Terme
bzw. Fallunterscheidungen, und Wiederholungsanweisungen. Die einzelnen Sprachelemente
werden nachfolgend erläutert. Für weitere Informationen verweisen wir auf den „MiniJavakurs“ unter http://java.sun.com/docs/books/tutorial/java/nutsandbolts/index.html.
Variablen haben in typisierten Sprachen wie Java immer einen festgelegten Datentyp. Jede
Variable entspricht einer Speicherzelle. Dies gilt natürlich nur, falls die Variable einen Typ
hat, der durch ein Speicherwort repräsentiert werden kann; eine Variable eines komplexen
Typs entspricht einer Gruppe von Speicherzellen. Es gibt mehrere Arten von Variablen:
Methoden-Parameter, lokale Variablen, Instanzvariablen und Klassenvariablen.
 Methoden haben Parameter (Ein/Ausgabevariablen)
 Methoden können lokale Variablen haben
 Jedes Objekt hat Instanzvariablen (nicht statisch)
 Klassen können Klassenvariablen besitzen (statisch)
Instanzvariablen und Klassenvariablen heißen auch die Datenfelder der Klasse. Betrachten
wir ein (willkürliches) Beispiel:
public class Main {
static double wechselkurs = 1.32;
int betrag = 7;
static int zehnCentStücke(double p) {
int cent = (int)((p-(int)(p)) * 100);
return((cent==20) ? 1 : 0) ;}
public static void main(String[] args) {
double p = 2.70;
System.out.print("p= ");
System.out.println(p);
}
}
Hier ist p in main eine lokale Variable, p in zehnCentStücke ein Parameter, betrag
eine Instanzvariable und .wechselkurs eine Klassenvariable.
Java kennt folgende primitive Datentypen:
• Zahlen: int, byte, short, long,
float, double (43.8F, 2.4e5)
• Zeichen: char (z.B. `x´)
Sonderzeichen `\n´, `\”´, `\\´, `\uFFF0´
• Zeichenreihen (Strings): “Infor” + “matik”
• Wahrheitswerte: boolean (true, false)
• „Leerer Typ“ void
Der leere Typ wird aus formalen Gründen gebraucht, wenn z.B. eine Methode kein Ergebnis
liefert. Hier sind einige Beispiele von Datendeklarationen:
boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
int decVal = 26;
int octVal = 032;
int hexVal = 0x1a;
double d1 = 123.4;
double d2 = 1.234e2;
float f1 = 123.4f;
String s = „ABCDE“;
Benötigt man Variablen für mehrere gleichförmige Daten (d.h. viele Daten mit gleichem
Typ), so kann man eine Reihung (Array) deklarieren (*).
Die Reihung könnte etwa wie im folgenden Beispielprogramm verwendet werden.
class ArrayDemo {
public static void main(String[] args) {
int[] anArray; // eine Reihung ganzer Zahlen
anArray = new int[10]; // Speicherallokation
anArray[8] = 500;
System.out.println("Element at index 8: " + anArray[8]);
}
}
Der Rumpf einer Java-Methode besteht aus einem Block, d.h. einer Folge von Anweisungen
(statements). Bei der Ausführung werden diese der Reihe nach abgearbeitet. Die wichtigste
Art von Anweisungen sind Ausdrücke (expressions). Bei ihrer Auswertung liefern sie einen
Wert ab und können per Nebeneffekt den Inhalt von Variablen verändern.
Hier ist ein Ausschnitt aus der Syntax von Java in BNF, in der Ausdrücke definiert werden
(vgl. *, Kap. 18):
•
•
•
•
•
•
•
Expression::=Expr1 [AssignmentOperator Expr1]
Expr1::=Expr2 [? Expression : Expr1]
Expr2 ::=Expr3 {Infixop Expr3}
Infixop::= + | * | < | == | != | || | && | …
Expr3::= PrefixOp Expr3 |
( Expr | Type ) Expr3 |
Primary {Selector} {PostfixOp}
Prefixop::= ++ | -- | ! | ~ |+ | Primary::= ( Expression ) | Literal | new Creator |
Identifier { . Identifier }[ IdentifierSuffix] | …
Beispielweise könnten Ausdrücke etwa wie folgt aussehen:
17 + 4
x = x + 1
b = b != !b
a += (a = 3)
v[i] != other.v[i]
cube[17][x+3][++y]
"Länge = " + ia.length + "," + ia[0].length + " Meter"
Achtung! Java verlangt, dass jede lokale Variable vor ihrer Verwendung initialisiert wurde,
und zwar so, dass es statisch erkennbar ist. Diese Bedingung wird vom Compiler mit einer so
genannten statischen Analyse überprüft und in Eclipse ggf. bereits beim Tippen des
Programmtextes als Fehler markiert. Allerdings kann die statische Analyse nur bedingt
erfolgreich sein. Im folgenden Beispiel sind zwar alle Variablen immer korrekt initialisiert,
dies wird aber nur im ersten Fall auch erkannt! Eine gute Faustregel ist es, alle Variablen
gleich bei der Deklaration zu initialisieren.
public class Analyse {
public int korrekt()
{int x;
if (17+4==21){x=1;};
return x;
}
public int inkorrekt()
{int x;
int i=5;
if (i==5){x=1;};
return x;
}
}
In Java gibt es folgende Operatoren (vergleiche die oben angegebene Syntax):


Arithmetische Operatoren: +, -, *, /, %, ++ und ––
/ und % sind (ganzzahlige) Division und Rest
++ und –– als Post- und Präfixoperatoren; a++ erhöht a um 1 und ergibt a, während
++a den Wert a+1 liefert
+ dient auch zur Konkatenation von Zeichenreihen!
Relationale Operatoren: ==, !=, <, <=, > und >=

•
•
•
•
•
Achtung! == und != vergleichen bei Objekten nur Referenzen!
Logische Operatoren: !, &&, ||, &, | und ^
a || b wertet b nur aus falls a nicht gilt
Zuweisungsoperatoren = , +=, -=, *=, /=, %, &=, |=, ^=, <<=, >>= und >>>=
Fallunterscheidung (dreistellig) x? y: z
Bitoperatoren ~, |, &, ^, >>, >>> und <<
können auch auf int angewendet werden (Bit-Repräsentation)
Operatoren new und instanceof
a instanceof b gibt an, ob Objekt a eine Instanz der Klasse b oder einer
ihrer Unterklassen ist
Operatoren für Member- und Array-Zugriff
MeineKlasse.meinDatenfeld
meinArray[7]
Hier sind noch zwei Anmerkungen für Spezialisten:
public class Demo
{
static public int sampleMethod() {
int i = 0;
boolean b = ++i==i++ | ++i==i++;
return i;
}
static public int sampleMethod2() {
int i = 0;
boolean b = ++i==i++ || ++i==i++;
return i;
}
}
Das Ergebnis von sampleMethod und sampleMethod2 unterscheidet sich, da im
zweiten Fall der hintere Teil der Disjunktion nicht ausgewertet wird. Merke: Dies ist kein
Beispiel für gute Programmierung! Man sagt, dass ein Ausdruck einen Nebeneffekt (oder
auch Seiteneffekt) hat, wenn er neben dem eigentlichen Ergebnis weitere Variablen verändert.
Ausdrücke mit Nebeneffekten sind schwer verständlich und sollten deshalb möglichst wenig
verwendet werden!
public class Rechtsshift {
static int i = -64;
static int j = i>>2;
static int k = i>>>2;
}
In diesem Beispiel hat i den Wert -64, j den Wert -16 und k den Wert 1073741808. Merke:
Bit-Shifts sollten nur mit Vorsicht verwendet werden!
Wie bereits oben erwähnt, ist Java eine streng typisierte Sprache. Das bedeutet, Alle
Operatoren in Java sind strikt typisiert, d.h., es wird geprüft, ob der Typ dem verlangten
entspricht. Z.B. kann << nicht auf float oder String angewendet werden. Zur Typumwandlung
gibt es entsprechende Casting-Operatoren; z.B.
(int)3.14 // ergibt 3
(double)3.14f // Ausweitung
Dabei ist zu beachten, dass bestimmte Typen inkompatibel sind
(z.B. boolean und int)
Ausdrücke werden gemäß den „üblichen“ Operator-Präzedenz-Regeln ausgewertet.
Regeln für die Auswertungsreihenfolge sind:
 linker vor rechter Operator, dann Operation
 gemäß Klammerung
 gemäß Operator-Hierarchie
Achtung: Die Reihenfolge der Auswertung von Ausdrücken kann das Ergebnis beeinflussen!
Beispiel:
int i=2, j = (i=3)*i; // ergibt 9
int i=2, j = i*(i=3); // ergibt 6
Überraschenderweise ist die Multiplikation also nicht kommutativ! Generell ist zu sagen, dass
die Verwendung von Nebeneffekten schlechter Programmierstil ist!
6.2 Anweisungen und Kontrollstrukturen
In Java gibt es folgende Arten von Anweisungen:
• Leere Anweisung ;
• Block, d.h. eine mit {…} geklammerte Folge von Anweisungen; Blöcke dürfen auch
geschachtelt werden
• Lokale Variablendeklaration
• Ausdrucksanweisung
 Zuweisung
 Inkrement und Dekrement
 Methodenaufruf
 Instanzerzeugung
Zur Steuerung des Ablaufs werden Fallunterscheidungen verwendet.
• if-Anweisung
 if (ausdruck) anweisung; [else anweisung;]
 üblicherweise sind die Anweisungen Blöcke.
 hängendes else gehört zum innersten if
• switch-Anweisung
 switch (ausdruck) {
{case constant : anweisung}
[default: anweisung]
Bei der Auswertung wird zunächst der Ausdruck ausgewertet und dann in den
entsprechenden Zweig verzweigt. Switch-Anweisungen sind tief geschachtelten ifAnweisungen vorzuziehen, da sie normalerweise übersichtlicher sind.
Kontrollflusselemente
• while- und do-Schleife
 while (ausdruck) anweisung;
 do anweisung; while (ausdruck);
• for-Schleife
 for (init; test; update) anweisung;
 init darf mehrere Anweisungen (mit Komma getrennt!) enthalten, auch gar
keine
 for (int i=0; i<10; i++){ … i…;};
• break, continue und return
 continue verlässt den Schleifendurchlauf

break verlässt die Schleife komplett
6.3 Sichtbarkeit und Lebensdauer von Variablen
Unter der Sichtbarkeit (Skopus) einer Variablen versteht man den Bereich im Quellcode, in
dem dieser Variablenname verwendet werden darf. Der Gültigkeitsbereich ist derjenige Teil
der Sichtbarkeit, in der man auf den Inhalt der Variablen zugreifen kann. Die Lebensdauer ist
die Zeitspanne, während der für die Variable Speicherplatz reserviert werden muss
Für jede der verschiedenen Variablenarten gibt es detaillierte Festlegungen hierzu.
Beispiel:
public class xyz {
int x;
public xyz() {
x = 1;
}
public int sampleMethod(int y) {
int z = 2;
return x + y + z;
}
}
In diesem Beispiel bezieht sich x innerhalb von xyz und in sampleMethod auf die
Instanzvariable, y in sampleMethod auf den Parameter, und z auf die lokale Variable.
Variablen können sich auch gegenseitig verschatten: Unter Verschattung versteht man die
Einschränkung des Gültigkeitsbereichs einer Variablen durch eine andere Variable gleichen
Namens. Es gelten folgende Regeln:
• Lokale Variablen werden in einem Block (einer Folge von Anweisungen) deklariert
und sind nur für diesen verwendbar.
• Lokale Variable sind ab ihrer Deklaration bis zum Ende des Blocks oder der Methode
sichtbar.
• Instanzvariable können von gleichnamigen lokalen Variablen verschattet werden, d.h.,
sie sind sichtbar aber nicht gültig.
Verschattung ist hilfreich, um das Lokalitätsprinzip zu unterstützen (z.B. in
Laufanweisungen). In Java ist es nicht erlaubt, dass lokale Variablen sich gegenseitig oder
Methodenparameter verschatten
Beispiel für Verschattung:
public class xyz {
int x;
public xyz() {
x = 1;
}
public int sampleMethod(int y) {
int x = 2;
return x + y;
}
}
Herunterladen