Skript zur Vorlesung Algorithmische Mathematik I

Werbung
Skript zur Vorlesung Algorithmische Mathematik I
Kai-Friederike Oelbermann
16. Januar 2016
Inhaltsverzeichnis
0 Inhalt der Vorlesung
4
1 Erste Programmierschritte
1.1 Der Computer als einfacher Taschenrechner . . . . . . . . . . . . . . . .
1.1.1 Eine Maschine zur Addition ganzer Zahlen . . . . . . . . . . . . .
1.1.2 Erläuterungen zum Programmcode . . . . . . . . . . . . . . . . .
1.1.3 Der Variablentyp int . . . . . . . . . . . . . . . . . . . . . . . .
1.1.4 Der Variablentyp float . . . . . . . . . . . . . . . . . . . . . . .
1.1.5 Der Variablentyp char . . . . . . . . . . . . . . . . . . . . . . . .
1.1.6 Die Kontrollstruktur if-else . . . . . . . . . . . . . . . . . . . .
1.1.7 Schleifen und Funktionen . . . . . . . . . . . . . . . . . . . . . .
1.1.8 Geschachtelte Kontrollstrukturen – ein einfacher Taschenrechner
1.1.9 Rundungsfehlerproblematik . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
5
5
6
6
7
7
8
11
12
2 Zahlentheorie
2.1 Der euklidische Algorithmus, größter gemeinsamer Teiler, Primzahlen
2.1.1 Definition (a teilt b) . . . . . . . . . . . . . . . . . . . . . . .
2.1.2 Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.3 Definition (größter gemeinsamer Teiler) . . . . . . . . . . . .
2.1.4 Lemma (Division mit Rest) . . . . . . . . . . . . . . . . . . .
2.1.5 Beispiel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.6 Satz (Euklidischer Algorithmus) . . . . . . . . . . . . . . . .
2.1.7 Das Programm ggT.c . . . . . . . . . . . . . . . . . . . . . .
2.1.8 Folgerung aus Satz 2.1.6 . . . . . . . . . . . . . . . . . . . . .
2.1.9 Definition (Unzerlegbarkeit, Primzahl) . . . . . . . . . . . . .
2.1.10 Satz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.11 Lemma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.12 Satz (von Euklid) . . . . . . . . . . . . . . . . . . . . . . . . .
2.1.13 Hauptsatz (über die Primfaktorzerlegung) . . . . . . . . . . .
2.2 Kongruenzen, Restklassenringe und -körper, Chinesischer Restsatz .
2.2.1 Definition (Kongruenz modulo m) . . . . . . . . . . . . . . .
2.2.2 Lemma (Äquivalenzrelation) . . . . . . . . . . . . . . . . . . .
2.2.3 Definition und Hilfssatz . . . . . . . . . . . . . . . . . . . . .
2.2.4 Lemma (Rechnen mit Kongruenzen) . . . . . . . . . . . . . .
2.2.5 Definiten und Satz (Addition und Multiplikation in Zm ) . . .
2.2.6 Beispiel für Z4 . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2.7 Anwendungen der Kongruenz-Rechnung . . . . . . . . . . . .
2.2.8 Satz (Lösbarkeit der Kongruenzgleichung ax ⌘ b mod m) . .
2.2.9 Definition (Einheit) . . . . . . . . . . . . . . . . . . . . . . . .
2.2.10 Folgerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2.11 Implementierung der Einheiten- und Inversenbestimmung . .
2.2.12 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
13
13
13
13
13
14
15
16
16
16
18
18
19
20
20
21
21
21
21
22
22
23
23
24
25
26
26
26
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2.3
2.2.13 Direktes Produkt von Restklassenringen
2.2.14 Satz (Chinesischer Restsatz) . . . . . . .
2.2.15 Bemerkung (Effektive Bestimmung eines
2.2.16 Implementierung der effektiven Variante
Arithmetik für beliebig lange natürliche Zahlen,
2.3.1 Dynamische Felder . . . . . . . . . . . .
2.3.2 Doppelt verkettete Listen . . . . . . . .
. . . . . .
. . . . . .
Urbildes)
. . . . . .
verkettete
. . . . . .
. . . . . .
. . . .
. . . .
. . . .
. . . .
Listen
. . . .
. . . .
3 Erzeugung von Zufallszahlen
3.1 Grundsätzliches . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Mathematik des Zufalls . . . . . . . . . . . . . . . . . . . . . .
3.2.1 Elementarereignisse und Ereignisalgebra . . . . . . . . .
3.2.2 Wahrscheinlichkeitsmaß . . . . . . . . . . . . . . . . . .
3.2.3 Beispiel (Idealer Würfel, diskrete Gleichverteilung) . . .
3.2.4 Beispiel (Gleichverteilung auf dem Intervall [0, 1], stetige
3.3 Pseudozufallszahlen und die Lineare Kongruenzmethode . . . .
3.3.1 Lineare Kongruenzmethode . . . . . . . . . . . . . . . .
3.3.2 Satz von Knuth . . . . . . . . . . . . . . . . . . . . . . .
3.3.3 Bemerkung . . . . . . . . . . . . . . . . . . . . . . . . .
3.3.4 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.4 Die Güte von Zufallszahlen . . . . . . . . . . . . . . . . . . . .
3.5 Statistische Tests . . . . . . . . . . . . . . . . . . . . . . . . . .
3.5.1 Der Chi-Quadrat-Test . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
26
28
29
30
31
31
32
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
Gleichverteilung)
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
37
37
37
37
38
38
39
39
40
40
42
42
43
43
43
Kapitel 0
Inhalt der Vorlesung
Sie werden kennen lernen, welche der auf dem Schulwissen oder den Inhalten der Erstsemestervorlesungen beruhenden Resultate aus Zahlentheorie, Graphentheorie, Analysis oder Stochastik algorithmischer
Natur sind. Ausgewählte Themengebiete werden theoretisch vorgestellt, entsprechende Algorithmen
werden entwickelt, analysiert und in lauffähige C-Programme übertragen. Die erforderlichen Programmierkenntnisse werden im Verlauf der Vorlesung entwickelt.
Dieses Skript orientiert sich an dem Skript von Professor Dr. Hans-Christoph Grunau zu seiner gleichnamigen Vorlesung und dem Buch Programmieren in C: Eine mathematikorientierte Einführung: Eine
Mathematikorientierte Einführung (Kirsch&Schmitt, 2007). Letzteres Buch finden Sie in der Universitätsbibliothek der OVGU unter diesem Link als E-Book.
Kapitel 3 bezieht sich zum Teil auf das Skript Numerische Methoden der Finanzmathematik, WiSe
2009/10 von Prof. Dr. J. Baumeister.
Auf der Webseite kai-friederike.de/wise2015algmathe.html finden Sie die in diesem Skript besprochenen Programmcodes, sowie zahlreiche Übungsaufgaben.
4
Kapitel 1
Erste Programmierschritte
1.1.
1.1.1.
Der Computer als einfacher Taschenrechner
Eine Maschine zur Addition ganzer Zahlen
C-Programme sind einfache Textdateien, die mit einem geeigneten Texteditor angelegt bzw. verändert
werden können. Geeignete Editoren mit Syntaxeinfärbung sind zum Beispiel Emacs, Kwrite oder Notepad++. Wir beginnen mit einem ersten Beispielprogramm:
1
2
3
4
#include <s t d i o . h>
i n t main ( void )
{
int zahl1 , zahl2 , e r g e b n i s ;
5
p r i n t f ( " D i e s e s Programm kann z w e i ganze Zahlen a d d i e r e n . \ n\
Geben S i e b i t t e I h r e n e r s t e n Summanden e i n : " ) ;
6
7
8
s c a n f ( "%i " ,& z a h l 1 ) ;
9
10
p r i n t f ( "Geben S i e nun b i t t e I h r e n z w e i t e n Summanden e i n : " ) ;
11
12
s c a n f ( "%i " ,& z a h l 2 ) ;
13
14
e r g e b n i s=z a h l 1+z a h l 2 ;
15
16
p r i n t f ( " Die Summe von %i und %i l a u t e t : %i . \ n\n" , z a h l 1 , z a h l 2 , e r g e b i s ) ;
17
18
19
20
}
return ( 0 ) ;
1.1.2.
Erläuterungen zum Programmcode
Wir wollen einige Punkte im obigen Programmcode etwas näher erläutern. Dabei stehen die Zahlen in
den runden Klammern für die jeweilige Zeilennummer im Code.
(1) Präprozessor-Direktiven: Es werden so genannte Headerdateien geladen, die viele Standardfunktionen (z.B. printf, scanf) bereitstellen.
(2) Programmkopf des Hauptprogramm main. Der Variablentyp int steht für den Rückgabewert der
Hauptfunktion. Dieser ist typischerweise ’0’ bei fehlerfreiem Programmdurchlauf. Der Variablename void bedeutet, dass dem Programm eine leere Input-Liste übergeben wird.
(3,20) Beginn und Ende des Anweisungsblocks.
(4) Deklaration der (ganzzahligen!) Integer-Variablen.
(19) return(0) gibt den Rückgabewert von main zurück. Damit wird das Programm beendet.
5
(9,13) Hinter dem %-Zeichen folgt ein Formatzeichen für die auszugebende Variable (z.B. %i für
Integer-Variablen).
’\n’ bewirkt einen Zeilenumbruch.
’\’ gestattet einen Zeilenumbruch im Programmcode.
1.1.3.
Der Variablentyp int
Bei der konkreten Realisierung eines Algorithmus kommen zu den theoretisch mathematischen Betrachtungen noch technische Aspekte hinzu. Denn der Computer muss schließlich bei der Ausführung
wissen, welche Art von Information verarbeitet werden soll. Zu dem Zweck legt die Programmiersprache
unterschiedliche Variablentypen (manchmal auch Datentypen genannt) fest. Zusammengefasst können
wir sagen, dass ein Variablentyp durch einen Wertebereich und die darauf anwendbaren Operationen
festgelegt wird.
Es gibt verschiedene Varianten ganzzahlige Integer-Variablen zu deklarieren:
Typ
short
unsigned short
int
unsigned int
long
unsigned long
Größe
2 Byte
2 Byte
4 Byte
4 Byte
8 Byte
8 Byte
Bereich
215 . . . 215
0 . . . 216 1
231 . . . 231
0 . . . 232 1
263 . . . 263
0 . . . 264 1
1
1
1
scanf/printf
%hi
%hu
%i
%u
%li
%lu
Den Zahlenbereich der jeweils abgedeckten Zahlen findet man in der Headerdatei <limits.h>. Zum
Beispiel trägt die dort deklarierte Variable INT_MAX den Wert +32.767 und gibt damit den maximalen
Wert für den Variablentyp int an. Das Schlüsselwort unsigned bedeutet, dass lediglich vorzeichenlose
Variablen 0 dargestellt werden können.
Hinweise und Warnungen
1. Der Compiler unternimmt nichts, um auf die Einhaltung des zulässigen Zahlenbereichs zu achten
(vergleiche Übungsaufgabe Blatt 1, (4)).
2. Beim Überlaufen des Zahlenbereichs können vollkommen sinnlose Ergebnisse entstehen. Das
heißt, man sollte sich vorab sinnvolle Variablentypen und zulässige Zahlenbereiche überlegen.
Wird der Zahlenbereich überschritten, sollte das Programm einen Warnhinweis geben.
3. Auf die Handhabung beliebig langer natürlicher Zahlen gehen wir in Abschnitt ?? ein.
1.1.4.
Der Variablentyp float
Im deutschen bedeutet das englische Wort float gleiten. Damit wird verständlich, warum man im
englischen Gleitpunktzahlen mit floating point numbers bezeichnet. Eine Gleitpunktzahl ist eine approximative Darstellung einer reellen Zahl. Daher sollte man sich die Realisierung reeller Zahlen im
Rechner weniger wie eine Zahl, sondern eher wie ein Intervall vorstellen.
In der Schule haben Sie sicherlich Gleitkommazahlen kennen gelernt. Die verschiedenen Bezeichnungen
im Deutschen rühren daher, dass man in der Schulmathematik als Dezimaltrennzeichen ein Komma
schreibt, während man international (und in der Wissenschaft generell) einen Punkt setzt, also 3.14
anstatt 3,14 schreibt.
Ähnlich wie beim Variablentyp int gibt es verschiedene float-Typen. Dabei ist double der Standardtyp.
6
Typ
float
double
long double
Größe
4 Byte
8 Byte
12 Byte
Genauigkeit an Stellen
ca. 8
ca. 16
ca. 20
scanf/printf
%f
%lf
%Lf
Bei der Handhabung großer Zahlen können zahlreiche Problem auftreten. Wir wollen kurz drei Problemklassen kommentieren:
Überlaufproblematik Wir schreiben das Programm Additionsmaschine1.c nun mit float-Variablen,
und nennen es Additionsmaschine2.c. Anschließend geben wir für den ersten Summanden die
Zahl 56 000 000.1 ein, und für den zweiten Summanden die Zahl 0.001. Die Ausgabe ist in dem
Fall 56 000 000.000000 + 0.00100 = 56 000 000.000000. Float-Variablen verfügen lediglich über 8
Stellen, sodass der Wert hinter dem Dezimalpunkt ignoriert wird. Die Verwendung von double
oder long double verschiebt das Problem nur.
Rundungsfehlerproblematik Die Rechnung (56 000 000.1 + 0.001) 56 000 000.1 führt bei der Verwendung von float-Variablen zum Ergebnis 0. Das Assoziativgesetz verliert also im gewohnt
klassischen Sinne seine Gültigkeit.
Ausgabeproblematik Das Programm Additionsmaschine2a.c arbeitet mit double-Variablen und
führt die obige Rechnung korrekt aus. Geben wir aber für den ersten Summanden die Zahl
56.0000001 und für den zweiten Summanden die Zahl 0.000000001 ein, so führt das zur Ausgabe
56.000000 + 0.000000 = 56.000000. Die ausgegebene Rechnung ist zwar im Prinzip richtig, aber
eben nicht die gewünschte Rechnung.
Dieses Problem lässt sich durch eine Spezifizierung der Vor- und Nachpunktstellen in der Ausgabeanweisung beheben (siehe Additionsmaschine2b.c):
p r i n t f ( " Die Summe von %2.10 l f und %1.10 l f l a u t e t : %10.10 l f \n" , \
zahl1 , zahl2 , e r g e b n i s ) ;
Als Fazit halten wir fest, dass der Umgang mit Gleitpunktzahlen immer kritisch reflektiert werden
muss.
1.1.5.
Der Variablentyp char
Auf dem zweiten Übungsblatt haben wir den Variablentyp char kennen gelernt, wobei die Abkürzung
char für Charakter steht, also für die Verarbeitung von Zeichen wie zum Beispiel Buchstaben und
Ziffern. In C lautet das zugehörige Formatzeichen %c. Intern werden Zeichen als ganze Zahlen kodiert.
Die standardisierte Zuordnung der Zeichen erfolgt durch die ASCII-Tabelle. ASCII steht für American
Standard Code for Information Interchange.
1.1.6.
Die Kontrollstruktur if-else
Wir haben im vorherigen Abschnitt gesehen, dass beim Umgang mit sehr großen bzw. sehr kleinen
Gleitpunktzahlen Probleme auftreten können. Wir wollen nun die Eingabe dahingehend überprüfen
und ggfls. einen Warnhinweis ausgeben.
Zur Realisierung von Fallunterscheidungen in Algorithmen benötigt man die if-else-Struktur. Deren
Syntax in C lautet:
i f (<bedingung >)
{
<An weisu ngsbl ock 1>
}
else
{
<An weisu ngsbl ock 2>
}
7
Diese Syntax realisiert folgendes. Wenn <bedingung> wahr ist, dann führe <Anweisungsblock 1> aus.
Ansonsten führe <Anweisungsblock 2> aus. Wenn im else-Block nichts zu machen ist, dann kann er
auch komplett weggelassen werden. Besteht ein Block nur aus einer einzigen Anweisung, so können die
geschweiften Klammern {. . .} weggelassen werden; wie bei der Berechnung des Betrags:
b=x;
if(b<0) b=-b;
Hat man mehrere Fälle, so kann man anstelle von verschachtelten if-else-Strukturen auch die folgende
Schreibweise nutzen:
i f (< bedingung 1>)
{
<An weisu ngsbl ock 1>
}
e l s e i f (< bedingung 2>)
{
<An weisu ngsbl ock 2>
}
...
Es gibt verschiedene Möglichkeiten, Bedingungen zu deklarieren, etwa:
if
if
if
if
(x>0)
(x>=0)
(x==0)
(x!=0)
//x
//x
//x
//x
größer als 0
größer gleich 0
gleich 0
ungleich 0
if(0<x)
&& (x<2)
if (x<=0) || (x>=2)
//x im Intervall (0,2)
//x in [-oo,0] U [2,oo]
Im Programm Additionsmaschine2c.c finden Sie eine if-else-Struktur, mit der ggfls. eine Fehlermeldung erzeugt wird:
if (
z a h l 1 >100000000 | | ( z a h l 1 <0.00000001 && z a h l 1 > 0.00000001) | |
z a h l 1 < 100000000 | | z a h l 2 >100000000 | | ( z a h l 2 <0.00000001 &&
z a h l 2 > 0 . 0 0 0 0 0 0 0 1 ) | | z a h l 2 < 100000000
)
{
p r i n t f ( " S i e muessen damit rechnen , d a s s d i e s e s E r g e b n i s wegen \
d e r G r o e s s e d e r Zahlen \n mit R u n d u n g s f e h l e r n b e h a f t e t i s t . \ n" ) ;
}
else
{
p r i n t f ( " D i e s e s E r g e b n i s wird durch R u n d u n g s f e h l e r n i c h t w e s e n t l i c h v e r f a e l s c h t . \ n" ) ;
}
Bevor wir dem Ziel dieses Abschnittes näher kommen können, das heißt, bevor wir selber einen einfachen Taschenrechner programmieren können, benötigen wir noch Funktionen bzw. Unterprogramme
und Schleifen. Wir beginnen mit Schleifen.
1.1.7.
Schleifen und Funktionen
Oft muss man innerhalb eines Programms die selbe oder zumindest sehr ähnliche Rechnungen oftmals
hintereinander durchführen. Um keine blutigen Finger zu bekommen, ist der Verzicht von so genannten
for- und while-Schleifen unverzichtbar.
Die for-Schleife
In C lautet die Syntax für die for-Schleife wie folgt:
f o r (< anweisung 1>;<bedingung >;<anweisung 2>)
{
<Anweisungsblock>
}
8
Mittels dieser Syntax wird folgendes realisiert: Zuerst wird <anweisung 1> durchgeführt. Bei dem
Schritt spricht man auch von der Initialisierung der for-Schleife. Anschließend wird der Wahrheitswert von <bedingung> geprüft. Ist dieser falsch, so wird die Schleife beendet. Ist der Wahrheitswert
von <bedingung> hingegen wahr, so wird der <Anweisungsblock> und anschließend <anweisung 2>
ausgeführt.
Als Beispiel für eine for-Schleife betrachten wir das Programm Summe_1_100_a.c, welches die Summe
der Zahlen von 1 bis 100 berechnet.
1
2
3
4
5
#include <s t d i o . h>
i n t main ( void )
{
i n t s =0; // I n i t i a l i s i e r u n g von s mit 0
i n t k ; // S c h l e i f e n z a e h l e r
6
f o r ( k=1; k<=100; k++)
{
s=s+k ;
}
7
8
9
10
11
12
13
14
}
p r i n t f ( " Die Summe von 1 b i s 100 l a u t e t %i . \ n" , s ) ;
return ( 0 ) ;
Die while-Schleife
In C lautet die Syntax für die while-Schleife folgendermaßen:
while (<bedingung >)
{
<Anweisungsblock>
}
Folglich wird beim Aufruf einer while-Schleife zunächst der Wahrheitswert von <bedingung> geprüft.
Ist dieser falsch, so wird die Schleife beendet. Andernfalls wird der <Anweisungsblock> ausgeführt.
Anschließend wird erneut der Wahrheitswert von <bedingung> geprüft, etc.
Als Beispiel für eine while-Schleife betrachten wir das Programm Summe_1_100_b.c, welches ebenfalls
die Summe der Zahlen von 1 bis 100 berechnet.
1
2
3
4
#include <s t d i o . h>
i n t main ( void )
{
i n t s =0; // I n i t i a l i s i e r u n g von s mit 0
i n t k =1; // A nf an gs we rt d e s S c h l e i f e n z a e h l e r =1
5
while ( k<=100)
{
s=s+k ;
k++;
}
6
7
8
9
10
11
12
13
14
}
p r i n t f ( " Die Summe von 1 b i s 100 l a u t e t %i . \ n" , s ) ;
return ( 0 ) ;
for- und while-Schleifen können durch den Befehl break vorzeitig beendet werden. Dazu schreibt man
break in den <Anweisungsblock>. Dieser Befehl bewirkt, dass die Schleife abgebrochen wird und das
Programm hinter den Schleifenaufruf springt.
Die continue-Anweisung– ebenfalls zu schreiben in den <Anweisungsblock> – bewirkt indes, dass alle
restlichen Anweisungen des <Anweisungsblocks> übergangen werden, und dass anschließend mit dem
nächsten eventuellen Schleifendurchlauf fortgefahren wird. Für den genauen Gebrauch der continueAnweisung studieren Sie den folgenden Programmcode. Dieser zeigt Ihnen darüber hinaus, wie man
ein Feld von Zahlen, einen so genannten array deklariert.
9
1
2
3
4
5
6
#include <s t d i o . h>
i n t main ( void )
{
i n t a [ 2 0 ] ; // I n i t i a l i s i e r u n g e i n e s F e l d e s mit 20 i n t e g e r Z a h l e n
i n t n=0; // I n i t i a l i s i e r u n g von n mit 0
int i ;
7
8
9
10
}
11
f o r ( i = 0 ; i < 2 0 ; i ++) {
p r i n t f ( "%i . Zahl : " , i +1);
s c a n f ( "%i " , &a [ i ] ) ;
12
f o r ( i =0; i <20; i ++)
{
i f ( a [ i ]<=0) continue ;
n++;
}
13
14
15
16
17
18
19
20
21
}
p r i n t f ( " Die Anzahl p o s i t i v e r Element l a u t e t %i . \ n" , n ) ;
return ( 0 ) ;
Funktionen in C
Bei größeren Programmieraufgaben ist die Verwendung von Funktionen absolut unverzichtbar. Jedes
einigermaßen abzutrennende Teilprogramm sollte in einer separaten Funktion bearbeitet werden. Das
erleichtert Ihnen (und den Leserinnen und Lesern Ihres Codes) den Überblick. Dabei ist eine rekursive
Programmierung von einer oder mehreren Funktionen durchaus möglich, aber oft nicht sinnvoll!
Die Syntax für eine Funktion mit dem Namen fname sieht folgendermaßen aus:
f t y p fname ( vtyp1 var1 , vtyp2 var , . . . )
{
f t y p f w e r t ; // D e k l a r a t i o n d e s R u e k g a b e w e r t s
<Anweisungsblock>
}
return ( f w e r t ) ;
// R u e c k g a b e w e r t w i r d z u r u e c k g e g e b e n
Zur Veranschaulichungen betrachten wir ein Programm, welches die Fakultät einer Zahl berechnet.
long f a k u l t a e t ( short a )
{
long e r g e b n i s =1;
short i =1;
}
while ( i<=a )
{
e r g e b n i s=e r g e b n i s ⇤ i ;
}
return ( e r g e b n i s ) ;
Für ein fehlerfreies Kompilieren beachten Sie, dass Funktionen vor ihrem ersten Durchlauf deklariert
werden müssen. Folglich baut man eine Programmdatei pname.c mit Funktionen folgendermaßen auf:
1
#include<s t d i o . h>
2
3
4
5
6
7
F u n k t i o n s k o p f f u e r fname1
{
...
return ( f w e r t 1 ) ;
}
8
10
9
10
11
12
13
14
15
16
17
18
19
F u n k t i o n s k o p f f u e r fname2
{
...
return ( f w e r t 2 ) ;
}
...
i n t main ( void )
{
...
w1=fname1 ( . . . ) ;
w2=fname2 ( . . . ) ;
20
21
22
}
return ( 0 ) ;
Auf der Webseite zur Vorlesungen finden Sie die Programme Fakultaet.c und Taschenrechner.c.
Beide erhalten eine bzw. mehrere Funktionen. Bevor wir auf das Programm Taschenrechner.c zu
sprechen kommen, folgen einige Bemerkungen zum Programm Fakultaet.c.
Wie Sie zum Teil schon in der Vorlesung ausprobiert haben, kann ein einfacher (Schul)taschenrechner
die Fakultät der Zahl 100 nicht berechnen. Diese Zahl ist einfach zu groß! Unser Programm Fakultaet.c
bricht bereits bei der Eingabe von Zahlen die größer als 20 sind ab. Das liegt an dem extrem schnellen Wachstum der Fakultätsfunktion: In der Analysis werden Sie die Stirlingsche Formel zu dessen
asymptotischen Verhalten kennen lernen. Diese besagt, dass
⇣ n ⌘n
p
n! t 2⇡n
.
e
Dabei bedeutet t asymptotisch gleich, also dass der Quotient der beiden Funktionen für n ! 1
gegen 1 konvergiert.
1.1.8.
Geschachtelte Kontrollstrukturen – ein einfacher Taschenrechner
Das Programm Taschenrechner.c arbeitet bei der Variablenübergabe an das Unterprogramm division
mit einem Zeiger. Das Prinzip des Zeigers kann man sich an folgendem Problem veranschaulichen:
Mein Klavier muss neu gestimmt werden. Da es aber sehr groß ist, ist es schlecht zu transportieren. Daher gebe ich der Klavierstimmerin eine Wegbeschreibung, damit sie zu mir
kommt.
Ähnliche Probleme treten in der Programmierung auf. Zum Beispiel wenn ein Datensatz (oder auch
nur eine Variable) durch eine Funktion bearbeitet werden soll. In dem Fall nimmt der Datensatz die
Funktion des Klaviers ein, und die Wegbeschreibung wird durch einen Zeiger ersetzt.
Streng genommen haben wir Zeiger schon bei dem Befehl scanf("%i", &zahl1);
kennen gelernt.
Dabei ist &zahl1 ein Zeiger auf die Variable zahl1. Die Wirkungsweise ist folgende: Die eingegebene
Zahl wird in dem für die Variable zahl1 reservierten Speicherplatz abgespeichert und kann mit zahl1
wieder abgerufen werden.
Um das allgemeine Verständnis von Zeigern zu schulen, folgt ein einfaches Beispiel. Dieses produziert
die Ausgabe: Zeiger-Wert: 7
i n t z a h l =7;
i n t ⇤ z e i g e r ; // Z e i g e r mit dem Namen ’ Z e i g e r ’ w i r d d e k l a r i e r t
z e i g e r=&z a h l ; // J e t z t z e i g t d e r Z e i g e r a u f d i e A d r e s s e d e r V a r i a b l e n
// ’ z a h l ’
p r i n t f ( " Z e i g e r Wert : %i \n" , ⇤ z e i g e r ) ;
Die Verwendung des Zeigers bei der Funktion division im Programm Taschenrechner.c erfolgt
aufgrund des Problems, dass man einer Funktion zwar mehrere Variablen übergeben kann, aber dass
sie im Allgemeinen nur einen Wert zurückgeben kann. Dabei soll die Divisionsfunktion zwei Werte
zurückgeben. Falls die Division wegen eines Nenners, der (ungefähr) gleich 0 ist, nicht durchgeführt
werden kann, soll der Wert 1 für die warnung-Variable zurückgegeben werden. Andernfalls soll die
11
Funktion das Ergebnis der Division zurückgeben und die warnung-Variable soll den Wert 0 behalten.
Indem wir der Funktion division einen Zeiger auf die Variable warnung übergeben (und nicht die
Variable selbst), kann die Funktion die Variable überschreiben, obwohl sie nur im Hauptprogramm
deklariert ist.
1.1.9.
Rundungsfehlerproblematik
Das Ziel dieses Abschnittes ist eine (kurze) Diskussion von möglichen Rundungsfehlern. Dieser Abschnitt ist keinesfalls vollständig, soll aber anhand von zwei Grundrechenarten die Problematik veranschaulichen.
Speichern wir in C eine reelle Zahl a 2 R als Gleitkommazahl (zum Beispiel als double), so haben wir
es im Allgemeinen mit einer Zahl im Intervall [a ", a + "] zu tun. Wir nennen " > 0 den absoluten
Fehler von a.
Bei double-Variablen, die im Intervall [ 108 , 108 ] liegen, hat man wegen der 16 gültigen Stellen einen
absoluten Fehler von maximal " = 10 8 . Diese Zahl ist eigentlich ganz schön klein und nicht gerade
aufsehen erregend. In Abschnitt 1.1.4 waren wir dennoch aufgrund der auftretenden Problematiken
etwas irritiert. Woran liegt das?
Das liegt an dem so genannten relativen Fehler |"/a|. Ist a = 10 9 und " = 10 9 , so ist der absolute
Fehler natürlich sehr klein, der relative Fehler mit 1 aber recht groß.
Wir betrachten nun exemplarisch die Addition und die Multiplikation von zwei Gleitkommazahlen in
den Intervallen [a1 "1 , a1 + "1 ] bzw. [a2 "2 , a2 + "2 ], das heißt mit absoluten Fehlern "1 , "2 > 0.
Addition Das Ergebnis der Addition der beiden Zahlen liegt im Intervall [(a1 + a2 )
a2 ) + ("1 + "2 )].
("1 + "2 ), (a1 +
Der absolute Fehler lässt sich abschätzen durch  "1 + "2 . Damit ist dieser unproblematisch
insofern die absoluten Fehler der zwei Summanden klein sein.
Beim relativen Fehler ist die Situation etwas komplizierter:
✓
◆
|a1 | |a"11 | + |a2 | |a"22 |
"1 + "2
max{|a1 |, |a2 |} "1
"2
=

+
a1 + a2
a1 + a2
|a1 + a2 |
|a1 | |a2 |
Der zweite Faktor des Terms auf der rechten Seite ist die Summe der relativen Fehler der zwei
Summanden a1 und a2 , und damit für unkomplizierte Eingabewerte ebenfalls unkompliziert.
Der erste Faktor hingehen, ist nur dann harmlos, solange zwei etwa gleich große Zahlen addiert
werden. Gilt hingegen a1 ⇡ a2 so ist dieser Faktor extrem groß, und somit auch der relative
Fehler des Ergebnisses der Addition.
Multiplikation Ohne Einschränkung nehmen wir an, dass a1 > 0, a2 > 0, 0 < "1 < a1 und 0 < "2 <
a2 . Damit liegt das Ergebnis der Multiplikation der beiden Zahlen im Intervall [(a1 "1 )(a2
"2 ), (a1 + "1 )(a2 + "2 )].
Der absolute Fehler ist  a1 "2 + a2 "1 + "1 "2 , also groß für große Zahlen.
Der relative Fehler lässt sich abschätzen durch  a"11 + a"22 + a"11 "a22 . Falls also die relativen Fehler
"1 /a1 und "2 /a2 klein sind, so ist auch der relative Fehler des Ergebnisses der Multiplikation
unproblematisch.
12
Kapitel 2
Zahlentheorie
2.1.
Der euklidische Algorithmus, größter gemeinsamer Teiler, Primzahlen
Wir starten dieses Kapitel mit einigen Bezeichnungen. Für die natürlichen Zahlen schreiben wir N =
{1, 2, 3, . . .} bzw. N0 := N [ {0}, falls wir die Null mitnehmen. Die ganzen Zahlen bezeichnen wir mit
Z = {. . . , 2, 1, 0, 1, 2, . . .}.
2.1.1.
Definition (a teilt b)
Für a, b 2 Z sagt man: a teilt b und schreibt a | b, falls es eine ganze Zahl c 2 Z gibt mit b = ac.
Andernfalls schreibt man a - b.
2.1.2.
Eigenschaften
Es folgen einige einfache Eigenschaften der Teilbarkeitslehre.
1. Jedes b 2 Z hat die trivialen Teilen a = ±1 und a = ±b.
2. Jedes a 2 Z ist Teiler von b = 0, d.h. es gilt a | 0 für alle a 2 Z. Andererseits folgt aus 0 | a, dass
a = 0. Deshalb lässt man die 0 bei der Teilbarkeitslehre weg.
3. Teilbarkeit ist transitiv, d.h. stets gilt
a | b,
b|c
=)
a|c
4. Weiterhin gilt stets
a 1 | b1 ,
a|b
=)
a 2 | b2
=)
ac | bc 8c 2 Z
a 1 a 2 | b 1 b2
5. Die einzigen Teiler von ±1 sind ±1. Daraus ergibt sich
a | b,
b|a
=)
a
= ±1 bzw. b = ±a
b
Bemerkung. Wir bauen die folgende Primzahl-Theorie auf dem Satz über den größten gemeinsamen
Teiler (ggT) auf.
2.1.3.
Definition (größter gemeinsamer Teiler)
1. Eine Zahl c 2 Z heißt gemeinsamer Teiler von a, b 2 Z, falls c | a und c | b.
2. Eine Zahl d 2 Z heißt größter gemeinsamer Teiler von a, b 2 Z, kurz d = ggT(a, b), falls
13
(a) d 2 N,
(b) d | a und d | b,
(c) aus c | a und c | b folgt c | d.
3. Zwei Zahlen a, b 2 Z heißen teilerfremd (oder relativ prim), falls ggT(a, b) = 1.
Eindeutigkeit von d = ggT(a, b)
Die Zahl d = ggT(a, b) ist eindeutig bestimmt. Diese Behauptung kann man zeigen, indem man das
Gegenteil annimmt: Seien d1 = ggT(a, b) und d2 = ggT(a, b). Aus Definition 2.1.3 2.(c) folgt d1 | d2
und d2 | d1 . Mit Eigenschaft 2.1.2 5. folgt d2 = ±d1 und folglich d1 = d2 , da es sich bei dem ggT um
eine natürliche Zahl handelt.
Existenz von d = ggT(a, b), falls a, b 6= 0
Wir zeigen als nächstes, dass der größte gemeinsame Teiler zweier Zahlen a, b 6= 0 immer existiert. Aus
a, b 6= 0 folgt 1 | a und 1 | b. Sei nun G(a, b) := {g 2 N : g | a und g | b} die Menge der natürlichen
gemeinsamen Teiler von a und b. Man sieht leicht, dass die Menge G(a, b) nach oben beschränkt ist,
denn es gelten
8g 2 N mit g | a gilt g  |a| und 8g 2 N mit g | b gilt g  |b|.
Damit hat die Menge G(a, b) ein maximales Element, und damit gilt ggT(a, b) = max{g 2 G(a, b)}.
Naiv kann man den größten gemeinsamen Teiler zweier Zahlen a, b 2 Z also berechnen, indem man
alle Möglichkeiten für g 2 G(a, b) (also für g = 1, 2, . . . , min{|a|, |b|}) systematisch durchprobiert und
ständig den maximalen Wert gmax updated (siehe Übungsblatt 4).
Viel schneller findet man den größten gemeinsamen Teiler zweier Zahlen a, b 2 Z aber über den Euklidischen Algorithmus (Euklid von Alexandria, griechischer Mathematiker, 3. Jahrhundert vor Christus).
Der Euklidische Algorithmus basiert auf dem folgenden (aus der Analysis bekannten?) Lemma.
2.1.4.
Lemma (Division mit Rest)
Seien a, b 2 N zwei natürliche Zahlen. Dann existieren eindeutig bestimmte Zahlen q 2 N und r 2 N0
mit
a = qb + r
und 0  r < b.
Wir nennen q den ganzzahliger Quotienten und r den ganzzahligen Rest der Division mit Rest.
Beweis. Sei x = a/b. Dann ist a = qb + r äquivalent zu x = q + r/b. Da x 2 (0, 1), folgt nach
einer Intervallschachtelung die Existenz einer natürlichen Zahl n 2 N mit x 2 [n 1, n). Setzen wir
anschließend q = n 1, so gilt 0  x q = x n + 1, und folglich 0  r/b < 1 bzw. 0  r < b.
Der Euklidische Algorithmus
Der Euklidische Algorithmus wird folgendermaßen initialisiert
a 0 = a = r0 a + s 0 b
mit r0 = 1, s0 = 0
a 1 = a = r1 a + s 1 b
mit r1 = 0, s1 = 1
14
Anschließend werden die Zahlen q1 , a2 2 N0 eindeutig aus der Division mit Rest (Lemma 2.1.4) bestimmt
mit 0  a2 < a1 .
a 0 = q1 a 1 + a 2
Falls a2 = 0, so gilt a1 | b und a1 | a(= a0 ), und es gilt ggT(a, b) = a1 (Beweis später!).
Andernfalls gilt 0 < a2 < a1 und die Division mit Rest (Lemma 2.1.4) liefert eindeutige Zahlen
q2 , a3 2 N0 mit
mit 0  a3 < a2 .
a 1 = q2 a 2 + a 3
Falls a3 = 0, so gilt ggT(a, b) = a2 (Beweis später!). Andernfalls gilt 0 < a3 < a2 und Lemma 2.1.4
liefert erneut eindeutige Zahlen q3 , a4 2 N0 mit
mit 0  a4 < a3 .
a 2 = q3 a 3 + a 4
etc. Es wird also eine absteigende Kette
b = a1 > a2 > . . . > a` > a`+1 = 0,
`
1
durch die Rekursion aj = qj aj + aj+1 mit 0  aj+1 < aj , j = 1, . . . , `, erzeugt. Für die letzte Gleichung
gilt a` 1 = q` a` . Es bleibt zu zeigen, dass wir mit a` tatsächlich den größten gemeinsamen Teiler von
a und b gefunden haben.
Beweis. Wegen a`+1 = 0 gilt a` | a` 1 . Ferner gilt wegen a` 2 = q` 1 a` 1 + a` , a` | a` und a` | a` 1 ,
dass a` | a` 2 . Analog zeigt man a` | a` 3 , . . . , a` | a1 = b. Andererseits teilt a` auch a, da a = a0 =
q1 a1 + a2 , a` |a1 und a` |a2 .
Sei nun c = gT (a, b) ein gemeinsamer Teiler von a und b, also c | a0 und c | a1 . Aus a0 = q1 a1 + a2
folgt c | a2 und folglich auch c | a3 , . . . , c | a` .
Endlichkeit des Euklidischen Algorithmus. Aufgrund der absteigenden Kette
b = a1 > a2 > . . . > a` > a`+1 = 0,
`
1
gilt l  b. Man sollte a, b 2 N also in der Reihenfolge bezeichnen, dass b = min(a, b) gilt.
2.1.5.
Beispiel.
Wir wollen die Zahl a = 91 = ao teilen durch die Zahl b = 37 = a1 . Die Rekursionsformel aj
qj aj + aj+1 des Euklidischen Algorithmus liefert:
91 = 2 · 37 + 17
1
=
3=1·2+1
37 = 2 · 17 + 3
2=2·1+0
17 = 5 · 3 + 2
Da a`+1 = a6 = 0, ist der größte gemeinsame Teiler gegeben durch a` = a5 = 1.
Die Bézout-Koeffizienten
Die Bézout-Koeffizienten (benannt nach dem französischen Mathematiker Étienne Bézout (1730-1783))
bestimmen für g = ggT(a, b) zwei ganze Zahlen r, s 2 Z sodass g = ra + sb. Die Bézout-Koeffizienten
werden später noch eine wichtige Rolle spielen.
15
Die Bestimmung von r und s erfolgt durch den so genannten erweiterten Euklidischen Algorithmus.
Dieser bestimmt zu jedem j = 0, 1, . . . , ` zwei Zahlen rj , sj 2 Z mit aj = rj a + sj b. Man kann zeigen,
dass für alle j = 1, 2, etc. gilt
rj+1 = rj
1
rj q j
und sj+1 = sj
1
s j qj
Für j = ` erhält man so ggT(a, b) = a` = r` a + s` b, also sind die Bézout-Koeffizienten gegeben durch
r = r` und s = s` .
Beispiel. Für die Zahlen a = 91 und b = 37 aus dem obigen Beispiel erhält man die Darstellung
ggT(91, 37) = 1 = ( 13) · 91 + 32 · 37.
2.1.6.
Satz (Euklidischer Algorithmus)
Seien a, b 2 N zwei natürliche Zahlen und a0 = a, a1 = b. Dann gibt es eine Schrittzahl ` mit 1  `  b,
sodass für die rekursiv definierte Folge {aj } mit
aj
1
= qj aj + aj+1
8j = 1, . . . , `
gilt a`+1 = 0 und a` = ggT(a, b). Ferner gibt es zwei Zahlen r, s 2 Z mit
ggT(a, b) = ra + sb.
Bestimmung von qj , aj+1 2 N0 in C. Nach Lemma 2.1.4 existieren eindeutige Zahlen qj , aj+1 mit
aj 1 = qj aj + aj+1 und 0  aj+1 < aj . Hier ist aj+1 offenbar der Rest der Division von aj 1 durch aj .
In der Programmiersprache C gibt der Befehl a % b für int/long-Variablen den Rest der Division von
a durch b aus (zum Beispiel gilt 7 % 3 = 1, 9 % 3 =0).
Damit ermittelt man qj , aj+1 2 N0 aus a_j+1=a_j-1 % a_j und q_j = (a_j-1-a_j+1) / a_j;.
2.1.7.
Das Programm ggT.c
Den Programmcode für das Programm ggT.c können Sie sich auf der Webseite zur Vorlesung herunterladen.
In dem Programmcode werden für die Variablen aj , qj , rj und sj keine Felder eingeführt, sondern nur
die folgenden Variablen für die Rekursion:
A
AV
AVV
=
ˆ
=
ˆ
=
ˆ
Variable für ein neues aj+1 , also
Vorgänger von A = aj+1 , also
Vorvorgänger von A = aj+1 , also
A = aj+1
AV = aj
AV V = aj
1
Analog werden die Variablen R, RV, RV V , S, SV, SV V und Q, QV, QV V definiert.
2.1.8.
Folgerung aus Satz 2.1.6
Seien a, b 2 Z\{0}. Dann gelten
1. ggT(a, b) = ggT(|a|, |b|)
2. Wenn ggT(a, b) = 1, so gilt
(a) aus a | bc folgt a | c,
(b) aus a | c und b | c folgt ab | c.
Beweis. Übungsblatt 5, Aufgabe 4.
16
Bemerkung. Man kann den größten gemeinsamen Teiler mehrerer Zahlen rekursiv definieren über
ggT(z1 , . . . , zk+1 ) := ggT(ggT(z1 , . . . , zk ), zk+1 ) 8k = 1, 2, . . .
Zusammenhang zwischen dem Euklidischen Algorithmus, den Fibonacci-Zahlen und dem
Goldenen Schnitt.
Die Fibonacci-Zahlen, benannt nach dem Pisaner Rechenmeister Leonardo Fibonacci (* um 1170 in
Pisa; † nach 1240 ebenda), weisen einige bemerkenswerte mathematische Besonderheiten auf. Unter
anderen beschrieb Fibonacci mit ihnen das Wachstum einer Kaninchenpopulation. Ferner lässt sich die
Laufzeit des Euklidischen Algorithmus, das heißt die Anzahl der benötigten Iterationen, mit Hilfe der
Fibonacci-Zahlen abschätzen.
Die Fibonacci-Zahlen sind definiert durch f0 := 1, f1 := 1 und die Rekursionsformel fk := fk 1 + fk 2
für alle k 2. Aus der Rekursionsformel folgt nun unmittelbar
fn+1 = 1 · fn + fn
fn = 1 · fn
1
1 + fn
2
···
f3 = 1 · f2 + f1
f2 = 1 · f1 + f0
= 2 · f1 + 0
Da f0 = 1 folgt also: Je zwei aufeinanderfolgende Fibonacci-Zahlen sind teilerfremd. Damit durchläuft
der Euklidische Algorithmus für die Berechnung von ggT(fn+1 , fn ) genau n Iterationen (vergleiche
Übungsaufgabe 1 (a) auf Blatt 6).
Wenden wir den Euklidischen Algorithmus auf zwei aufeinanderfolgende Fibonacci-Zahlen fn+1 und
fn an, so sind alle ganzzahligen Quotienten qk = 1. Da beim Euklidischen Algorithmus die Reste ak+1
umso schneller klein werden, desto größer die Quotienten qk sind, ist die Eingabe zweier aufeinanderfolgende Fibonacci-Zahlen der ungünstigste Fall, was die Anzahl der nötigen Schritte betrifft. Da
fn in Abhängigkeit von n exponentiell wächst, folgt, das die Anzahl der Schritte zur Berechnung von
ggT(a, b) höchstens logarithmisch mit a, b wächst. Der Euklidische Algorithmus ist also eine sehr effiziente Methode zur Berechnung des größten gemeinsamen Teilers großer Zahlen. Er benötigt nicht die
Primfaktorzerlegung der Argumente.
Per Induktion kann man zeigen, dass für Fibonacci-Zahlen folgende explizite Darstellung gilt (vergleiche
Übungsaufgabe 1 (b) auf Blatt 6)
0
1
p !k+1
p !k+1
1 @ 1+ 5
1
5
A.
ak = p
2
2
5
p
Dabei ist der Term 1+2 5 die irrationale Zahl, die sich aus den Längenverhältnissen des Goldenen
Schnitts ergibt. Als Goldenen Schnitt bezeichnet man das Teilungsverhältnis einer Strecke a + b, bei
dem das Verhältnis des Ganzen zu seinem größeren Teil a dem Verhältnis des größeren zum kleineren
Teil b entspricht,
p
a+b
a
1+ 5
= =
=: .
a
b
2
Da, wie oben gesehen ein Zusammenhang zwischen den Fibonacci-Zahlen und dem Euklidischen Algorithmus besteht, und die Fibonacci-Zahlen wiederum etwas mit dem Goldenen Schnitt zu tun haben,
hat der Euklidische Algorithmus folglich auch etwas mit dem Goldenen Schnitt zu tun:
Geometrisch lässt sich der Euklidische Algorithmus folgendermaßen darstellen: Von einem Rechteck
der Seitenlängen a und b (a > b) werden Quadrate der Seitenlänge b abgeschnitten, solange es geht.
17
Dann bleibt ein Restrechteck übrig, von welchem wieder Quadrate abgeschnitten werden und so weiter.
Das Verfahren wird solange fortgesetzt, bis das Zerlegen in Quadrate aufgeht, das heißt, kein Rechteck
mehr übrig bleibt. Die Seitenlänge des kleinsten Quadrats ist dann der größte gemeinsame Teiler von
a und b:
Wenden wir nun den Euklidische Algorithmus auf a =
und b = 1 an, so bricht das Verfahren
nie ab, da ja nach dem Abschneiden eines Quadrates wieder ein Rechteck mit den Seitenverhältnis
herauskommt, etc. Wenn man nun genau hinguckt, kann die die so genannte Goldene Spirale entdecken.
Bemerkung. In einer Zahlentheorievorlesung würden nun Bemerkungen über kleinste gemeinsame
Vielfache folgen. Deren Theorie verläuft aber analog zu der ggT-Theorie und ist daher von unserem
algorithmischen Standpunkt aus entbehrlich. Wir wenden uns nun dem Konzept der Primzahl zu.
2.1.9.
Definition (Unzerlegbarkeit, Primzahl)
1. Eine Zahl p 2 N heißt unzerlegbar, falls p > 1 und p nur die trivialen Teiler 1 und p besitzt.
2. Eine Zahl p 2 N heißt prim (oder Primzahl ), falls p > 1 und für alle Zahlen a, b 2 N gilt:
p | ab
2.1.10.
=)
p | a oder p | b.
Satz
Eine Zahl p 2 N ist unzerlegbar genau dann wenn p auch prim ist.
Beweis. Unzerlegbarkeit ) Primzahl: Sei eine Zahl p 2 N unzerlegbar, d.h. p > 1 und 1 und p sind
die einzigen Teiler in N. Seien nun a, b 2 N so, dass p | ab. Gilt p | a, so ist nichts zu zeigen. Daher
nehmen wir an, dass p - a. Der einzige gemeinsame Teiler von p und a ist 1, also ggT(p, a) = 1. Mit
p | ab und mit Folgerung 2.1.8 gilt daher p | b.
Unzerlegbarkeit ( Primzahl: Sei nun p 2 N prim; insbesondere gilt damit p > 1. Sei a 2 N Teiler von
p. Damit existiert ein b 2 N mit p = ab. Damit gilt p | ab. Da p prim ist, folgt p | a oder p | b.
• Wenn p | a, dann existiert ein c 2 N mit a = pc. Mit p = ab gilt damit p = ab = pcb. Da man in
N kürzen kann, folgt 1 = bc, und damit 1 = b = c. Folglich gilt auch a = p.
• Wenn p | b folgt analog a = 1
Also hat p in N nur die trivialen Teiler 1 und p und ist damit unzerlegbar.
18
Bemerkung. In der Schule werden Primzahlen oft genauso definiert, wie wir die Unzerlegbarkeit
in Definition 2.1.9 definiert haben. Gott sei Dank besagt Satz 2.1.10, dass die beiden Begriffe in
N äquivalent sind. In allgemeinen Integritätringen gilt das nicht, sondern nur in solchen ‘mit einem
euklidischen Algorithmus’.
2.1.11.
Lemma
Jedes n 2 N\{1} besitzt einen kleinsten Primteiler p = pmin (n) > 1 (das heißt p | n und p ist prim).
Die Zahl n 2 N\{1} ist prim genau dann wenn pmin (n) = n.
Beweis. Betrachte die Teilmenge
T (n) := {a 2 N ; a
2 und a | n}.
Da n selber ein Element der Menge T (n) ist, ist T (n) nicht leer. Damit besitzt T (n) ein minimales
Element p 1. Wegen der Minimalität ist p unzerlegbar und nach Satz 2.1.10 auch prim.
Naiver Algorithmus zur Bestimmung des kleinsten Primfaktors
Da a  n für alle a 2 T (n) gilt, kann man pmin (n) durch eine Vorwärtssuche programmieren:
f o r ( j =2; j<=n ; j ++)
{
i f ( n%j == 0 ) return ( j ) ;
}
// Test , ob j | n
Wir wollen als nächstes die Anzahl der Operationen (n%j und j++) bestimmen, um den kleinsten
Primteiler so zu bestimmen:
• Sei An die Anzahl der Operationen zu einem gegebenen n 2 N\{1}
• Wenn n prim ist, so gilt An = n
1
• Angenommen die Zahl n habe d-Stellen in der Basis 10. Damit gilt
n = (ad
1 ad 1 . . . a1 a0 )10
 (99 . . . 9)10 ,
und folglich 0.1 · 10d  n < 10d .
• Im ungünstigsten Fall, also wenn n prim ist, muss man mit einem Aufwand von An = O(10d )
(sprich: groß O von 10d ) rechnen.
Den kleinsten Primteiler kann man zwar (etwas) schneller berechnen, aber nicht viel. Zum Glück! Denn
auf der Primfaktorsuche beruhen viele unserer Verschlüsselungsalgorithmen.
Verbesserung des Algorithmus zur Bestimmung des kleinsten Primfaktors
Als erstes stellen wir die Beobachtung auf, dass aus n = ab mit a  b, a, b 2 N folgt
p
a2  ab = n
=)
a  n.
Damit reicht es nur bis j 
p
n zu suchen, also wie in min_prim_teiler.c:
#include<s t d i o . h>
#include<math . h>
...
w=s q r t ( n+1. e 16); // wegen R u n d u n g s f e h l e r
...
f o r ( j =2; j<=w ; j ++)
{
19
...
i f ( n%j ==0)
{p=j ;
break ;
}
}
Wir wollen erneut die Anzahl an Operationen (n%j und j++) bestimmen, um den kleinsten Primteiler
so zu bestimmen:
• Sei nun A0n die Anzahl der notwendigen Operationen, und die Zahl n habe wieder d-Stellen in
der Basis 10.
p
p d
• Dann gilt A0n  10d = 10 .
p d
• Im ungünstigsten Fall, also wenn n prim ist, gilt A0n = O( 10 ).
p d
• Damit ist der verbesserte Algorithmus im ungünstigsten Fall etwa um den Faktor 10 besser.
2.1.12.
Satz (von Euklid)
Es gibt unendlich viele Primzahlen.
Beweis. Wie nehmen an, dass es nur endlich viele Primzahlen gibt, also n Stück:
1 < p1 < p2 < . . . < pn .
Sei nun die natürliche Zahl z definiert durch z := p1 p2 · · · pn + 1. Damit gilt für jedes pk , k = 1, . . . , n
z
= p1 · · · pk 1 pk+1 pn + 1/pk
|
{z
} |{z}
pk
2N
2
/ N.
2(0,1)
Das heißt nichts anderes, als dass keins der p1 , . . . , pn Teiler von z sind. Aus Lemma 2.1.11 folgt nun,
dass pmin (z) > pn . Aber das ist ein Widerspruch zur Annahme, dass pn die größte Primzahl ist.
Bemerkungen über die Verteilung von Primzahlen
1. Die Menge P (n) := {p 2 N | p ist prim und p  n} enthält alle Primzahlen bis zu einer gegebenen
Zahl n. Mit #P (n) bezeichnen wir die Anzahl der Elemente in P (n).
Johann Carl Friedrich Gauß (deutscher Mathematiker, 1777-1855) vermutete und Jacques Hadamard (französischer Mathematiker, 1865-1963) und Charles-Jean de la Vallée-Poussin (belgischer
Mathematiker, 1866-1962) bewiesen unabhängig den so genannten Primzahlsatz, das heißt
✓
◆
n
lim #P (n)
= 0.
n!1
ln(n)
2. Sei dn die Anzahl der verschiedenen Primfaktoren von n. Godfrey Harold Hardy (1877-1947) und
S. Ramanujan (1887-1920) bewiesen 1917, dass dn durchschnittlich ln(ln(n)) ist.
2.1.13.
Hauptsatz (über die Primfaktorzerlegung)
Jede Zahl n 2 N\{1} kann eindeutig als Produkt von Potenzen wachsender Primzahlen pj dargestellt
werden:
n=
pv11 pv22
· · · pvkk
20
=
k
Y
j=1
v
pj j ,
mit 2  p1 < . . . < pk  n, k 2 N. Wir nennen vj 2 N die Vielfachheit des Primteilers pj .
Beweis. Wir beweisen zunächst die Existenz der Darstellung und anschließend die Eindeutigkeit.
1. (Existenz). Der Nachweis erfolgt konstruktiv nach dem folgenden Algorithmus:
(a) Setze n1 = n und bestimme p1 = pmin (n1 ). Bestimme anschließend die Anzahl v1 , die
beschreibt wie oft p1 Teiler von n1 ist.
(b) Setze nun n2 = n/pv11 2 N und bestimme p2 sowie v2 , etc. bis nk+1 = 1.
2. (Eindeutigkeit). Wir nehmen an, dass es zwei Darstellungen gibt, das heißt
pv11 pv22 · · · pvkk = q1w1 q2w2 · · · qkwk = n1 = n.
Nun gilt p1 = pmin (n1 ) und q1 = pmin (n1 ), also p1 = q1 und folglich auch v1 = w1 . Für j = 2, . . . , k
folgt analog pj = qj und vj = wj .
2.2.
Kongruenzen, Restklassenringe und -körper, Chinesischer Restsatz
Restklassenkörper sind heutzutage für die Sicherheit in der public key cryptography unverzichtbar.
Beispielsweise wird dort das diskrete Logarithmusproblem eingesetzt, welches extrem rechenaufwendig
ist.
2.2.1.
Definition (Kongruenz modulo m)
Zwei Zahlen a, b 2 Z heißen kongruent modulo m, kurz a ⌘ b mod m, falls m | (a
b).
Das folgende Lemma zeigt, dass Kongruenz modulo m eine Äquivalenzrelation ist. (Äquivalenzrelationen
kennen Sie sicherlich bereits aus der Linearen Algebra. Wie Sie wissen, sind sie dem Gleichheitsbegriff
nachempfunden und verallgemeinern diesen.)
2.2.2.
Lemma (Äquivalenzrelation)
Sei m 2 Z\{1} fest. Dann gelten
1. Reflexivität: a ⌘ a mod m für alle a 2 Z.
2. Symmetrie: Aus a ⌘ b mod m folgt b ⌘ a mod m.
3. Transitivität: Aus a ⌘ b mod m und b ⌘ c mod m folgt a ⌘ c mod m.
Beweis: Übungsaufgabe.
Als nächstes zeigen wir, dass Äquivalenzrelationen Klasseneinteilungen nachsichziehen.
2.2.3.
Definition und Hilfssatz
1. Für eine Zahl a 2 Z und ein festes m 2 N\{1} heißt
a := {b 2 Z | b ⌘ a
mod m} = {z = a + km | k 2 Z}
die Restklasse von a modulo m. Damit enthält a alle ganzen Zahlen, die bei Division durch m
den selben Rest 2 {0, 1, . . . , m 1} lassen wie a.
2. Mit Zm := {a | a 2 Z} = {0, 1, . . . , m
1} bezeichnen wir die Menge aller Restklassen modulo m.
3. Es gilt stets:
(a) Aus a \ b 6= ; folgt a = b.
S
S 1
(b) Z = aZ a = m
a=0 a.
21
Beweis. Zu (a): Sei a \ b 6= ;. Dann existieren k, l 2 Z mit a + km = b + lm, und folglich a b =
(l k)m. Damit
m | (a b), was gleichbedeutend istSmit a ⌘ b mod m und somit a = b.
Sm gilt
1
1
Zu (b): Z
a
ist klar. Wir müssen also nur Z ⇢ m
a=0
a=0 a zeigen: Dazu sei b 2 Z eine beliebige
Zahl. Wir betrachten die Menge
{↵ 2 N0 | ↵ ⌘ b
mod m} = {b + km | k 2 Z und b + km
0}.
Das Minimum dieser Menge existiert und wir bezeichnen es mit
a := min{↵ 2 N0 | ↵ ⌘ b
mod m}.
Nun gilt a ⌘ b mod m und folglich b 2 a. Außerdem gilt a < m, da a ansonsten nicht minimal wäre.
2.2.4.
Lemma (Rechnen mit Kongruenzen)
Sei a ⌘ a0 mod m und b ⌘ b0 mod m. Dann gelten
1. Addition: a + b ⌘ a0 + b0 mod m.
2. Subtraktion: a
b ⌘ a0
b0 mod m.
3. Multiplikation: ab ⌘ a0 b0 mod m.
Beweis. Voraussetzungsgemäß finden wir Zahlen k, l 2 Z mit a
folgen die Behauptungen aus den folgenden drei Gleichungen:
1. (a + b)
(a0 + b0 ) = (a
a0 ) + (b
b0 ) = km + lm = (k + l)m,
2. (a
(a0
a0 )
b0 ) = km
b)
3. (ab)
b0 ) = (a
(b
(a0 b0 ) = (a0 + km)(b0 + lm)
lm = (k
a0 = km und b
b0 = lm. Damit
l)m,
a0 b0 = (a0 l + b0 k + klm)m.
Als nächstes fragen wir uns, ob man in den Mengen Zm auch rechnen kann? Die Versuchung ist
beispielsweise groß, a + b = a + b und ab = ab zu setzen.
Wir beobachten zunächst, dass aus a0 2 a und b0 2 b folgt
a0 ⌘ a
mod m
und
b0 ⌘ b
mod m.
Mit Lemma 2.2.4 gilt a0 + b0 ⌘ a + b mod m und folglich a0 + b0 2 a + b mod m. Damit können wir
die Addition und die Multiplikation in Zm definieren.
2.2.5.
Definiten und Satz (Addition und Multiplikation in Zm )
1. Durch
a + b := a + b und a · b := a · b
für alle a, b 2 Zm werden Addition und Multiplikation in Zm definiert.
2. Es gelten die Eigenschaften eines kommutativen Ringes mit Einselement, d.h.
(a)
(b)
(c)
(d)
(e)
(f)
(g)
Assoziativgesetz für ‘+’: (a + b) + c = a + (b + c)
Nullelement: a + 0 = a
Inverses Element bzgl. ‘+’: a + a = 0
Assoziativgesetz für ‘·’: (ab)c = a(bc)
Einselement: a1 = 1a = a
Kommutativität für ‘·’: ab = ba
Distributivgesetz: a(b + c) = ab + ac
22
Beweis. Exemplarisch beweisen wir nur (a): Aus der Definition der Addition folgt zunächst
(a + b) + c = (a + b) + c = (a + b) + c.
Aus dem in Z gültigen Assoziativgesetz und der erneuten Anwendung der Definition der Addition in
Zm schließen wir
(a + b) + c = a + (b + c) = a + (b + c).
2.2.6.
Beispiel für Z4
Wie betrachten die Restklassen Z4 = {0, 1, 2, 3}. Diese sind
0 = {. . . , 8, 4, 0, 4, 8, 12, . . .}
1 = {. . . , 7, 3, 1, 5, 9, 13, . . .}
2 = {. . . , 6, 2, 2, 6, 10, 14 . . .}
3 = {. . . , 5, 1, 3, 7, 11, 15 . . .}
Für die Addition und die Multiplikation ergeben sich die zwei folgenden Tabellen:
+ 0 1 2 3
·
0 1 2 3
0
1
2
3
0
1
2
3
0
0
0
0
0
1
2
3
1
2
3
0
2
3
0
1
3
0
1
2
0
1
2
3
0
2
0
2
0
3
2
1
Dabei beobachten wir, dass zum Beispiel 2 2 = 0 gilt. Ein Produkt kann also 0 sein, obwohl beide
Faktoren ungleich Null sind. Das heißt, es gibt Nullteiler und insbesondere ist Z4 damit kein Körper.
2.2.7.
Anwendungen der Kongruenz-Rechnung
1. Quersummen-Regel für die Teilbarkeit durch 3 und 9: Sei m 2 {3, 9}. Dann gilt
10 ⌘ 1
mod m,
102 ⌘ 1
mod m,
...,
10k ⌘ 1
mod m
für alle k 2 N.
Die Zifferndarstellung
einer n-stelligen Zahl z 2 Z zur Basis 10 lässt sich schreiben als z =
Pn
k
a0 + k=1 ak 10 . Aus 10k ⌘ 1 mod m, ak ⌘ ak mod m und Lemma 2.2.4 folgt ak 10k ⌘ ak
mod m und folglich auch
z = a0 +
n
X
k=1
ak 10k ⌘
n
X
ak
mod m.
k=0
Damit ist der Rest von z = (an · · · a1 a0 )10 bei Division durch m = 3 oder m = 9 gleich der
Quersumme Q(z) = an + · · · + a1 + a0 . Beispielsweise gilt
2015 ⌘ 2 + 0 + 1 + 5 ⌘ 8
⌘2
mod 9 (bzw.
mod 3)
mod 3.
2. Alternierende Quersummen-Regel für die Teilbarkeit durch 11. Ähnlich wie oben gilt
10 ⌘
1
mod m,
102 ⌘ ( 1)2
mod m,
23
...,
10k ⌘ ( 1)k
mod m
für alle k 2 N.
Damit folgt für eine n-stelligen Zahl z 2 Z zur Basis 10
z = a0 +
n
X
k=1
ak 10k ⌘
n
X
( 1)k ak .
k=0
Damit ist der Rest von z = (an · · · a1 a0 )10 bei Division durch m = 11 gleich der alternierenden
Quersumme Q(z) = a0 a1 + a2 ± · · · ± ( 1)n an .
3. Vermutung von Fermat: Fermat vermutete, dass die Glieder der Folge, auch Fermat-Zahlen gen
nannt, Fn := 22 + 1 für alle n 2 N0 prim sind. In der Tat sind die ersten fünf Zahlen prim:
F0 = 21 + 1 = 3,
F1 = 22 + 1 = 5,
F3 = 28 + 1 = 257,
F2 = 24 + 1 = 17,
F4 = 216 + 1 = 65537.
Euler fand dann allerdings heraus, dass F5 = 232 + 1 durch 641 teilbar ist. Sein Beweis beruht
auf Kongruenzen modulo 641: Zunächst gilt
641 = 1 + 64 · 10 = 1 + 27 · 5
4
641 = 625 + 16 = 5 + 2
=)
4
=)
5 · 27 ⌘
4
5 ⌘
2
mod 641 und
1
4
(A)
(B)
mod 641
Damit folgt
(B)
F5 = 232 + 1 = 24 228 + 1 ⌘ ( 5)4 27·4 + 1
⌘
(5 · 27 )4 + 1
(A)
mod 641
( 1)4 + 1
mod 641 ⌘
mod 641 ⌘ 0
mod 641,
und folglich ist F5 durch 641 teilbar.
Versuchen Sie mal, die nächsten Folgeglieder Fn , n 6, auf mögliche Teiler hin zu überprüfen.
Mit unsigned long-Zahlen lassen sich immerhin Zahlen bis 264 1 darstellen. Aber das reicht
schon nicht mehr aus, um beispielsweise die Zahl F6 = 264 + 1 mit unserem Programm min_prim_teiler.c zu analysieren.
Die Kongruenzgleichung ax ⌘ b mod m
Wir wenden uns nun der Frage zu, unter welchen Bedingungen an a und b die Division a/b in Zm
möglich ist. Wir fragen uns also, wann ein x 2 Z existiert mit
ax ⌘ b
mod m
(G)
Sei x 2 Z eine Lösung von (G). Dann gilt ax b ⌘ 0 mod m. Folglich existiert eine Zahl k 2 Z mit
ax b = km.
Sei nun g der größte gemeinsame
✓ Teiler von
◆ a und m, g := ggT(a, m). Dann sind sowohl a/g als auch
a
m
m/g ganze Zahlen. Mit b = g
x
k folgt g | b. Ein notwendige Bedingung für die Lösbarkeit
g
g
|
{z
}
2Z
von (G) ist also, dass g die Zahl b teilt.
2.2.8.
Satz (Lösbarkeit der Kongruenzgleichung ax ⌘ b mod m)
Seien a, b 2 Z. Dann ist die Kongruenzgleichung (G) auflösbar, genau dann wenn der größte gemeinsame
Teiler von a und m die Zahl b teilt,
(G) auflösbar
()
24
ggT(a, m) | b.
In diesem Fall gibt es g = ggT(a, m) verschiedene Lösungen.
Beweis (ist algorithmisch). Aufgrund der Überlegungen zur notwendigen Bedingungen reicht es
(= zu zeigen. Gelte also g = ggT(a, m) | b. Der größte gemeinsame Teiler lässt sich mittels der
Bézout-Koeffizienten r, s 2 Z schreiben als
g = sa + rm.
Aus g | b folgt die Existenz der ganzen Zahl l := b/g 2 Z. Damit gilt
b = l g = l(sa + rm) = (ls) a + (lr) m.
Folglich gilt b ⌘ (ls) a mod m und x0 := ls ist eine Lösung von (G).
Als nächstes wollen wir noch die g = ggT(a, m) mod m verschiedenen Lösungen finden. Aus a(x
x0 ) ⌘ 0 mod m folgt a(x x0 ) = k m für ein k 2 Z. Division durch g ergibt
a
(x
g
|{z}
x0 ) = k
=:a0 2N
Mit m0 | a0 (x
sind die
m
g
|{z}
mit ggT(a0 , m0 ) = 1.
=:m0 2N
x0 ) und Folgerung 2.1.8 folgt m0 |(x
x = x0 + n
m
g
x0 ) und damit x
mit n = 0, 1, . . . , g
x0 = nm0 für ein n 2 N. Damit
1
weitere Lösungen von (G).
Der Fall b = 1:
Im Fall b = 1 besteht die Aufgabe darin ein x 2 Z zu finden, sodass
ax ⌘ 1
mod m.
(G1 )
Falls die Gleichung (G1 ) ein Lösung hat sagen wir, dass a ein multiplikatives Inverses x hat.
2.2.9.
Definition (Einheit)
Ein Element a 2 Zm heißt eine Einheit, falls ein Element x 2 Zm existiert mit a x = 1.
Bemerkungen und Sprechweisen
1. Nach Satz 2.2.8 hat die Gleichung
az = 1
eine Lösung, genau dann wenn ggT(a, m) | 1, das heißt genau dann wenn ggT(a, m) = 1. Die
einzige Lösung ist dann
x0 =
sb
= s,
g
wobei s der Bézout-Koeffizient ist mit g = 1 = s a + r m.
2. Wenn ein Element a 2 Zm eine Einheit ist, dann existiert ein inverses Element x 2 Zm mit
a z = 1. Man schreibt dann x = a 1 wobei x ⌘ s mod m.
25
3. Die Menge der Einheiten
ZE
m := {a 2 Zm | ggT(a, m) = 1}
ist eine Gruppe bezüglich der Multiplikation. Wir nennen sie die multiplikative Einheitsgruppe in
Zm .
2.2.10.
Folgerung
Der Restklassenring Zm ist ein Körper genau dann wenn ZE
m = Zm \{0} genau dann wenn m
Primzahl ist. Für Primzahlen m = p 2 N heißt Zp der Restklassenkörper modulo p.
2 eine
Beweis. Übung.
2.2.11.
Implementierung der Einheiten- und Inversenbestimmung
Auf der Webseite zur Vorlesung finden Sie das Programm Einheiten_in_Z_mod_mZ.c. Wir wollen kurz
seine Funktionalität und sein Vorgehen erläutern.
Funktionalität: Das Programm prüft, ob ein einzugebendes Element a 2 {1, . . . , m 1} eine Einheit
im Restklassenring Zm ist. Dabei wird m ebenfalls vom Nutzer eingegeben. Falls a eine Einheit
in Zm ist, wird der Repräsentant x 2 {0, 1, . . . , m 1} der Inversen a 1 2 Zm bestimmt.
Vorgehen: Das Programm bestimmt zuerst den größten gemeinsamen Teiler von a und m, g =
ggT(a, m), und den Bézout-Koeffizienten s 2 Z mit g = s a + r m. In diesem Fall ist
x⌘s
mod m
die einzige Lösung von a x = 1. Der Repräsentant x ist damit der Rest von s bei Division durch
m, in C-Sprache heißt dass x=s%m; .
2.2.12.
Beispiel
Wir fragen uns, ob das Element 37 eine Einheit im Restklassenring Z91 ist? Dazu prüfen wir, ob der
größte gemeinsame Teiler von 37 und 91 eins ist: Wegen ggT(37, 91) = 1 ist 37 also eine Einheit. Das
1
Inverse bestimmen wir nun über den Bézout-Koeffizienten s. Da 1 = 32 37 + ( 13) 91 gilt 37 = 32.
In der Tat gilt 37 · 32 = 1184 ⌘ 1 mod 91.
2.2.13.
Direktes Produkt von Restklassenringen
Motivation. Für sehr großes m ist es für die Bestimmung von Einheiten und Inversen effizienter über
die Primfaktorzerlegung von m
m = pv11 · · · pvkk ,
vj
1, 2  p1 < . . . < pk
v
zu gehen und separat modulo mj := pj j zu untersuchen.
Definition. Seien m1 , . . . , mk 2 N\{1} gegeben und m := m1 · · · mk . Zu einer Zahl a 2 Z bezeichnet
[a]mj 2 Zmj , j = 1, . . . , k die zu a gehörende Restklasse modulo mj (früher a für festes m). Unter
dem direkten Produkt der Restklassenringe Zm1 , . . . , Zmk versteht man die Menge aller k-Tupel
Zm1 ⇥ · · · ⇥ Zmk := {([a1 ]m1 , . . . , [ak ]mk ) | aj 2 Z, j = 1, . . . , k}
mit der Addition
([a1 ]m1 , . . . , [ak ]mk ) + ([b1 ]m1 , . . . , [bk ]mk ) := ([a1 + b1 ]m1 , . . . , [ak + bk ]mk )
26
und der Multiplikation
([a1 ]m1 , . . . , [ak ]mk ) · ([b1 ]m1 , . . . , [bk ]mk ) := ([a1 · b1 ]m1 , . . . , [ak · bk ]mk ).
Es gelten analog die Eigenschaften (1)-(8) von Satz 2.2.5 mit
Null-Element ([0]m1 , . . . , [0]mk )
Eins-Element ([1]m1 , . . . , [1]mk ).
Bemerkungen. 1. Man zeigt leicht, dass ([a1 ]m1 , . . . , [ak ]mk ) genau dann eine Einheit in Zm1 ⇥
· · · ⇥ Zmk ist, wenn [aj ]mj eine Einheit in Zmj ist für alle j = 1, . . . , k (Übungsaufgabe).
2. Ebenso ist leicht zu zeigen, dass eine Einheit a = ([a1 ]m1 , . . . , [ak ]mk ) in Zm1 ⇥ · · · ⇥ Zmk
nur ein einziges inverses Element a 1 = x = ([x1 ]m1 , . . . , [xk ]mk ) hat (Übungsaufgabe).
Wir betrachten als nächstes für m = m1 · · · mk die Abbildung
: Zm ! Zm1 ⇥ · · · ⇥ Zmk
([a]m ) := ([a]m1 , . . . , [a]mk ) 8[a]m 2 Zm .
Das Bild dieser Abbildung nennen wir den Vektor der Restklassen von a modulo mj , j = 1, . . . , k.
Das folgende Lemma zeigt, dass die Abbildung wohldefiniert ist.
Lemma (Wohldefiniertheit von
Die Abbildung
).
mit m = m1 · · · mk hat folgende Eigenschaften:
1. Ein anderer Repräsentant b 2 [a]m hat dasselbe Bild, d.h.
([b]m ) =
([a]m ).
2. Die Abbildung : Zm ! Zm1 ⇥ · · · ⇥ Zmk ist ein so genannter Ringhomomorphismus, d.h. es
gelten die Eigenschaften
(a)
([a]m + [b]m ) =
([a]m ) + ([b]m )
(b)
([a]m · [b]m ) =
([a]m ) ·
([b]m )
Beweis. Zu 1.: Sei b 2 [a]m ein Element der zu a gehörenden Restklasse in Zm . Dann haben nach
Definition a und b bei der Division durch m den gleichen Rest. Folglich gilt auch b ⌘ a mod m, und
damit ist (b a) ein Vielfaches von m, b a = lm = l(m1 · · · mk ) für ein l 2 Z.
Ferner gelten mj | b a, b a ⌘ 0 mod mj und b ⌘ a mod mj für alle j = 1, . . . , k. Laut Definition
folgt nun die Behauptung, [b]mj = [a]mj für alle j = 1, . . . , k.
Zu 2.: Wir beweisen Teil (a). Aufgrund von Satz 2.2.5 und der Definition von
([a]m + [b]m )
2.2.5
=
2.2.5
=
def
=
gilt
def
([a + b]m ) = ([a + b]m1 , . . . , [a + b]mk )
([a]m1 + [b]m1 , . . . , [a]mk + [b]mk )
def
([a]m1 , . . . , [a]mk ) + ([b]m1 , . . . , [b]mk ) =
([a]m ) + ([b]m ).
Ganz analog beweist man Teil (b),
([a]m · [b]m ) =
([a]m ) · ([b]m ).
Unter geeigneten Bedingungen an m1 , . . . , mk ist sogar ein Ringisomorphismus, d.h.
Wann das der Fall ist diskutieren wir im folgenden Satz.
27
ist bijektiv.
2.2.14.
Satz (Chinesischer Restsatz)
Seien mj 2 N\{1}, j = 1, . . . , k paarweise teilerfremd, das heißt es gelte ggT(mi , mj ) = 1 für alle
1  i < j  k. Ferner sei m = m1 · · · mk .
Dann ist der Ringhomomorphismus : Zm ! Zm1 ⇥ · · · ⇥ Zmk , definiert durch
([a]m ) := ([a]m1 , . . . , [amk ),
bijektiv, das heißt er ist ein so genannter Ringisomorphismus.
Beweis. Wir beweisen zunächst, dass
injektiv ist: Sei
gezeigt, sobald wir [a]m = [b]m zeigen können. Also los!)
Aus ([a]m ) = ([b]m ) folgt nach Definition
([a]m ) =
([b]m ). (Die Injektivität ist
([a]m1 , . . . , [a]mk ) = ([b]m1 , . . . , [b]mk ),
also [a]mj = [b]mj , a ⌘ b mod mj und
mj | (b
a) für alle j = 1, . . . , k.
(1)
Aus (1) mit j = 1 folgt die Existenz eines l1 2 Z mit
b
(⇤)
a = l 1 m1 .
Aus (1) mit j = 2 und (⇤) folgt m2 | l1 m1 . Da ggT(m1 , m2 ) = 1 und aufgrund von Folgerung 2.1.8
folgt m2 | l1 . Damit existiert ein l2 mit l1 = l2 m2 . Damit gilt
Da (b
l2 2 Z
b
a = l2 (m2 m1 ),
..
.
b
a = lk (mk · · · m2 m1 ) = lk m,
lk 2 Z.
a) nun ein Vielfaches von m ist, gilt [a]m = [b]m .
Als nächstes müssen wir noch zeigen, dass
auch surjektiv ist. Dabei ist
surjektiv genau dann wenn
(Zm ) = Zm1 ⇥ · · · ⇥ Zmk .
(2)
(Zm ) ⇢ Zm1 ⇥ · · · ⇥ Zmk .
(3)
Nach Definition gilt
Da wie oben gezeigt injektiv ist, gilt # (Zm ) = #Zm . Mit (3) reicht es damit zu zeigen, dass die
Anzahl der Elemente in den Mengen Zm1 ⇥ · · · ⇥ Zmk und Zm gleich sind. Zum einen gilt
80
9
1
>
>
<
=
B
C
#(Zm1 ⇥ · · · ⇥ Zmk ) = # @
[a1 ]m1
,...,
[ak ]mk
A | aj 2 Z, j = 1, . . . , k
| {z }
>
>
| {z }
:
;
m1 Möglichkeiten
mk Möglichkeiten
= m1 · · · mk = m.
Ferner gilt
Zm = #{[a]m | a 2 Z} = #{0, 1, . . . , m
28
1} = m.
Anwendungsbeispiel für den Chinesischen Restsatz. Frau Sun hat in diesem Jahr einen runden Geburtstag gefeiert; gleichzeitig hat sie auch ein volles Jahrsiebt vollendet. Wie alt ist Frau Sun
geworden? Die (eindeutige!) Antwort – 70 Jahre – ist nicht schwer zu erraten.
Herr Li dagegen hat das letzte volle Jahrsiebt vor 2 Jahren vollendet; sein letzter runder Geburtstag
liegt bereits 8 Jahre zurück. Wie alt ist Herr Li? Interessant ist, dass tatsächlich auch das Alter x von
Herrn Li durch diese beiden Angaben eindeutig festliegt (jedenfalls wenn man von einem realistischen
Alter eines Menschen ausgeht):
Die Zahl x ergibt bei ganzzahliger Division durch 7 den Rest 2 und bei ganzzahliger Division durch 10
den Rest 8. Die Zahl x lässt sich also darstellen als
x = s · 7 + 2 = t · 10 + 8.
Anders ausgedrückt gilt
x⌘2
mod 7 und x ⌘ 8
mod 10
Der Chinesische Restsatz sagt nun aus, dass wenn die Moduln 7 und 10 teilerfremd sind, es eine
eindeutige Lösung x Modulo 7 · 10 = 70 gibt. Herr Li ist also x = 58 Jahre alt (oder, aber eher
unwahrscheinlich x0 = 58 + 70 = 128 Jahre).
Implementierung des Chinesischen Restsatzes. Wir fragen uns nun, wie man unter den Voraussetzungen des Chinesischen Restsatzes 2.2.14 den Ringisomorphismus implementiert. Für ([a]m ) :=
([a]m1 , . . . , [a]mk ) liegt das auf der Hand; man muss nur einen Repräsentanten für [a]mj in {1, . . . , mj
1} finden. Aber das ist nicht so schwer. Schwieriger ist es indes
1
: (Zm1 ⇥ · · · ⇥ Zmk ) ! Zm ?
zu implementieren.
2.2.15.
Bemerkung (Effektive Bestimmung eines Urbildes)
Angenommen es gelten die Voraussetzungen des Chinesischen Restsatzes 2.2.14. Unsere Aufgabe besteht darin zu einem gegebenen w = ([w1 ]m1 , . . . , [wk ]mk ) das Urbild zu bestimmen, das heißt einen
Repräsentanten a 2 {0, 1, . . . , m 1} mit
([a]m ) = w
bzw. [a]m =
1
(w)
Die primitive Variante besteht darin alle a 2 Zm auszuprobieren.
f o r ( a =0; a<m; a++)
{
b e r e c h n e den B i l d v e k t o r b =([ a ]_{m_1 } , . . . , [ a ]_{m_k} )
v e r g l e i c h e b mit w : w=b?
wenn j a , dann break ;
}
Damit hätten wir allerdings einen Operationsaufwand von O(k · m).
Eine effektive Variante besteht in den folgenden Schritten:
1. Es soll das Modul-Gleichungssystem gelöst werden
Finde a 2 Z so dass:
a ⌘ wj
mod mj
für alle j = 1, . . . , k
(1)
2. Die Idee zur Lösung dieses Modul-Gleichungssystems besteht darin eine Basis a1 , . . . , ak 2 Z zu
bestimmen mit der Eigenschaft
ai ⌘
ij
mod mj
29
für alle j = 1, . . . , k
(Gi )
wobei
ij
:=
(
1 i=j
0 sonst
das Kronecker-Symbol ist.
3. Hat man nämlich so eine Basis bestimmt, ist die Linearkombination
a :=
k
X
2Z
w i ai
i=1
ein Repräsentant des Urbildes, also [a]m =
1 (w).
4. Wir kommen nur zur Lösung von (Gi ). Es gilt
(
ai ⌘ 0 mod mj 8i =
6 j
(Gi ) ()
ai ⌘ 1 mod mi
(Vgl. Übungsaufgabe)
()
(
mj | ai 8j 6= i
ai = kmi + 1 , k 2 Z
(2)
(3)
Da die mj paarweise teilerfremd sind, ist
m1 · · · mk
| ai
mi
m
() mit qi :=
gilt ai = lqi , l 2 Z
mi
(2) ()
(4)
Also
(Gi ) () (2) und (3) () (3) und (4)
() ai = lqi = kmi + 1,
(5)
l, k 2 Z
(6)
() 9k, l 2 Z : 1 = ( k)mi + lqi .
Da mi und qi = m/mi teilerfremd sind, gibt es Bézout-Koeffizienten r, s 2 Z mit
ggT(mi , qi ) = 1 = rmi + sqi .
Also löst k =
r, l = s die Gleichung (6) und mit (3) ist
ai = 1
rmi
eine Lösung von (Gi ).
5. Als letztes muss man nur noch die Linearkombination a =
präsentant a 2 {0, 1, . . . , m 1} bestimmen.
2.2.16.
Implementierung der effektiven Variante
1. Für alle i = 1, . . . , k führe aus:
• bestimme qi = m/mi
• bestimme Bézout-Koeffizienten r in 1 = rmi + sqi
• berechne ai = 1
rmi
2. berechne Linearkombination a =
Pk
i=1 wi ai
3. bestimme Repräsentanten von a: a 2 {0, 1, . . . , m
30
1}
Pk
i=1 wi ai
bilden und einen Re-
2.3.
Arithmetik für beliebig lange natürliche Zahlen, verkettete Listen
In den Übungen kam schon öfter die Frage nach Programmen, die mit beliebig langen natürlichen
Zahlen arbeiten können. Dies ist natürlich möglich. Dazu wollen wir eine natürliche Zahl z 2 N zur
Basis 10 als Feld oder Liste der Ziffern ak darstellen,
z = (an an
1 · · · a0 )10 =
n
X
ak 10k ,
k=0
mit ak 2 {0, 1, . . . , 9} für alle k = 0, . . . , n und an 6= 0. Dabei wird jede Ziffer ak als char-Integervariable
gespeichert werden.
Das Problem dabei ist, dass man oft den Wert von n (also die Anzahl der Ziffern der Zahl z) nicht
kennt und deshalb den Speicherplatz für z erst zur Laufzeit – das heißt dynamisch – anfordern möchte.
Weiterhin verändern Addition, Subtraktion und Multiplikation die Ziffernfolge des Resultats.
2.3.1.
Dynamische Felder
Bisher haben wir statische Felder deklariert durch typ feldname[anzahl];
Zum Beispiel reserviert der Befehl
char z i f f e r n [ 1 0 ] ;
Speicher für die char-Variablen ziffern[0],...,ziffern[9]. Im Funktionsaufruf
z=wert_b10 ( z i f f e r n , n ) ;
ist ziffern nichts anderes als ein Pointer auf das erste Element ziffern[0].
Zur Speicherplatzverwaltung in C benötigen wir C-Standardfunktionen aus <stdlib.h>. Diese sind:
malloc
calloc
realloc
free
memory allocation (Zuteilung, Bereitstellung von Speicher)
cleaned allocation
re-allocation (Wieder-Zuteilung von Speicher)
free (Freigabe von Speicherplatz)
Malloc. Der Funktionskopf
void ⇤ m a l l o c ( s i z e _ t n ) ;
liefert einen Zeiger auf uninitialisierten Speicher von n zusammenhängenden Bytes zurück; wenn Speicher nicht verfügbar gibt er 0 =NULL zurück. Dabei ist zu beachten, dass der Zeiger zunächst den Typ
void hat und anschließend noch in den gewünschten Typ umgewandelt werden muss. Das geschieht
mittels des so genannten cast-Operators.
char ⇤ z i f f e r n ;
int n ;
...
z i f f e r n =(char ⇤ ) m a l l o c ( n⇤ s i z e o f ( char ) ) ;
wobei char* das casting auf den Typ char vornimmt und sizeof(char) die Anzahl der Bytes für
char-Variablen beschreibt.
Calloc. Der Funktionskopf
void ⇤ c a l l o c ( s i z e _ t n , s i z e _ t b ) ;
liefert einen Zeiger auf ein Feld von n Objekten mit jeweils b Bytes Speicher zurück, jedes Feld wird
mit 0 initialisiert; wenn Speicher nicht verfügbar gibt er 0 =NULL zurück. Ein Beispiel ist
double ⇤ z a h l e n ;
int n ;
...
z a h l e n =(double ⇤ ) c a l l o c ( n , s i z e o f ( double ) ) ;
31
Free. Der Funktionskopf
void f r e e ( void ⇤ p t r ) ;
gibt den Speicher, auf den der Zeiger ptr zeigt, wieder frei. Dabei muss der Zeiger prt mit malloc,
calloc oder realloc erzeugt worden sein. Ein Beispiel ist
f e l d =( i n t ⇤ ) c a l l o c ( n , s i z e o f ( i n t ) ) ;
...
free ( feld );
Realloc. Für den Funktionskopf
void ⇤ r e a l l o c ( void ⇤ ptr , s i z e _ t n ) ;
unterscheiden wir zwei Fälle:
Fall 1: Falls prt==NULL gilt, dann verhält sich realloc genauso wie malloc.
Fall 2: Falls der Zeiger ptr durch malloc oder calloc erzeugt wurde, dann wird
• ein neues Feld der Länge n erzeugt
• die Daten des alten Feldes werden in das neue Feld kopiert (beachte: nicht alle, wenn n<n_alt)
• das alte Feld wird freigegeben
Auf der Webseite zur Vorlesung finden Sie die Programme Einlesen_strings und Einlesen_lange_strings.c. In beiden Programmen wird die für die dynamische Speicherverwaltung wichtige C-Standardbibliothek <stdlib.h> verwendet.
Einlesen_strings.c: Dieses Programm fragt den Nutzer zuerst, welche maximallaenge sein String
haben soll. Mit dem Funktionsaufruf
dynfeld=(char*)(malloc((maximallaenge+1)*sizeof(char));
und nach der Prüfung ob genügend Speicherplatz vorhanden ist,
if(!dynfeld){...},
wird der Speicher für das String-Feld dynfeld reserviert. Anschließend kann ein String der Länge
`  maximallaenge eingegeben werden.
Einlesen_lange_strings.c: Dieses Programm kann einen beliebig langen String einlesen und bearbeiten. Beliebig lange heißt dabei: so lang, wie ein zusammenhängender Speicherblock verfügbar ist.
Dazu werden zwei String-Felder initialisiert; zwischenfeld und dynfeld. In einer while-Schleife
wird das Feld zwischenfeld mittels des Funktionsaufrufs
zwischenfeld=(char*)(realloc(dynfeld,(laufindex+1)*sizeof(char)));
solange um ein Element erweitert, bis die return-Taste gedrückt wird. Anschließend wird durch
if(!zwischenfeld){...} geprüft, ob der nötige Speicherplatz vorhanden ist. Falls ja, wird
dynfeld=zwischenfeld; gesetzt.
2.3.2.
Doppelt verkettete Listen
Es kann passieren, dass es keinen zusammenhängenden Speicherblock der Länge n + 1 für die Ziffern
zk der Zahl
z = (zn zn
1 · · · z1 z0 )10 =
n
X
k=0
zk · 10k
mehr gibt. In diesen Fällen können die Funktionen malloc, calloc und realloc keinen Speicher
zuweisen. Daher ist es am besten diese Fälle abzufangen, zum Beispiel so wie im Programm Einlesen_lange_strings.c:
32
i f ( ! z w i s c h e n f e l d ) // A e q u i v a l e n t zu z w i s c h e n f e l d=NULL
{
p r i n t f ( " S p e i c h e r p l a t z kann n i c h t b e r e i t g e s t e l l t werden ! Programmabbruch ! \ n\n" ) ;
f r e e ( dynfeld ) ;
return ( 1 ) ;
}
Dieses Beispiel entspricht der Speicher-Situation, in der es nur noch freie Speicherlücken der Länge
< n + 1 gibt:
belegt
frei
belegt
frei
belegt
frei
Ein Ausweg ist, dass man die freien Speicherlücken verwendet, wie in den Programmen Liste_doppelt_verkettet.c und Addition_lang.c. Um deren Funktionsweise zu verstehen, müssen wir
uns zunächst mit Strukturen in C vertraut machen.
Datenstrukturen in C
Eine Datenstruktur ist ein Objekt, in dem eine Menge von anderen Objekten (Elementen) des jeweils
gleichen Typs gespeichert werden kann, und die bestimmte Operationen (z.B. 1. Liste initialisieren,
2. Liste verlängern, 3. Liste anzeigen, 4. Liste löschen) zur Verfügung stellt. Die vielleicht einfachste
Datenstruktur ist ein Array.
In diesem Abschnitt wollen wir kennen lernen, wie man sich selbst neue Datentypen aus bekannten
Datentypen definiert. Die abstrakte Syntax dazu lautet:
struct struct_name
{
typ 1 var_name1 ;
typ 2 var_name2 ;
...
};
Als Beispiel deklarieren wir den Datentyp datum:
struct datum
{
int tag ;
char monat [ 1 0 ] ; //Wort mit max . 10 Buchstaben
int j a h r ;
};
Als nächstes wollen wir eine Variable vom Typ struct erzeugen. Das geht zum Beispiel so
struct datum E i n s t e i n s _ g e b ={14 , " Maerz " , 1 8 7 9 } ;
Anschließend können wir den Geburstag von Albert Einstein ausgeben mit Hilfe des Befehls
p r i n t f ( " E i n s t e i n s G e b u r t s t a g i s t d e r : %i . %s %i \n" , \
E i n s t e i n s _ g e b . tag , E i n s t e i n s _ g e b . monat , E i n s t e i n s _ g e b . j a h r ) ;
Ähnlich wie bei den Standardtypen, kann man auch Zeiger auf struct-Variablen deklarieren. Das geht
zum Beispiel so
#include <s t d i o . h>
#include < s t d l i b . h>
i n t main ( void )
{
struct datum
{...};
struct datum E i n s t e i n s _ g e b ={14 , "Marz" , 2 0 1 5 } ;
33
struct datum ⇤ z e i g e r=&E i n s t e i n s _ g e b ;
// Damit z e i g t " z e i g e r " a u f d i e s t r u c t
Variable " Einsteins_geb "
p r i n t f ( " E i n s t e i n s G e b u r t s t a g i s t d e r : %i . %s %i \n" , \
E i n s t e i n s _ g e b . tag , E i n s t e i n s _ g e b . monat , E i n s t e i n s _ g e b . j a h r ) ;
z e i g e r >t a g =15;
// Der G e b u r t s t a g w i r d g e a e n d e r t .
p r i n t f ( " E i n s t e i n s G e b u r t s t a g i s t d e r : %i . %s %i \n" , \
E i n s t e i n s _ g e b . tag , E i n s t e i n s _ g e b . monat , E i n s t e i n s _ g e b . j a h r ) ;
}
return 0 ;
Algorithmische Bausteine zum Arbeiten mit Listen für lange Zahlen
In den Programmen Liste_doppelt_verkettet.c und Addition_lang.c werden so genannte doppelt
verkette Listen erzeugt. Diese kann man sich folgendermaßen veranschaulichen:
Abbildung 2.1: Der Kopf steht für den Anfang der Liste. Der Anfangszeiger zeigt auf den ersten Knoten.
Der Endzeiger auf den letzten Knoten der Liste. Jeder Knoten zeigt auf seinen Vorgängerknoten und
seinen Nachfolgerknoten. Zudem ist in ihm eine Information gespeichert. Der erste und letzte Knoten
zeigt natürlich nicht auf seinen Vorgänger bzw. Nachfolger, sondern auf NULL.
Bei einer Liste können die einzelnen Elemente an beliebigen voneinander unabhängigen Stellen im
Speicher stehen. Zu jedem Element merkt man sich einen Zeiger auf die Speicherstelle des vorherigen
Elementes und des nächsten Elementes. Beim ersten Element einer Liste ist der Vorgänger der NULLZeiger, beim letzten Element ist der Nachfolger der NULL-Zeiger. Zusätzlich braucht man noch einen
Zeiger auf das erste Element einer Liste.
Listen haben den Nachteil, dass das Durchlaufen langsamer ist als bei einem Array, da der Zugriff auf
aufeinanderfolgende Speicherbereiche wesentlich schneller geht als bei weit auseinander liegenden.
Wir kommen nun zur Implementierung von doppelt verketteten Listen in C. Dazu definieren wir die
struct-Typen Listenkopf und Knoten:
struct L i s t e n k o p f
{
char z i f f e r : // I n f o d e s Knotes
char Knoten ⇤ an fang :
// i n d e r G r a f i k d a r g e s t e l l t durch B" f u e r b e g i n
char Knoten ⇤ ende :
// i n d e r G r a f i k d a r g e s t e l l t durch "E" f u e r end
};
sowie
struct Knoten // Element i n d o p p e l t v e r k e t t e t e n L i s t e n
{
char Z i f f e r ; // Datenelement
34
struct Knoten ⇤ N a c h f o l g e r ; // P o i n t e r a u f Knoten V a r i a b l e
struct Knoten ⇤ Vorgaenger ; // P o i n t e r a u f Knoten V a r i a b l e
};
Wir kommen nun zur Illustration, wie man eine doppelt verkettete Liste anlegen und anzeigen kann.
Dazu hangeln wir uns an den folgenden vier Kernfunktionen aus dem Programm Liste_doppelt_verkettet.c entlang. Insbesondere werden Sie sich mit den Umgang mit Zeigern auf Variablen von
Typ struc Listenkopf und struc Knoten vertraut machen müssen:
1. Initialisierung der Liste: Zunächst muss der Listenkopf alloziert werden. Der Rückgabewert der
dazu benötigte Funktion ist ein Zeiger auf eine Variable vom Typ struct Listenkopf:
struct L i s t e n k o p f ⇤ L i s t e a n l e g e n ( void )
{
struct L i s t e n k o p f ⇤ k o p f =( struct L i s t e n k o p f ⇤ ) ( m a l l o c ( s i z e o f ( struct L i s t e n k o p f ) ) ) ;
// Der Z e i g e r " k o p f " w i r d d e k l a r i e r t und durch " m a l l o c " w i r d
// d e r S p e i c h e r b e r e i t g e s t e l l t . Wir b e n o e t i g e n h i e r den " c a s t "
// Operator f u e r d i e n s t u c t Typ " L i s t e n k o p f "
i f ( k o p f ) // k u r z f u e r i f ( k o p f ! = 0 ) ; Wahr , f a l l s genuegend S p e i c h e r p l a t z vorhanden i s t .
{
kopf >a n f a n g =0; // A n f a n g s z e i g e r w i r d a u f N u l l g e s e t z t
kopf >ende =0;
// E n d z e i g e r w i r d a u f N u l l g e s e t z t
return ( k o p f ) ;
}
}
return ( 0 ) ;
2. Einfügen neuer Knoten in eine Liste: An bestehende Listen aus Variablen vom Typ struct Knoten
soll jeweils hinten ein neuer Knoten angehängt werden. An die Funktion Anhaengen wird ein Zeiger auf einen Listenkopf sowie eine char-Variable übergeben.
i n t Anhaengen ( struct L i s t e n k o p f ⇤ kopf , char z i f f )
{
struct Knoten ⇤ haengean=( struct Knoten ⇤ ) ( m a l l o c ( s i z e o f ( struct Knoten ) ) ) ;
// Z e i g e r a u f e i n e n Knoten mit e r f o r d e r l i c h e n S p e i c h e r
i f ( haengean ) //Nur b e i e r f o l g r e i c h e r S p e i c h e r a l l o k i e r u n g
{
haengean >Z i f f e r= z i f f ; // h i e r w i r d d i e u e b e r g e b e n e Z i f f e r r e i n g e s c h r i e b e n
haengean >N a c h f o l g e r =0; // d e r Knoten w i r d h i n t e n dran g e h a e n g t ,
//
> N a c h f o l g e r d e s neuen Knotens z e i g t a u f NULL
haengean >Vorgaenger=kopf >ende ; // V o r g a e n g e r k n o t e n d e s neuen
// Knotens , w i r d d e r b i s l a n g l e t z t e Knoten
}
}
i f ( ! ( kopf >a n f a n g ) ) // F a l l s d i e u e b e r g e b e n e L i s t e l e e r war
{
kopf >a n f a n g=haengean ; // d e r neue Knoten w i r d A nfan gskn oten
kopf >ende=haengean ;
// d e r neue Knoten w i r d Endknoten
}
e l s e // F a l l s d i e u e b e r g e b e n e L i s t e n i c h t l e e r war ,
{
kopf >ende >N a c h f o l g e r=haengean ; // d e r N a c h f o l g e k n o t e n
// d e s a k t u e l l l e t z t e n Knotens w i r d a u f den neue Knoten g e s e t z t
kopf >ende=haengean ; // d e r Ende Z e i g e r w i r d a u f den
// neuen Knoten g e s e t z t .
}
return ( 1 ) ;
return ( 0 ) ;
35
3. Anzeigen der Liste: An die Funktion VorwaertsAnzeigen wird ein Zeiger auf eine Variable vom
Typ struct Listenkopf übergeben.
void V orwae rtsA nze igen ( struct L i s t e n k o p f ⇤ k o p f )
{
struct Knoten ⇤ a k t u e l l=kopf >a n f a n g ; // Z e i g e r a u f L i s t e n k o p f
if (! aktuell )
{
}
// F a l l s d e r N u l l z e i g e r u e b e r g e b e n wurde , w i r d e i n e
// e n t s p r e c h e n d e Meldung a u s g e g e b e n .
p r i n t f ( " S i e h a t t e n k e i n e Zahl e i n g e g e b e n . \ n" ) ;
return ;
p r i n t f ( " I h r e e i n g e g e b e n e Zahl : \ n" ) ;
while ( a k t u e l l ) // S o l a n g e " a k t u e l l " a u f e i n e n Knoten z e i g t , w i r d
// d e s s e n Z i f f e r a u s g e g e b e n .
{
}
}
p r i n t f ( "%c " , a k t u e l l >Z i f f e r ) ;
a k t u e l l=a k t u e l l >N a c h f o l g e r ; // " a k t u e l l " a u f s e i n e n N a c h f o l g e k n o t e n g e s e t z t
p r i n t f ( " \n" ) ;
return ;
Die Funktion RueckwaertsAnzeigen funktioniert analog, mit
{
}
p r i n t f ( "%c " , a k t u e l l >Z i f f e r ) ;
a k t u e l l=a k t u e l l >Vorgaenger ; // " a k t u e l l " a u f s e i n e n V o r g a e n g e r k n o t e n g e s e t z t
4. Löschen einer Liste: An die Funktion Loeschen wird ebenfalls ein Zeiger auf eine Variable vom
Typ struct Listenkopf übergeben.
void Loeschen ( struct L i s t e n k o p f ⇤ k o p f )
{
struct Knoten ⇤ a k t u e l l=kopf >a n f a n g ; // " a k t u e l l " w i r d a u f den
// A n f a n g s z e i g e r d e r u e b e r g e b e n e n L i s t e g e s e t z t
struct Knoten ⇤ n a c h f ;
while ( a k t u e l l ) // S o l a n g e d i e L i s t e Knoten e n t h a e l t
{
n a c h f=a k t u e l l >N a c h f o l g e r ; // S i c h e r n d e r A d r e s s e d e s N a c h f o l g e r s
f r e e ( a k t u e l l ) ; // d e r a k u e l l e Knoten w i r d g e l o e s c h t
a k t u e l l=n a c h f ; // N a c h f o l g e r w i r d nun d e r " a k t u e l l e " Knoten
}
}
f r e e ( k o p f ) ; // Loeschen den L i s t e n k o p f s
return ;
36
Kapitel 3
Erzeugung von Zufallszahlen
Viele Prozesse in den Naturwissenschaften, der Technik und der Finanzwelt sind so komplex, dass man
bei ihrer mathematischen Modellierung Zufallselemente hinzufügen muss (Erzeugung von Schlüsselzahlen
in der Kryptographie, Simulation von Abläufen in der realen Welt wie z.B. Ampelschaltungen, wo
taucht der Bösewicht in einem Computerspiel auf, etc.).
In solchen Situationen sucht man nach der Lösung indem man sozusagen eine Näherungslösung auswürfelt.
Das zugehörige Themengebiet ist sehr umfangreich und von den mathematischen Begriffen her teils
sehr anspruchsvoll, sodass wir in diesem Rahmen nur einen sehr kleinen Einblick in die Welt der
stochastischen Methoden verschaffen und die Hauptideen an einfachen Beispielen illustrieren.
Ein Computer als deterministische Maschine kann per se gar keine wirklich zufälligen Werte erzeugen,
sondern nur so genannte Pseudozufallszahlen. Wir wollen nun eine populäre Verfahren zur Erzeugung
dieser Zahlen vorstellen und einige praktische Aspekte diskutieren. Das Problem jeden Verfahrens Zufallszahlen mittels eines Algorithmus zu erzeugen ist offenbar: erzeugen und Zufall ist ein Widerspruch
in sich! Pseudozufallszahlen sollen Zahlenfolgen sein, die zufällig sind, d.h. die Eigenschaften besitzen, die dem echten Zufall nahe kommen (vgl. Vorlesung Einführung in die Wahrscheinlichkeitstheorie
und Statistik). Wir wollen hier Zufallszahlen zunächst lediglich so verstehen, dass kein Muster und
keine Struktur in der Folge erkennbar sein sollte. Die Wahrscheinlichkeitstheorie und Statistik stellt
Hilfsmittel bereit, solche Folgen tatsächlich auf Zufälligkeit zu testen.
3.1.
Grundsätzliches
Der Wunsch, zufällige Ereignisse zu generieren wurde durch die Rechenmöglichkeiten stark in den
Vordergrund gerückt. Zur Geschichte:
• 1938: Kendall und Babington-Smith erzeugen mit einer schnell drehenden Scheibe 100 000 zufällige
Ziffern.
• Seit 1940/50 werden numerische und arithmetische Verfahren zur Erzeugung von Zufallszahlen
verwendet.
• 1957: Das erste ERNIE-Projekt (Electronic Random Number Indicator Equipment) wurde realisiert. Es wurden mit Hilfe von Vakuumröhren bis zu 50 Zufallsziffern pro Sekunde erzeugt.
• 1955: Die Rand-Corporation (Research ANd Development) veröffentlicht ein Buch mit ca. 1
Millionen Zufallsziffern (Wikipedia Artikel).
3.2.
3.2.1.
Mathematik des Zufalls
Elementarereignisse und Ereignisalgebra
Ein Zufallsexperiment wird mathematisch beschrieben durch eine nichtleere Menge ⌦, deren Elemente
! Elementarereignisse genannt werden. Bestimmte Teilmengen A 2 A, wobei A eine Menge von
37
Teilmengen von ⌦ ist, heißen Ereignisse. Dabei muss A eine Ereignissalgebra sein, d.h. die folgenden
Eigenschaften müssen erfüllt sein:
E 1: Die leere Menge ; 2 A und ⌦ 2 A.
E 2: Ist A 2 A ein Ereignis, so ist auch das Komplement (Gegenereignis) Ac = ⌦\A 2 A ein
Ereignis.
E 3: Sind A, B 2 A Ereigniss, so ist ihre Vereinigung A [ B 2 A ein Ereignis.
Die kleinstmögliche Ereignisalgebra ist {;, ⌦}) und die größtmögliche die Potenzmenge P(⌦) := {A |
A ✓ ⌦} (d.h. die Menge aller Teilmengen). Das Tupel (⌦, A) nennt man einen Messraum.
3.2.2.
Wahrscheinlichkeitsmaß
Wir betrachten einen Messraum (⌦, A) und wollen jedem Ereignis A 2 A eine Wahrscheinlichkeit
zuordnen. Dies geschieht durch Abbildung P : A ! [0, 1], die jedem Ereignis A 2 A eine Zahl
zwischen 0 und 1 zuordnet, sodass
W 1: P(⌦) = 1
W 2: A \ B = ; =) P(A [ B) = P(A) + P(B).
Wir nennen P ein Wahrscheinlichkeitsmaß.
3.2.3.
Beispiel (Idealer Würfel, diskrete Gleichverteilung)
Für das wahrscheinlichkeitstheoretische Modell eines fairen Würfels wählen wir die Elementarereignisse
⌦ = {1, 2, 3, 4, 5, 6}, als Ereignisalgebra die Potenzmenge A = P(⌦) und das Wahrscheinlichkeitsmaß
P({i}) =
1
6
für alle i 2 ⌦.
Damit ergibt sich für das Ereignis, dass eine ungerade Augenzahl geworfen wird, d.h. für das Ereignis
A = {1, 3, 6} = {1} [ {3} [ {5}
die folgende Wahrscheinlichkeit
P(A) = P({1}) + P({3}) + P({5}) =
3
1
= .
6
2
Zufallsexperimente, bei denen alle Elementarereignisse gleich wahrscheinlich sind, heißen auch LaplaceExperimente.
Mit der C-Funktion rand() können wir das 20-fache Werfen eines Würfels simulieren (vergleiche Programm zufall.c auf der Homepage):
1
2
3
4
5
6
7
8
9
10
11
12
#include < s t d l i b . h>
#include <s t d i o . h>
i n t main ( void ) {
int a , i ;
f o r ( i =0; i <20; i ++)
// Es w i r d 20 mal " g e w u e r f e l t "
{
a=rand ( ) ;
p r i n t f ( "%i " , a%6+1); // ( Rest b e i D i v i s i o n durch 6)+1
}
p r i n t f ( " \n" ) ;
return 0 ;
}
38
Problem: Jeder Aufruf des Programms liefert immer wieder die gleiche Zufallszahlenfolge. Dies kann
man umgehen, indem man den Zufallsgenerator initialisiert, indem man einen seed setzt. Dies geschieht
mit der Funktion srand(). Die Verwendung von Datum/Uhrzeit zur Initialisierung des Generators führt
dazu, dass die erzeugte Zahlenfolge zufällig wird. Zum Beispiel so (vergleiche Programm zufall2.c
auf der Homepage):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <s t d i o . h>
#include < s t d l i b . h>
#include <time . h>
i n t main ( void )
{
int i , a ;
time_t t ;
time (& t ) ; // Z e i t a b s t a n d zum 1 . 0 1 . 1 9 7 0 , 0 0 : 0 0 : 0 0 ( Greenwich Mean Time )
// i n Sekunden .
s r a n d ( ( unsigned i n t ) t ) ;
/⇤ Z u f a l l s g e n e r a t o r i n i t i a l i s i e r e n ⇤/
f o r ( i =0; i <20; i++ )
{
a=rand ( ) ;
p r i n t f ( "%i " , a%6+1); // ( Rest b e i D i v i s i o n durch 6)+1
}
p r i n t f ( " \n" ) ;
return 0 ;
}
3.2.4.
Beispiel (Gleichverteilung auf dem Intervall [0, 1], stetige Gleichverteilung)
Sei ⌦ ein endliches reelles Intervall, also ⌦ = [a, b] für a, b 2 R. Die Wahrscheinlichkeit eines Ereignisses
A = [c, d] ✓ [a, b] ist
P(A) =
d
b
c
.
a
Damit besitzen alle Teilintervalle gleicher Länge dieselbe Wahrscheinlichkeit. Die Theorie der stetigen
Wahrscheinlichkeitsverteilungen benötigt sehr viel theoretischen Hintergrund. Beispielsweise kann man
hier nicht auf die Potenzmenge als Ereignisalgebra zurückgreifen. Näheres lernen Sie in der Vorlesung
Einführung in die Wahrscheinlichkeitstheorie und Statistik.
3.3.
Pseudozufallszahlen und die Lineare Kongruenzmethode
Die erste Realisierung der Pseudozufallzahlserzeugung bestand in der Nutzung der Dezimalziffern transzendenter Zahlen. Die Zahl ⇡ wurde 1873 mit 703, 1960 mit 100 000 und 1986 mit 107 Dezimalstellen
berechnet. Die statistische Analyse ergab, dass keine signifikanten Abweichungen von der Gleichverteilung auftraten. Da aber die Algorithmen zur Berechnung transzendenter Zahlen in der Regel sehr
kompliziert sind, werden in der Praxis meist andere Algorithmen benutzt.
Sei M eine endliche Menge. Wir wollen hier Pseudozufallszahlen betrachten, die sich als Iterierte einer
Funktion f : M ! M in folgenderweise ergeben:
xk+1 := f (xk ).
(3.1)
Die Abbildung f heißt dabei Generator der Folge und der Startwert x0 der Samen/seed. Da die Menge
M endlich ist, können nicht alle Folgeglieder xk verschieden sein. Seien die Indizes k > ` die ersten
für die xk = x` eintritt und r := k `. Da xk = x` , folgt xn+r = xn für alle n
`. Also wird die
Pseudozufallszahlfolge (xk )k2N0 periodisch mit Periode r.
Die Folge (xk )k2N0 ist durch die Wahl von f und x0 vollständig bestimmt. Durch geschickte Wahl von
f – gewünscht wir eine gute Durchmischung von M – kann man erreichen, dass sich die Folge wie
39
eine Zufallsfolge verhält. Ein weitverbreitetes Verfahren zur Erzeugung von Pseudozufallszahlen ist die
lineare Kongruenzmethode.
3.3.1.
Lineare Kongruenzmethode
Sei der Modul m 2 {2, 3, . . .} fest und die Menge M definiert durch M = {0, 1, . . . , m
betrachten folgende Konkretisierung zur Realisierung von Pseudozufallszahlen
f : M ! M,
M 3 x 7 ! ax + b
mod m,
1}. Wir
(3.2)
wobei der Faktor a 2 M \{0} und das Inkrement b 2 M \{0} vorgegebene Werte. Mit einem festen seed
x0 2 M erhält man eine Folge von Pseudozufallszahlen
xk+1 = (a xk + b)
mod m.
Durch die Modulo-Operation erzeugt diese Vorschrift eine ganzzahlige Folge mit Werten aus der Menge
{0, 1, . . . , m 1}. Teilt man die Pseudo-Zufallszahlen xk durch m,
⇣k :=
xk
m
für alle k = 0, 1, 2, . . .
so sind die ⇣k zwar näherungsweise auf dem Intervall [0, 1] gleichverteilt und erfüllen eine Reihe von
Kriterien echter Zufallszahlen. Sie besitzen aber dennoch gewisse Regelmäßigkeiten, die echte Zufallszahlen nicht aufweisen.
Damit die Abbildung f aus (3.2) den gesamten Bildraum ausfüllt (also bijektiv ist) und der Zyklus
damit maximal ist, muss b zu m teilerfremd sein. Aber diese Forderung reich aber nicht aus, wie das
folgende Beispiel zeigt.
Beispiel
1. Betrachte die Wahl m = 10, a = b = 7. Hier ist der erzeugte Zyklus für x0 = 0
7, 6, 9, 0, 7, 6, 9, 0
ziemlich kurz, r = 4, obwohl b = 7 zu m = 10 teilerfremd ist.
Wir kommen nun zum Hauptergebnis über lineare Kongruenzgeneratoren.
3.3.2.
Satz von Knuth
Lineare Kongruenzgeneratoren erreichen nach dem Satz von Knuth genau dann ihre maximal mögliche
Periodenlänge m, wenn die folgenden Voraussetzungen erfüllt sind:
1. Das Inkrement b ist zum Modul m teilerfremd.
2. Jeder Primfaktor von m teilt a
1.
3. Wenn m durch 4 teilbar ist, dann auch a-1.
Beweis. Siehe Knuth (1981).
Der Satz von Knuth nennt uns Bedingungen für einen linearen Kongruenz-Generator, damit er der
Minimalforderung, einen Zyklus maximaler Länge zu erzeugen, genügt. Jedoch garantieren diese Bedingungen noch lange keinen guten Zufallsgenerator, wie das folgende Beispiel zeigt:
40
Beispiele (Fortsetzung)
2. Betrachte die Wahl a = b = 1. Hier ist der erzeugte Zyklus für x0 = 0
1, 2, 3, . . . , m
1, 0, 1, . . .
maximal, aber sicherlich nicht zufällig.
3. Auf der Internetseite www.mathematik.tu-clausthal.de/interaktiv/simulation/erzeugunggleichverteilter-zufallszahlen/ kann man ausprobieren, ob die gewählten Parameter optimal sind, das heißt, ob die maximale Periodenlänge m erreicht wird.
Für den Startwert x0 = 3 , Faktor a = 1 Modul m = 4, Inkrement b = 4 ist das nicht der Fall.
Ändert man das Inkrement zu b = 5 wird die maximale Periodenlänge m = 4 erreicht.
Für Anwendungen in der Kryptographie ist es wichtig, dass nicht aus einer Reihe von Pseudo-Zufallszahlen
auf die jeweils nächste Pseudo-Zufallszahl geschlossen werden kann. Für die Anwendung in der stochastischen Simulation sind keine so starken Voraussetzungen notwendig, so dass hier häufig lineare
Kongruenzgeneratoren zur Erzeugung von Zufallszahlen zur Anwendung kommen.
Beispiele (Fortsetzung)
4. In C gibt es den Zufallszahlengenerator drand48, bei dem m = 248 , a = 25 214 903 917 und
b = 11 gesetzt ist. Die Zykluslänge ist maximal, da die Bedingungen von Satz 3.3.2 erfüllt
sind. Um (pseudo)-zufällige double-Variablen zu erzeugen, kann man die Funktion drand48()
folgendermaßen nutzen (vergleiche Programm zufall3.c auf der Homepage):
1
2
3
#include <s t d i o . h>
#include < s t d l i b . h>
#include <time . h> //
4
5
6
7
8
9
i n t main ( void )
{
int i ;
double a ;
time_t t ;
10
time (& t ) ; // Z e i t a b s t a n d zum 1 . 0 1 . 1 9 7 0 , 0 0 : 0 0 : 0 0 ( Greenwich Mean Time )
// i n Sekunden .
11
12
13
s r a n d 4 8 ( ( long i n t ) t ) ;
14
15
16
17
18
19
20
21
22
23
}
/⇤ Z u f a l l s g e n e r a t o r i n i t i a l i s i e r e n ⇤/
f o r ( i =0; i <20; i++ )
{
a=drand48 ( ) ;
p r i n t f ( "%l f " , a ) ;
}
p r i n t f ( " \n" ) ;
return 0 ;
5. Der C-Generator rand arbeitet mit den Parametern m = 231 , a = 1 103 515 245 und b = 11. Sind
hier auch die Bedingungen von Satz 3.3.2 erfüllt? (Übungsaufgabe)
6. Von Knuth wurde der Generator mit m = 216 , a = 137 und b = 187 vorgeschlagen. Die Zykluslänge ist ebenfalls maximal.
7. Betrachte die Wahl m = 231 , a = 65 539, b = 0. Dies ist der Zufallsgenerator RANDU, wie er
von IBM in den Computern in den 60er Jahren verwenden wurde. Die maximal erreichbare Zykluslänge r ist hier nicht ganz maximal, aber mit r = 229 nahezu maximal. Neben der mangelnden
41
Abbildung 3.1: Lage der mit Hilfe des Zufallszahlengenerators aus Beispiel 7 erzeugten Punkte im
Einheitswürfel. Quelle: Kirsch&Schmitt (2007).
Minimalität der Zykluslänge gibt es einen weiteren Nachteil dieses Generators: Betrachtet man
für ein beliebiges k 2 N0 das Zahlentripel
(⇣k , ⇣k+1 , ⇣k+2 ) 2 [0, 1)3 .
und interpretiert das Tripel als kartesische Koordinaten von Punkten im Einheitswürfel, so verteilen sich diese nicht gleichmäßig über den Würfel, sondern ausschließlich auf 15 Ebenen (siehe
Abbildung 3.1)1 .
3.3.3.
Bemerkung
Im Allgemeinen sollte man niemals Teile einer Pseudozufallszahl als weitere Zufallszahl benutzen.
Will man aus großen Zahlen kleine gewinnen, d.h. will man etwa von Zufallszahlen xk 2 [0, m) auf
Zufallszahlen yk 2 [0, r), r < m, übergehen, so sollte man dies nicht gemäß
yk := xk
mod r
tun, sondern etwa mittels
yk := bxk /wc
3.3.4.
mod r,
w
geeignet.
Beispiel
8. Betrachte den Spezialfall a = 31 415 821, b = 1, m = 108 . Der Startwert x0 := 1 234 567 liefert
die Zahlen x1 , . . . , x20 . Die Folge der jeweils letzten Ziffer lautet 8, 9, 0, 1, 2, 3, 4, . . . , 5, 6, 7. Wir
beobachten also eine vollkommene Regelmäßigkeit.
1
Siehe auch microsoft-confirms-that-xp-contains-random-number-generator-bug.html
42
3.4.
Die Güte von Zufallszahlen
Wie soll man gute und weniger gute Zufallszahlgeneratoren auseinanderhalten? Ein Merkmal haben
wir schon kennen gelernt: Die Ausschöpfung des zur Verfügung stehenden Zahlenraums. Ferner gibt
es theoretische und empirische Tests für die Güte von Generatoren. Theoretische Tests setzen am
Generator selbst an, empirische Tests an den erzeugten Zahlenfolgen. Folgende Kriterien werden dabei
untersucht: Gleichverteiltheit, Unkorreliertheit, Effizienz bzw. Schnelligkeit.
3.5.
Statistische Tests
Die erzeugten Zufallszahlen können durch statistische Tests auf ihre Gleichverteilung untersucht werden. Folgende Tests kommen dabei in Frage:
1. Chi-Quadrat Test: Test auf Übereinstimmung zweier Wahrscheinlichkeitsverteilungen
2. Kolmogorov-Smirnov-Test: Gewisse Verfeinerung des Chi-Quadrat Tests
3. Poker-Test: Betrachtet Gruppen zu je 5 aufeinanderfolgender Zahlen und beobachtet, welches
der Folgen 7 Muster mit dem Quintupel übereinstimmt:
(a) Alle verschieden: abcde
(b) Ein Paar: aabcd
(c) Zwei Paare: aabbc
(d) Drei Gleiche: aaabc
(e) Full house: aaabb
(f) Vier gleiche: aaaab
(g) Fünf gleiche: abcde
Auf diese Anzahlen wird ein Chi-Quadrat-Test angewendet, um herauszufinden, ob die empirische
Verteilungsfunktion in Übereinstimmung mit der Gleichverteilung ist.
3.5.1.
Der Chi-Quadrat-Test
Der Chi-Quadrate-Test ( 2 -Test), entwickelt von Karl Pearson um 1900, ist eines der ältesten und
mächtigsten Testverfahren in der Statistik. In der einfachsten Form dient es der Prüfung der Verträglichkeit von beobachteten relativen Häufigkeiten – hier in einer (möglichst) gleichverteilten Zufallsfolge mit Werten in M = {0, 1, . . . , m 1}, m 2 N, – mit der diskreten Gleichverteilung auf M ,
d.h. P({i}) = 1/m für alle i 2 M .
Dazu teilen wir die vorliegenden Zufallszahlen x1 , . . . , xn 2 M in disjunkte Kategorien Kj ✓ M, j =
1, . . . , `, die den Raum der möglichen Zufallszahlen M ausschöpfen. Das Eintreten der Kategorie Kj
ist unter der Annahme der Gleichverteilung durch die Wahrscheinlichkeit
pj =
#Kj
m
(Anzahl der Elemente in Kj geteilt durch Anzahl der Elemente in M )
verknüpft.
Das Ziel ist ein Test der einfachen Hypothese
H0 : pj = #Kj /m für jedes j = 1, . . . , `
gegen die Alternative
H1 : pj 6= #Kj /m für ein j 2 {1, . . . , `}
43
Die Idee besteht darin, eine handhabbare Testgröße anzugeben, die es gestattet, bei einer kritischen
Größe die Hypothese H0 abzulehnen. Diese Testgröße ist
2
:=
X̀ (#Kj
n pj )2
n pj
j=1
;
man nennt sie die 2 - Statistik mit ` 1 Freiheitsgraden. Damit erhalten seltene Kategorien eine hohe
Gewichtung und der 1 -Wert ist umso größer, desto stärker die Abweichung zwischen beobachteter
und theoretischer Verteilung ist. Wenn 2 eine bestimmte Schranke c überschreitet, dann wird die
Hypothese verworfen. Zu einem vorgegebenen Testniveau ↵ > 0 (zugelassene Wahrscheinlichkeit für
Fehler 1. Art, d.h. H0 wird zurückgewiesen, obwohl sie in Wirklichkeit wahr ist) kann man c aus der
2 -Tabelle ablesen.
Beispiel: Bitkette der Länge 50
Betrachte die Bitkette
10101 00000 01111 01000 10001 01011 00110 01000 10001 00010
Wir finden 19 1-Bits und 31 0-Bits. Bei einer unterstellten Gleichverteilung für das Auftreten eines
1-Bits mit der Wahrscheinlichkeit 0.5 erhalten wir für den 2 -Wert:
2
=
(19
25)2 (31 25)2
36 36
+
=
+
= 2.88
25
25
25 25
Die 2 -Tabelle weist als kritischen Wert c = 2.71 für ↵ = 0.1 und c = 3.84 für ↵ = 0.05 aus
(Freiheitsgrad 1, p = 1 ↵). Damit lehnen wir die Hypothese im Fall ↵ = 0.1 ab. Im Fall ↵ = 0.05
verwerfen wir die Hypothese nicht (was aber nicht heißt, dass sie auf jeden Fall richtig ist).
44
Literaturverzeichnis
R. Kirsch&U. Schmitt (2007). Programmieren in C: Eine mathematikorientierte Einführung, Springer, Berlin.
Donald E. Knuth (1981). The Art of Computer Programming (Volume 2), Addison-Wesley.
45
Herunterladen