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