Zum Inhalt springen

Kurs:Einführung in die mathematische Logik (Osnabrück 2011-2012)/Vorlesung 8

Aus Wikiversity

Wir kehren nun zur Ausgangsfrage dieses Kurses zurück, ob es eine Maschine geben kann, die mathematische Probleme (etwa aus der Zahlentheorie) löst. In den vorhergehenden Vorlesungen haben wir eine formale Sprache entwickelt, in der man solche nichttrivialen Probleme präzise formulieren kann. Ferner haben wir gesehen, wie ein formaler Beweis (eine Ableitung im Prädikatenkalkül) in dieser Sprache aussieht, und dass es nach dem Vollständigkeitssatz für jeden mathematisch beweisbaren Ausdruck der Sprache auch einen formalen Beweis gibt.

In dem vorgestellten Ableitungskalkül der Prädikatenlogik sind die Starttautologien und die Ableitungsregeln übersichtlich strukturiert. Zwar nehmen die Starttautologien häufig Bezug auf beliebige Ausdrücke (und Variablen) der Sprache, doch da die Ausdrücke prinzipiell auflistbar sind, gilt dies auch für die Starttautologien. Daher kann man sich auch einen Algorithmus vorstellen, der nach und nach alle formalen Beweise und somit auch alle formal-beweisbaren Ausdrücke ausgibt. Ein andersgelagertes Problem ist die Fragestellung, ob es ein Entscheidungsverfahren für die Prädikatenlogik gibt, ob es also ein algorithmisches Verfahren gibt, dass zu einem gegebenen Ausdruck überprüfen kann, ob es dafür einen formalen Beweis gibt oder nicht.

Wenn wir bisher von Algorithmen gesprochen haben, so haben wir dabei immer an intuitiv durchführbare Algorithmen gedacht, ohne ein konkretes Durchführungsmodell vor Augen zu haben. In dieser Vorlesung stellen wir die Arbeitsweise einer konkreten algorithmischen Maschine vor, der Registermaschine, die wir von nun an als mechanische Realisierung unserer intuitiven Vorstellung von Algorithmen auffassen wollen.



Registermaschinen

Es gibt verschiedene Möglichkeiten, eine deterministisch arbeitende Maschine zu modellieren. Wir arbeiten hier mit Registermaschinen, da diese einem wirklichen Computer ziemlich nahe kommen und daher etwas vertrauter sind als Turingmaschinen oder rekursive Funktionen (wobei letztere vom mathematischen Standpunkt her eleganter sind).


Unter einer Registermaschine versteht man eine endliche Folge von Registern R1,R2,,Rm (oder Speichern), deren Inhalt jeweils eine natürliche Zahl ist, die durch eine endliche (eventuell leere) Folge von Strichen repräsentiert wird.

Ein Programm für eine Registermaschine ist eine endliche durchnummerierte Folge von Befehlen B1,B2,,Bh, wobei es für die einzelnen Befehle Bj die folgenden Möglichkeiten gibt.

  1. i+ (erhöhe den Inhalt des Registers Ri um 1, d.h. um einen Strich).
  2. i (reduziere den Inhalt des Registers Ri um 1, d.h. ziehe einen Strich ab; wenn der Inhalt leer ist, so lasse ihn leer).
  3. C(ij) (wenn das i-te Register leer ist, so gehe zum Befehl Bj, andernfalls zum nächsten Befehl).
  4. Drucke (drucke den Inhalt des ersten Registers).
  5. Halte an.

Dabei muss  im  für alle in einer Programmzeile adressierten Register und  jh  für alle adressierten Befehlszeilen gelten. Die letzte Befehlszeile Bh ist ein Haltebefehl und sonst gibt es keinen Haltebefehl.

Die beiden ersten Befehle nennt man Inkrementierung bzw. Dekrementierung. Der dritte Befehl ist der Abfragebefehl oder die (bedingte) Sprunganweisung. Es folgen Druckbefehl und Haltebefehl.

Ein Programm für eine Registermaschine arbeitet die Befehle der Reihe nach ab und zwar unter den jeweiligen zum Bearbeitungszeitpunkt vorgefundenen Registerbelegungen. Wenn die aktuelle Programmzeile ein bedingter Sprungbefehl C(ij) ist, so wird, falls die Bedingung zu diesem Zeitpunkt erfüllt ist (also falls das Register Ri leer ist), zur Programmzeile Bj gewechselt. Wenn die Endzeile Bh, also der Haltebefehl erreicht wird, so ist die Bearbeitung beendet.

Die Belegung (oder der Inhalt) des Registers Ri, die sich im Laufe des Programmdurchlaufs mehrfach ändern kann, werden wir häufig mit ri bezeichnen. Dies ist stets eine natürliche Zahl. Wenn das Register Ri leer ist, so ist sein Inhalt ri=0.

Die Möglichkeiten einer Registermaschine scheinen auf den ersten Blick recht bescheiden zu sein. Man sieht aber recht schnell, dass man aus diesen wenigen Befehlen Programmabschnitte zusammensetzen kann, die zunehmend komplexere Befehle ausführen. Komplexe Befehle, von denen schon gezeigt wurde, dass sie sich mit Hilfe der Grundbefehle realisieren lassen, werden ohne weiteren Kommentar weiterverwendet.

Man sagt, dass ein Programm korrekt ist, wenn es das tut, was es tun soll. Wenn beispielsweise gesagt wird, dass ein Programm zwei Zahlen addiert, so wird die Korrektheit dadurch bewiesen, dass man eben durch Analyse des Programmcodes nachweist, dass bei beliebiger Belegung der beiden Register, deren Inhalte addiert werden sollen, das Programm schließlich anhält und in einem weiteren Register wirklich die Summe der beiden Zahlen gespeichert ist. Ein Korrektheitsnachweis ist häufig eine mühevolle Kleinarbeit mit aufwändigen Fallunterscheidungen, in den natürlich auch mathematische Überlegungen eingehen, wie z.B. bei der Addition die Eigenschaft, dass s+t=s+(t1)+1 ist, was einen induktiven Korrektheitsbeweis ermöglicht. Wir werden diese Korrektheitsüberlegungen häufig abkürzen.



Programmbeispiele

Wir beschreiben nun einige Programme bzw. Programmabschnitte für Registermaschinen. Wenn man Programme aus schon entwickelten Programmabschnitte zusammensetzt, so ändern sich natürlich die absoluten Befehlsnummern im Programm, was wir aber ignorieren werden.


Einen unbedingten Sprung (ein „Go to-Befehl“) zu einer bestimmten Programmzeile, der also nicht von einer Abfrage abhängt, kann man dadurch realisieren, dass man ein neues Register Rk hinzunimmt, das von keiner anderen Programmzeile adressiert wird und dessen Inhalt auf 0 gesetzt wird. Dann bewirkt der Befehl C(kj), dass zur j-ten Programmzeile gewechselt wird, da der Inhalt des Registers Rk im gesamten Programmverlauf gleich 0 bleibt.



Ein Programm soll sämtliche natürlichen Zahlen der Reihe nach ausdrucken. Dazu brauchen wir eine Registermaschine mit zwei Registern R1 und R2, die zum Start beide leer sind. Das zweite Register bleibt unverändert und wird nur für den unbedingten Sprungbefehl verwendet. Die Haltezeile wird nie erreicht.

  1. Drucke
  2. 1+
  3. Gehe zu 1
  4. Halte an


Das Register Ri soll geleert werden. Dies geschieht durch das folgende Programm.

  1. C(i,4)
  2. i
  3. Gehe zu 1
  4. Halte an

Wir erlauben, dass bei einer Registermaschine die Anfangsbelegung der Register von außen festgelegt wird. Man könnte aber auch festlegen, dass die Anfangsbelegung stets die Nullbelegung ist, ohne die Berechnungsmöglichkeiten der Registermaschine einzuschränken. Dann kann man die eigentlich gewünschte Anfangsbelegung dadurch erreichen, dass man dem Programm ein „Belegungsprogramm“ voranstellt, das den einzelnen Registern Ri durch die si Befehle i+,,i+ die gewünschte Belegung si zuweist.

Man könnte auch erstmal ein „Entleerungsprogramm“ vorschalten, das alle Register leert und daran anschließend die Belegung durchführt, doch muss man für den Entleerungsvorgang, der nach Beispiel 8.4 einen unbedingten Sprungbefehl verwendet, zumindest ein leeres Register zur Verfügung haben.


Wenn der Registerinhalt ri um eine natürliche Zahl k erhöht werden soll, also k-fach direkt hintereinander inkrementiert werden soll, so schreiben wir dafür auch i++ mit k Pluszeichen.


Es soll mit einer Registermaschine festgestellt werden, ob der Inhalt ri des Registers Ri größer oder gleich dem Inhalt rj des Registers Rj ist. Dazu reserviert man das leere Register Rk (i,j,k seien paarweise verschieden) und baut einen Programmabschnitt der folgenden Art.

  1. C(j,6)
  2. j
  3. C(i,7)
  4. i
  5. Gehe zu 1
  6. k+
  7. Halte an

Wenn dieser Programmabschnitt abgelaufen ist, so steht im Register Rk der Wert rk=1 oder rk=0, je nachdem, ob  rirj  ist oder nicht, und zwar unabhängig davon, ob man damit die Eingangsdaten oder Zwischendaten, wenn das Programm den ersten Befehl abarbeitet, meint. Die Korrektheit dieses Programms beruht darauf, dass  rs  genau dann gilt, wenn  r1s1  ist. Dies ermöglicht einen Induktionsbeweis.



Wir wollen überprüfen, ob die Inhalte von zwei Registern Ri und Rj übereinstimmen. Dazu kann man das Programm aus Beispiel 8.5 einfach abändern zu

  1. C(j,6)
  2. j
  3. C(i,9)
  4. i
  5. Gehe zu 1
  6. C(i,8)
  7. Gehe zu 9
  8. k+
  9. Halte an

Bei Gleichheit erhält man  rk=1,  bei Ungleichheit  rk=0


In den obigen beiden Beispielen wurde die Antwort im Register Rk (in der Form 0 oder 1 abgespeichert). Der Druckbefehl nimmt aber immer Bezug auf R1. Daher ist es nötig, Registerinhalte in andere Register zu verschieben.


Wir wollen den Registerinhalt ri des Registers Ri in das Register Rj übertragen (unabhängig von dessen Inhalt). Dies leistet das folgende Programm.

  1. Leere Rj
  2. C(i,6)
  3. i
  4. j+
  5. Gehe zu 2
  6. Halte an

Bei diesem Programm wird im Laufe der Durchführung der Ausgangsregister der Übertragung leer gemacht. Dies ist nicht immer erwünscht, häufig möchte man den Inhalt eines Registers kopieren und sich den Inhalt zugleich merken.


Wir wollen den Registerinhalt ri des Registers Ri in das Register Rj übertragen (unabhängig von dessen Inhalt), ohne Ri zu leeren. Dazu brauchen wir ein drittes Register Rk und das folgende Programm.

  1. Leere Rj
  2. Leere Rk
  3. C(i,8)
  4. i
  5. j+
  6. k+
  7. Gehe zu 3
  8. Übertrage den Inhalt von Rk nach Ri
  9. Halte an

Hier wird zwar im Laufe des Programms der Inhalt von Ri verändert, zum Schluss wird der ursprüngliche Inhalt aber wieder hergestellt.



Die beiden Registerinhalte ri (von Ri) und rj (von Rj) sollen addiert werden, wobei die Summe zum Schluss in Rk stehen soll (es seien i,j,k paarweise verschieden). Dies leistet das folgende Programm.

  1. Leere Rk
  2. Übertrage ri nach Rk
  3. C(j,7)
  4. j
  5. k+
  6. Gehe zu 3
  7. Halte an

Mit der Addition und der Kopie von Inhalten kann man auch den Inhalt eines Registers zu einem anderen Register dazuaddieren. Dies kann man natürlich auch einfach direkt realisieren.


Die beiden Registerinhalte ri (von Ri) und rj (von Rj) sollen multipliziert werden, wobei das Produkt zum Schluss in Rk stehen soll (es seien i,j,k paarweise verschieden). Dies leistet das folgende Programm mit dem Hilfsregister R.

  1. Leere Rk
  2. Übertrage den Inhalt von Ri nach R ohne Ri zu leeren
  3. C(j,7)
  4. Addiere den Inhalt von R zu Rk hinzu
  5. j
  6. Gehe zu 2
  7. Halte an

Die Korrektheit dieses Programms beruht auf  rs=(r1)s+s;  für das Produkt rs muss man r-mal s mit sich selbst addieren.



Es soll überprüft werden, ob der Registerinhalt rt (von Rt) den Registerinhalt rj (von Rj) teilt. Falls ja soll das Programm 1 ausgeben, andernfalls 0. Dies leistet das folgende Programm mit den Hilfsregistern Rk und R (für Teilprogramme braucht man noch weitere Hilfsregister). Das Ausgaberegister R1 soll zu Beginn leer sein.

  1. Leere R
  2. Berechne rtr und schreibe das Ergebnis in Rk (ohne rt,r zu verändern)
  3. Bei  rk>rj  gehe zu 8
  4. Bei  rk=rj  gehe zu 7
  5. +
  6. Gehe zu 2
  7. 1+
  8. Drucke
  9. Halte an


Es soll überprüft werden, ob der Registerinhalt rj (von Rj) eine Primzahl ist. Falls ja soll das Programm 1 ausgeben, andernfalls 0. Dies leistet das folgende Programm mit dem Hilfsregister Rt (für Teilprogramme braucht man noch weitere Hilfsregister). Das Ausgaberegister R1 soll zu Beginn leer sein.

  1. Leere Rt
  2. t+
  3. t+
  4. Wenn rt=rj, so gehe zu 8
  5. Wenn rtrj, so gehe zu 9[1]
  6. Wenn rj von rt geteilt wird, so gehe zu 9
  7. Gehe zu 3
  8. 1+
  9. Drucke
  10. Halte an


Es sollen die geraden Zahlen 4 daraufhin überprüft werden, ob sie die Eigenschaft in der Goldbachvermutung erfüllen, also ob sie die Summe von zwei Primzahlen sind. Das Programm soll die Ausgabe 0 machen, falls ein Gegenbeispiel gefunden wurde. Dies leistet das folgende Programm mit den Registern Rn, Rk und Ri, die alle zu Beginn auf 0 gesetzt seien. Auch das Ausgaberegister R1 soll zu Beginn leer sein. Wir testen ab 6, um uns auf ungerade Primzahlen als Summanden beschränken zu können.

  1. n++++
  2. n++
  3. Leere Rk
  4. k+
  5. k++
  6. Wenn  rkrn,  so gehe zu 12
  7. Wenn rk eine Primzahl ist, so gehe zu 9
  8. Gehe zu 5
  9. Berechne rnrk, schreibe das Ergebnis in Ri
  10. Wenn ri eine Primzahl ist, so gehe zu 2
  11. Gehe zu 5
  12. Drucke
  13. Halte an



Fußnoten
  1. Die Programmzeile (5) ist nur für rj=0,1 von Bedeutung.



<< | Kurs:Einführung in die mathematische Logik (Osnabrück 2011-2012) | >>

PDF-Version dieser Vorlesung

Arbeitsblatt zur Vorlesung (PDF)