Kapitel 10

Kapitel 10 Kapitel 10

www2.fh.rosenheim.de
von www2.fh.rosenheim.de Mehr von diesem Publisher
28.10.2013 Aufrufe

10 Algorithmen 477 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 10 Algorithmen Das WAS bedenke, mehr bedenke WIE. J.W. von Goethe, Faust II 10.1 Berechenbarkeit 10.1.1 Algorithmen und ihre prinzipielle Ausführung In den vorausgegangenen Kapiteln wurde gezeigt, dass die durch einen Computer zu bearbeitenden Aufgaben durch eine endliche Aneinanderreihung einfacher Anweisungen, – letztlich in Maschinensprache – beschrieben werden müssen. Eine solche Beschreibung, wie eine Aufgabe auszuführen ist, bezeichnet man als Algorithmus. Der Begriff Algorithmus leitet sich vom Namen des arabischen Gelehrten Al Chwarizmi ab, der um 820 lebte. Prozedurale Algorithmen Unter einem Algorithmus, im engerem Sinne einem prozeduralen Algorithmus, versteht man eine Vorschrift zur Lösung einer Klasse von Problemen in Form einer endlichen Anzahl elementarer Aktionen, die man als Zustandsübergänge eines (technischen) Systems interpretieren kann, wobei die Zustände durch Variablen gekennzeichnet werden. Die Klasse der Probleme entspricht dem Definitionsbereich des Algorithmus, die Auswahl bestimmter Probleme erfolgt durch Parameter. Der Lösungsplan soll darüber hinaus nach Möglichkeit so allgemein formuliert werden, dass er für eine Klasse ähnlich gelagerter Probleme anwendbar ist. Die elementaren Aktionen müssen klar, eindeutig und hinreichend einfach sein, so dass auf Grund von vorgegebenen Definitionen ihre Ausführung immer möglich ist. Die Werte der Variablen können nach dem Schema Eingabe-Verarbeitung-Ausgabe (EVA) als Startwerte (Parameter) von außen vorgegeben werden (Eingabe), sie können durch die einzelnen Aktionen des Algorithmus geändert (Verarbeitung, Wertzuweisung) und nach außen übertragen werden (Ausgabe). Sinnvollerweise verlangt man, dass mindestens ein Ergebnis als Ausgabewert erscheint. Wegen der endlichen Anzahl der Aktionen ist ein Algorithmus statisch finit. Von den Aktionen fordert man, dass sie effektiv in endlicher Zeit und unter Verwendung endlicher Ressourcen (z.B. Speicherplatz) ausführbar sein müssen. Daraus ergibt sich, dass ein Algorithmus auch dynamisch finit sein muss, dass also alle benötigten Ressourcen zu jedem beliebigen Zeitpunkt der Abarbeitung endlich sein müssen. Stoppt der Algorithmus für jede gültige Eingabe nach endlich vielen Schritten, so heißt er terminierend. Auch nicht-terminierende Algorithmen können durchaus sinnvoll sein, so etwa Prozess- Steuerungen und Betriebssysteme. Gibt es nach jeder Aktion nur eine Folgeaktion, so heißt der Algorithmus determiniert. Man kann die Bedingung der Determiniertheit aufgeben und nicht-determinierte Algorithmen in Betracht ziehen, bei denen nach einer Aktion verschiedene Folgeaktionen zur Verfügung stehen. Auch nicht-determinierte Algorithmen haben eine praktische Bedeutung. Beispiel: Soll eine Verbindung in einem Netz von Knoten A nach Knoten B aufgebaut werden, so können ungeachtet des detaillierten Verbindungsweges über verschiedene andere Knoten unterschiedliche Lösungen existieren und akzeptabel sein. Die Eindeutigkeit ist in diesem Falle also von untergeordneter Bedeutung. Erfolgt die Auswahl alternativer Aktionen zufällig, so spricht man von einem stochastischen Algorithmus.

<strong>10</strong> Algorithmen 477<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

<strong>10</strong> Algorithmen<br />

Das WAS bedenke, mehr bedenke WIE.<br />

J.W. von Goethe, Faust II<br />

<strong>10</strong>.1 Berechenbarkeit<br />

<strong>10</strong>.1.1 Algorithmen und ihre prinzipielle Ausführung<br />

In den vorausgegangenen <strong>Kapitel</strong>n wurde gezeigt, dass die durch einen Computer zu bearbeitenden<br />

Aufgaben durch eine endliche Aneinanderreihung einfacher Anweisungen, – letztlich<br />

in Maschinensprache – beschrieben werden müssen. Eine solche Beschreibung, wie<br />

eine Aufgabe auszuführen ist, bezeichnet man als Algorithmus. Der Begriff Algorithmus leitet<br />

sich vom Namen des arabischen Gelehrten Al Chwarizmi ab, der um 820 lebte.<br />

Prozedurale Algorithmen<br />

Unter einem Algorithmus, im engerem Sinne einem prozeduralen Algorithmus, versteht man<br />

eine Vorschrift zur Lösung einer Klasse von Problemen in Form einer endlichen Anzahl elementarer<br />

Aktionen, die man als Zustandsübergänge eines (technischen) Systems interpretieren<br />

kann, wobei die Zustände durch Variablen gekennzeichnet werden. Die Klasse der Probleme<br />

entspricht dem Definitionsbereich des Algorithmus, die Auswahl bestimmter Probleme<br />

erfolgt durch Parameter. Der Lösungsplan soll darüber hinaus nach Möglichkeit so allgemein<br />

formuliert werden, dass er für eine Klasse ähnlich gelagerter Probleme anwendbar ist. Die<br />

elementaren Aktionen müssen klar, eindeutig und hinreichend einfach sein, so dass auf<br />

Grund von vorgegebenen Definitionen ihre Ausführung immer möglich ist. Die Werte der Variablen<br />

können nach dem Schema Eingabe-Verarbeitung-Ausgabe (EVA) als Startwerte (Parameter)<br />

von außen vorgegeben werden (Eingabe), sie können durch die einzelnen Aktionen<br />

des Algorithmus geändert (Verarbeitung, Wertzuweisung) und nach außen übertragen werden<br />

(Ausgabe). Sinnvollerweise verlangt man, dass mindestens ein Ergebnis als Ausgabewert<br />

erscheint. Wegen der endlichen Anzahl der Aktionen ist ein Algorithmus statisch finit.<br />

Von den Aktionen fordert man, dass sie effektiv in endlicher Zeit und unter Verwendung endlicher<br />

Ressourcen (z.B. Speicherplatz) ausführbar sein müssen. Daraus ergibt sich, dass ein<br />

Algorithmus auch dynamisch finit sein muss, dass also alle benötigten Ressourcen zu jedem<br />

beliebigen Zeitpunkt der Abarbeitung endlich sein müssen. Stoppt der Algorithmus für jede<br />

gültige Eingabe nach endlich vielen Schritten, so heißt er terminierend.<br />

Auch nicht-terminierende Algorithmen können durchaus sinnvoll sein, so etwa Prozess-<br />

Steuerungen und Betriebssysteme. Gibt es nach jeder Aktion nur eine Folgeaktion, so heißt<br />

der Algorithmus determiniert. Man kann die Bedingung der Determiniertheit aufgeben und<br />

nicht-determinierte Algorithmen in Betracht ziehen, bei denen nach einer Aktion verschiedene<br />

Folgeaktionen zur Verfügung stehen. Auch nicht-determinierte Algorithmen haben eine<br />

praktische Bedeutung. Beispiel: Soll eine Verbindung in einem Netz von Knoten A nach Knoten<br />

B aufgebaut werden, so können ungeachtet des detaillierten Verbindungsweges über<br />

verschiedene andere Knoten unterschiedliche Lösungen existieren und akzeptabel sein. Die<br />

Eindeutigkeit ist in diesem Falle also von untergeordneter Bedeutung. Erfolgt die Auswahl<br />

alternativer Aktionen zufällig, so spricht man von einem stochastischen Algorithmus.


478 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Schritte bei der Ausführung von Algorithmen<br />

Algorithmen sind ein grundlegendes Konzept der Informatik, denn eine der großen Hauptaufgaben<br />

der Informatik ist ja gerade die Konstruktion von Algorithmen und deren Umsetzung<br />

in Programme.<br />

Dabei ist in folgenden Schritten vorzugehen:<br />

• Zunächst muss ein Algorithmus zur prinzipiellen Lösung des anstehenden Problems gefunden<br />

werden. Wesentlich ist dabei die Beschreibung der Ein- und Ausgabedaten sowie die<br />

funktionale Abhängigkeit zwischen diesen. Man bezeichnet diesen Schritt als Algorithmierung.<br />

• Im nächsten Schritt, der Programmierung, ist der Algorithmus als Programm zu formulieren.<br />

Die dazu verwendete Programmiersprache soll als Bindeglied zwischen Mensch und Maschine<br />

von den Maschinendetails möglichst abstrahieren und eine aus Sicht des Programmierers<br />

möglichst einfache und vollständige Formulierung beliebiger Algorithmen unterstützen.<br />

• Der letzte Schritt ist die Ausführung des Programms auf einem Computer. Dazu müssen<br />

die in der gewählten Programmiersprache formulierten Anweisungen interpretiert und in<br />

endlich viele, einfache, direkt ausführbare Einzelaktionen umgesetzt werden können. Dabei<br />

müssen auch die benötigten Ressourcen zu jeder Zeit endlich bleiben.<br />

Grundsätzliche Fragen bei der Ausführung von Algorithmen<br />

Bei der Ausführung von Algorithmen stellen sich die folgenden grundsätzlichen Fragen:<br />

• Kann jedes Problem durch einen Algorithmus beschrieben werden, also zumindest prinzipiell<br />

bei genügend großem Bemühen gelöst werden?<br />

• Kann jeder Algorithmus in ein Programm übertragen werden? Oder anders ausgedrückt:<br />

Welchen Anforderungen muss eine Programmiersprache genügen, damit jeder Algorithmus<br />

damit formuliert werden kann?<br />

• Ist ein Computer grundsätzlich in der Lage, einen bekannten, als Programm formulierten<br />

Algorithmus auszuführen?<br />

• Was ist ein Computer als formales System?<br />

Die erste dieser Fragen bezieht sich auf die Berechenbarkeit von Problemen, die zweite auf<br />

die Theorie der Programmiersprachen und die dritte auf die Komplexität von Algorithmen<br />

bzw. Programmen. Alle diese Fragen erfordern über die oben gegebenen Definitionen hinaus<br />

eine mathematische Präzisierung des Begriffs Algorithmus. Eine zentrale Aufgabe der<br />

Informatik im Zusammenhang mit der Komplexitätstheorie ist es, nicht nur irgendeinen Algorithmus<br />

zur Lösung eines Problems zu finden, sondern einen möglichst effizienten, wobei<br />

man die Effizienz etwa am Speicherbedarf oder an der benötigten Ausführungszeit messen<br />

kann.<br />

Von grundsätzlicher Bedeutung ist dabei: wie lässt sich ein Computer als abstraktes Modell,<br />

d.h. als ein auf das Wesentliche beschränktes formales System beschreiben? Insbesondere<br />

ist zu klären, inwieweit verschiedene formale Ansätze (Turing-Maschine, Schaltwerke, formale<br />

Sprachen etc.) wirklich vollständige Beschreibungen des Systems Computer bieten und<br />

inwieweit diese zueinander äquivalent sind.


<strong>10</strong> Algorithmen 479<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

<strong>10</strong>.1.2 Entscheidungsproblem und Church-Turing-These<br />

Lange Zeit war die Mehrzahl der Mathematiker der Ansicht, dass man von jeder Aussage<br />

algorithmisch entscheiden könne, ob sie wahr oder falsch sei. Anders ausgedrückt, man<br />

glaubte, dass man immer zeigen könne, jedes Problem sei entweder lösbar oder unlösbar.<br />

Es handelt sich hier um das berühmte Entscheidungsproblem, das um 1930 in einer Konfrontation<br />

zwischen den beiden Mathematikern David Hilbert (1862-1942) und Kurt Gödel<br />

seinen Höhepunkt und seine Lösung fand: Gödel wies in seinem Unvollständigkeitstheorem<br />

[Göd31] im Jahre 1931 nach, dass alle widerspruchsfreien axiomatischen Formulierungen<br />

der Zahlentheorie unentscheidbare Aussagen enthalten. Mit der Erkenntnis, dass eben nicht<br />

jede Aussage algorithmisch entscheidbar ist, war der Nachweis geführt, dass streng algorithmisch<br />

arbeitende Computer prinzipiell nicht jedes Problem lösen können. Dies wurde bemerkenswerterweise<br />

zu einer Zeit entdeckt, als es noch gar keine Computer gab. Vereinfacht<br />

ausgedrückt bedeutet das Unvollständigkeitstheorem auch, dass Wahrheit eine höhere<br />

Qualität besitzt als Beweisbarkeit.<br />

Möchte man beweisen, dass zu einem bestimmten Problem kein Algorithmus zu dessen Lösung<br />

existiert, so muss man den Begriff Algorithmus formalisieren. Hierzu gibt es verschiedene<br />

Ansätze, die teilweise schon in vorausgehenden <strong>Kapitel</strong>n eingeführt worden sind. Verwendet<br />

man das Konzept der Turing-Maschine als Modell zur formalen Beschreibung von<br />

Algorithmen, dann ist jedes Problem algorithmisch lösbar, das als Turing-Maschine dargestellt<br />

werden kann. Ein Computer ist dann äquivalent zu einer universellen Turing-Maschine<br />

U, d.h. zu einer Turing-Maschine, die jede andere Turing-Maschine T simulieren kann. Zur<br />

Programmierung von U muss auf dem Eingabeband von U eine Beschreibung der zu simulierenden<br />

Turing-Maschine T gespeichert werden und außerdem die Eingabe x, die von T<br />

verarbeitet werden soll. Die Eingabe x wird dann von U in genau derselben Weise verarbeitet,<br />

wie dies durch T geschehen würde. In diesem Sinne ist also die universelle Turing-<br />

Maschine U eine abstrakte Beschreibung für jeden Computer.<br />

Es konnte ferner gezeigt werden, dass zu jeder Darstellung eines Algorithmus (der für eine<br />

berechenbare Funktion steht) in Form einer Turing-Maschine die Formulierung desselben<br />

Problems als formale Sprache, als Programm auf einer Registermaschine oder als Schaltwerk<br />

äquivalent ist. Es gibt noch weitere Beschreibungsmöglichkeiten von Algorithmen, etwa<br />

durch rekursive Funktionen, die sich aus einfachen, offensichtlich berechenbaren Funktionen<br />

bilden lassen. Es besteht daher nach A. Church und A. Turing eine sehr starke Evidenz (ohne<br />

dass ein strenger mathematischer Beweis möglich wäre) dafür, dass nicht nur die oben<br />

genannten verschiedenen Möglichkeiten der Formalisierung von Algorithmen gleichwertig<br />

sind und zu denselben Ergebnissen über die Berechenbarkeit von Problemen führen, sondern<br />

dass dies allgemein für alle im intuitiven Sinn „vernünftigen“ Formalisierungen gilt. Dieser<br />

Sachverhalt ist als die Church-Turing-These bekannt. Mit anderen Worten: Wenn ein<br />

Problem nicht durch eine Turing-Maschine gelöst werden kann, so ist es überhaupt nicht<br />

algorithmisch lösbar.<br />

Obwohl die Church-Turing-These wegen des Bezugs auf mathematisch nicht präzisierbare<br />

Begriffe wie „intuitiv“ und „vernünftig“ nicht beweisbar ist, werden Beweise auf der Grundlage<br />

dieser These allgemein akzeptiert. Man definiert auf diesem Hintergrund die zentralen<br />

Begriffe Berechenbarkeit und Entscheidbarkeit, bzw. Beweisbarkeit wie folgt:<br />

Definition<br />

Eine Funktion f(x) heißt berechenbar, wenn es einen Algorithmus gibt, der bei gegebenem x<br />

das Ergebnis f(x) liefert.<br />

Eine gegebene Menge M heißt entscheidbar, wenn es einen Algorithmus gibt, der für jedes<br />

vorgegebene Element x die Aussage liefert, ob x in M enthalten ist oder nicht.


480 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Es muss nicht berechenbare Funktionen geben<br />

Die Vermutung, dass in diesem Sinne nicht jede Funktion berechenbar ist, liegt eigentlich<br />

nahe und lässt sich wie folgt erhärten:<br />

Ein Algorithmus muss wegen der Abbildung auf eine Turing-Maschine in jedem Falle durch<br />

ein Alphabet A mit einem endlichen Zeichenvorrat dargestellt werden können. Im Falle einer<br />

Turing-Maschine ist dazu ja sogar das binäre Alphabet A={0, 1} ausreichend. Wie bereits<br />

früher gezeigt, umfasst der Nachrichtenraum A* zwar unendlich viele, aber abzählbar viele<br />

verschiedene Zeichenreihen. Es gibt daher auch nur abzählbar viele Algorithmen, d.h. alle<br />

Algorithmen könnten unter Verwendung der natürlichen Zahlen im Prinzip durchnummeriert<br />

werden. Außerdem kann jeder Algorithmus nach seiner Definition nur eine Funktion berechnen.<br />

Da aber die Menge aller Funktionen sicher überabzählbar ist (wie die Menge der reellen<br />

Zahlen), folgt, dass es in diesem Sinne nicht berechenbare Funktionen geben muss und<br />

dass diese Menge der nicht berechenbaren Funktionen sogar überabzählbar sein muss.<br />

<strong>10</strong>.1.3 Das Halteproblem<br />

Wie bereits ausgeführt, gibt es Probleme, die nicht berechenbar sind, d.h. Aussagen, von<br />

denen nicht ermittelt werden kann, ob sie wahr oder falsch sind. Hierbei wird nicht nur behauptet,<br />

es gäbe Probleme, zu deren Lösung noch kein Algorithmus bekannt sei, sondern es<br />

wird die viel stärkere Aussage gemacht, dass deshalb kein Algorithmus zur Lösung derartiger<br />

Probleme gefunden werden konnte, weil ein solcher grundsätzlich nicht existiert.<br />

Ein berühmtes Beispiel für ein nicht berechenbares Problem ist das Halteproblem. Dabei<br />

geht es um die Frage, ob es einen Algorithmus gibt, mit dessen Hilfe man für ein beliebiges<br />

Programm P bestimmen kann, ob es mit beliebigen Eingabedaten D jemals stoppen wird<br />

oder nicht. Mit anderen Worten: es soll geprüft werden, ob ein Programm in eine Endlosschleife<br />

geraten kann. In der Praxis verlangt man einfach, dass ein Programm innerhalb einer<br />

bestimmten Zeitspanne zu einem Ende kommen muss, andernfalls wird es vom Bediener<br />

zwangsweise abgebrochen. Für eine theoretische Überlegung ist diese Vorgehensweise<br />

natürlich wenig tauglich.<br />

Der Beweis, dass das Halteproblem tatsächlich nicht berechenbar ist, wird wie folgt geführt:<br />

Man nimmt zunächst an, es existiere ein Algorithmus zur Lösung des Halteproblems. Man<br />

kann dann ein Programm TEST schreiben, das als Eingabedaten das zu testende Programm<br />

P erhält. Da getestet werden soll, ob ein beliebiges Programm P mit beliebigen Eingabedaten<br />

stoppt, kann man insbesondere auch den Programmtext von P selbst als Eingabedaten für<br />

das zu testende Programm P benützen, auch wenn das Programm P damit nicht unbedingt<br />

eine sinnvolle Ausgabe liefert. So abwegig ist dieses Vorgehen aber durchaus nicht: ein Editor<br />

muss beispielsweise auch in der Lage sein, seinen eigenen Programmtext zu editieren.<br />

Das Testprogramm TEST soll nun JA ausgeben, falls P stoppt und NEIN, falls P nicht stoppt,<br />

also in eine Endlosschleife gerät. Damit ergibt sich das folgende Flussdiagramm:<br />

Rufe TEST mit P als Eingabedaten<br />

Prüfe, ob P mit P als Eingabedaten stoppt<br />

P stoppt<br />

Gib aus: JA<br />

P stoppt nicht<br />

Gib aus: NEIN<br />

Abbildung <strong>10</strong>.1.1:<br />

Flussdiagramm des Programms TEST zum Beweis<br />

der Nichtberechenbarkeit des Halteproblems.


<strong>10</strong> Algorithmen 481<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Im nächsten Schritt wird, immer noch unter der Annahme, es gäbe einen Algorithmus zur<br />

Lösung des Halteproblems, das folgende Programm TEST1 konstruiert:<br />

Rufe TEST1 mit P als Eingabedaten<br />

Rufe TEST mit P als Eingabedaten<br />

Prüfe, ob TEST JA oder NEIN ausgibt<br />

TEST gibt JA aus<br />

TEST gibt NEIN aus<br />

Gehe in Endlosschlei- Stoppe<br />

Abbildung <strong>10</strong>.1.2:<br />

Flussdiagramm des Programms TEST1 zum Beweis<br />

der Nichtberechenbarkeit des Halteproblems.<br />

Nun wird das Programm TEST1 mit dem Programmtext von TEST1 als Eingabedaten aufgerufen.<br />

Dies ist erlaubt, da das zu testende Programm ja jedes beliebige Programm sein darf,<br />

also auch TEST1 selbst. Damit ergibt sich aber ein Widerspruch, denn nimmt man an, TEST1<br />

stoppt, so folgt, dass TEST1 nicht stoppt und umgekehrt. Aus diesem Widerspruch folgt<br />

zwingend, dass die ursprüngliche Annahme, es gäbe einen Algorithmus zur Lösung des Halteproblems,<br />

falsch war. Damit ist bewiesen, dass das Halteproblem in der Tat nicht berechenbar<br />

ist.<br />

Beweis durch Widerspruch und Strange Loops<br />

Die zum Beweis der Nichtberechenbarkeit des Halteproblems verwendete Technik des Beweises<br />

durch Widerspruch wird in ähnlichen Zusammenhängen häufig verwendet. Oft soll<br />

die Behauptung bewiesen werden, ein bestimmtes Problem P sei nicht lösbar, d.h. nicht berechenbar.<br />

Man nimmt nun an, es gäbe einen Algorithmus der das Problem P löst. Lässt<br />

sich dann unter Verwendung dieser Annahme ein Programm (das z.B. auf einer Turing-<br />

Maschine oder einer Registermaschine lauffähig wäre) konstruieren, das widersprüchlich<br />

arbeitet, so ist die Annahme falsch und die ursprüngliche Behauptung, P sei nicht lösbar bzw.<br />

nicht berechenbar, ist bewiesen.<br />

Die Essenz dieser auf den ersten Blick etwas spitzfindig anmutenden Beweisführung folgt<br />

aus sich gegenseitig widersprechenden Aussagen (sog. Strange Loops), die typisch für<br />

diese Klasse von Problemen sind. Auch beim Beweis des Unvollständigkeitstheorems hat<br />

Gödel diese Methode angewendet. Man kann solche widersprüchlichen Aussagen auch<br />

sprachlich formulieren, wie folgende Kostprobe zeigt:<br />

„Der folgende Satz ist wahr.“<br />

„Der vorhergehende Satz ist falsch.“<br />

Beweis durch Reduktion<br />

Eine weitere in der Theorie der Berechenbarkeit viel verwendete Beweismethode ist die Reduktion.<br />

Man führt dabei ein ungelöstes Problem auf ein gelöstes zurück. Man sagt, eine<br />

Menge M⊆N ist reduzierbar auf M'⊆N, wenn es eine eindeutige, berechenbare Funktion<br />

f(n):N→N gibt, so dass f(n)∈M' genau dann gilt, wenn n∈M ist. Es gilt dann der Satz:<br />

Ist M auf M' reduzierbar und ist M' entscheidbar, dann ist auch M entscheidbar. Ist dagegen<br />

M' nicht entscheidbar, so ist auch M nicht entscheidbar.


482 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Gelingt es also beispielsweise, durch eine Abbildungsvorschrift ein Problem P auf ein nicht<br />

berechenbares Problem, z.B. das Halteproblem, zurückzuführen, so ist gezeigt, dass auch P<br />

nicht berechenbar ist.<br />

Das Äquivalenzproblem und partielle Berechenbarkeit<br />

Neben dem Halteproblem gibt es eine ganze Reihe weiterer nicht berechenbarer Probleme<br />

mit durchaus praktischer Relevanz, so beispielsweise das Äquivalenzproblem. Dabei geht es<br />

darum, zu entscheiden, ob zwei Programme dieselbe Aufgabe lösen (d.h. in diesem Sinne<br />

zueinander äquivalent sind) oder nicht. Ein Vergleich des Halteproblems und des Äquivalenzproblems<br />

zeigt auch, dass es zwei Klassen der Nichtberechenbarkeit gibt: Im Falle des<br />

Halteproblems wird das zu testende Programm in vielen Fällen stoppen, das hypothetische<br />

Testprogramm wird dann die Antwort JA ausgeben. In diesem Fall kann man auch nachvollziehen,<br />

warum das getestete Programm gerade mit den verwendeten Eingabedaten stoppt.<br />

Man bezeichnet das Halteproblem daher als partiell nicht berechenbar. Das Äquivalenzproblem<br />

ist dagegen so geartet, dass es selbst dann keine allgemein gültige Methode gibt, die<br />

Äquivalenz zweier Programme zu beweisen, wenn diese tatsächlich äquivalent sind.<br />

Sowohl für das Halteproblem als auch für das Äquivalenzproblem muss nochmals betont<br />

werden, dass es um eine allgemeine Methode geht. Es können also in beiden Fällen durchaus<br />

Algorithmen gefunden werden, die für einzelne Programme oder auch eine eingeschränkte<br />

Menge von Programmen das Halteproblem bzw. das Äquivalenzproblem lösen.<br />

<strong>10</strong>.1.4 Primitiv rekursive Funktionen<br />

Für eine detailliertere Untersuchung der Berechenbarkeit von Problemen ist die hier vorgestellte<br />

Möglichkeit der formalen Beschreibung durch rekursive Funktionen sehr nützlich.<br />

Zunächst wird die Klasse der primitiv rekursiven Funktionen betrachtet. Diese entstehen aus<br />

einigen einfachen Grundfunktionen über einem beliebigen Alphabet, auf welche man endlich<br />

viele Operationen anwenden kann. Da jedes Alphabet definitionsgemäß auf die natürlichen<br />

Zahlen mit Null N0 abbildbar ist, genügt es, nur N0 zu betrachten. Die reellen Zahlen werden<br />

so nicht erfasst, was ja auch nicht erforderlich ist, da wegen der notwendigen Stellenzahlbeschränkung<br />

reelle Zahlen ohnehin in diesem Sinne nicht berechenbar sind. Rationale Zahlen,<br />

d.h. Brüche, sind dagegen als Paare von natürlichen Zahlen der Art (Zähler / Nenner)<br />

darstellbar. Hierbei wird ausgenutzt, dass die Menge der rationalen Zahlen abzählbar ist, im<br />

Gegensatz zu der überabzählbaren Menge der reellen Zahlen. Die Abzählbarkeit der rationalen<br />

Zahlen sieht man sofort durch folgendes Schema:<br />

Tabelle <strong>10</strong>.1.1: Ordnet man die rationalen Zahlen als zweidimensionales Schema an, so kann leicht<br />

eine eineindeutige Abbildung (Durchnummerieren) auf die natürlichen Zahlen angegeben werden.<br />

1: 1/1 2: 1/2 6: 1/3 7: 1/4 15: 1/5 16: 1/6 28: 1/7 .....<br />

3: 2/1 5: 2/2 8: 2/3 14: 2/4 17: 2/5 27: 2/6 .....<br />

4: 3/1 9: 3/2 13: 3/3 18: 3/4 26: 3/5 .....<br />

<strong>10</strong>: 4/1 12: 4/2 19: 4/3 25: 4/4 .....<br />

11: 5/1 20: 5/2 24: 5/3 .....<br />

21: 6/1 23: 6/2 .....<br />

22: 7/1.....<br />

.....


<strong>10</strong> Algorithmen 483<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Von diesen Überlegungen ausgehend, wird die Klasse P der primitiv rekursiven Funktionen<br />

wie folgt definiert:<br />

Definition: Die Klasse der primitiv rekursiven Funktionen ist die kleinste Klasse P von Funktionen<br />

über N0 für die gilt:<br />

1. Die konstante Funktion C n ist in P:<br />

∀n ∈N ist C : N → N mit C (x) = 0 ∀x ∈N<br />

in<br />

2 1 2 3 2<br />

n n n n<br />

0 0 0 P<br />

Das Argument x der Funktion C ist hier also ein n-Tupel: C n (x) = C n (x1, x2, . . . xn).<br />

Das Ergebnis ist unabhängig von x immer 0, also eine skalare Größe.<br />

2. Die Nachfolgerfunktion N ist in P:<br />

N: N → N mit N( x) = x +1ist<br />

in P<br />

0<br />

3. Die Projektionsfunktion P ist in P:<br />

∀n∈N und 1 ≤ i ≤ n gilt: P : N → N mit P ( x , x ,.. x ) = x ist in<br />

n n n<br />

i 0 0 i 1 2 n i P<br />

Aus einer Funktion mit n Argumenten liefert also die Projektion Pi das i-te Argument als<br />

Ergebnis. Beispielsweise gilt für n=3 und i=2:<br />

3<br />

P ( x , x , x ) = x<br />

4. Die Substitutionsfunktion ist in P:<br />

Sind h1, h , … h beliebige Funktionen in P und ist g: 2 n 0 0 in P, so gilt:<br />

n<br />

f: N 0 → N 0 mit f(x) = g(h1, h , … h ) ist in P<br />

2 n n<br />

n<br />

0 0<br />

N → N<br />

Die Funktion f entsteht also durch Ersetzen (Substituieren) der Argumente xi der Funktion<br />

g durch die Funktionen hi.<br />

5. Die primitive Rekursion ist in P:<br />

n n+ 2<br />

1<br />

Sind g: N 0 → N 0 und h: N 0 → N 0 in P, so ist auch jede Funktion f: N 0 → N 0 in P,<br />

welche folgende Bedingung erfüllt:<br />

0<br />

+ n<br />

n<br />

f(0,y) = g(y) ∀y<br />

∈ N<br />

f(x+1,y) = h(x,y,f(x,y)) ∀x ∈ N und ∀y<br />

∈ N<br />

Man sagt, f entsteht aus g und h durch primitive Rekursion.<br />

Die Beschränkung auf die natürlichen Zahlen kann ohne weiteres durch Ersetzen von N0<br />

durch A * über einem beliebigen Alphabet A aufgehoben werden. Reelle Zahlen können offenbar<br />

nicht verarbeitet werden, da die Menge der reellen Zahlen nicht abzählbar (überabzählbar)<br />

ist. Reelle Zahlen müssen durch rationale Zahlen, also durch Brüche, angenähert<br />

werden. Brüche können dabei als Paare von natürlichen Zahlen dargestellt werden.<br />

Beispiel: Addition und Multiplikation<br />

Die Additionsfunktion plus(x,y)=x+y soll als primitiv rekursive Funktion dargestellt werden.<br />

Man erhält plus durch primitive Rekursion aus g und h:<br />

plus(0,y) = g(y) = P2 2 (0,y) = y<br />

plus(x+1,y) = h(x,y,plus(x,y)) = N[P3 3 (x,y,plus(x,y))]<br />

Die Multiplikation mul(x,y)=xy lässt sich dann durch die Addition ausdrücken:


484 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

mul(0,y) = C 2 (0,y) = 0<br />

mul(x+1,y) = plus1(x,y,mul(x,y))<br />

mit plus1(x,y,z) = plus[P2 3 (plus1(x,y,z), P3 3 (plus1(x,y,z))]<br />

Weitere Beispiele für primitiv rekursive Funktionen sind die Fakultät, die Bestimmung des<br />

größten gemeinsamen Teilers zweier natürlicher Zahlen etc.<br />

Für primitiv rekursive Funktionen gelten einige wichtige Sätze, die hier ohne Beweis aufgeführt<br />

werden:<br />

• Jede primitiv rekursive Funktion ist berechenbar.<br />

• Jede primitiv rekursive Funktion ist durch eine Turing-Maschine darstellbar, die Umkehrung<br />

gilt jedoch nicht. Es gibt also Turing-Maschinen, die keiner primitiv rekursiver Funktion entsprechen.<br />

• Primitiv rekursive Funktionen sind durch sehr rudimentäre Programmiersprachen (Loop<br />

Programs) mit folgenden Eigenschaften darstellbar [Mey67]:<br />

- Es gibt die Konstante 0.<br />

- Es gibt Zuweisungen der Art Y=X.<br />

- Es gibt die Inkrementierung X=X++.<br />

- Es gibt Verzweigungen (z.B. durch IF).<br />

- Es gibt die Hintereinanderausführung beliebig vieler Anweisungen.<br />

- Es gibt FOR-Schleifen, die eine beliebig lange Sequenz von Anweisungen umfassen, wobei<br />

der Schleifenindex innerhalb der Schleife nicht verändert werden darf (Bounded<br />

Loops).<br />

Man könnte der Sprache sogar noch weitere Einschränkungen auferlegen; dies würde jedoch<br />

zu einer unbequemen Handhabung führen. Jedes in einer derartigen Programmiersprache<br />

geschriebene Programm entspricht einer primitiv rekursiven Funktion. Da nur FOR-<br />

Schleifen mit einer vorab bekannten Anzahl von Schritten aber weder WHILE-Schleifen noch<br />

Sprünge (GOTO) zugelassen sind, kann es in einem solchen Programm keine Endlosschleifen<br />

geben, das Halteproblem spielt hier also keine Rolle.<br />

Es ist offensichtlich, dass die primitiv rekursiven Funktionen eine sehr wichtige Klasse von<br />

Funktionen beschreiben, aber eben nicht alle durch eine Turing-Maschine darstellbaren, also<br />

nicht alle berechenbaren Funktionen. Es ist daher eine Erweiterung dieses Konzepts nötig.<br />

<strong>10</strong>.1.5 µ-rekursive Funktionen und die Ackermann-Funktion<br />

Im Jahre 1928 zeigte Ackermann, dass nicht jede berechenbare Funktion primitiv rekursiv<br />

ist, indem er eine berechenbare, aber nicht primitiv rekursive Funktion angab: die Ackermann-Funktion.<br />

Es ist dies die einfachste bekannte Funktion, die schneller wächst als jede<br />

primitiv rekursive Funktion, also insbesondere auch schneller als die Fakultät und jede Exponentialfunktion.<br />

Die Ackermann-Funktion A(x,y) ist wie folgt definiert:<br />

A(0,y) = y+1<br />

A(x+1,0) = A(x,1)<br />

A(x+1,y+1) = A[x,A(x+1,y)]<br />

Beispiel:<br />

Man erkennt leicht, wie schnell A wächst, wenn man einige Werte berechnet:<br />

A(1,1)=3 A(1,2)=4 A(2,2)=7 A(3,3)=61 A(4,4)><strong>10</strong> <strong>10</strong><strong>10</strong>2<strong>10</strong>0


<strong>10</strong> Algorithmen 485<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Explizite berechnet man A(1,2)=4 wie folgt:<br />

A(1,2) = A(0,A(1,1)) = A(0,A(0,A(1,0))) = A(0,A(0,A(0,1))) = A(0,A(0,2)) = A(0,3) = 4<br />

Die Behauptung, dass A schneller wächst als jede andere primitiv rekursive Funktion lässt<br />

sich folgendermaßen ausdrücken:<br />

∀f( n) ∈P: ∃m∈ N0 : A( n, n) > f( n) ∀ n > m<br />

Möchte man die Ackermann-Funktion programmieren, so ist das durch Ausnutzung der Rekursivität<br />

z.B. in Java oder C sehr einfach. Wie schon früher erwähnt, kann aber grundsätzlich<br />

jede Rekursion auch durch eine Iteration ausgedrückt werden. Im Falle der Ackermann-<br />

Funktion gelingt die iterative Programmierung aber mit der im vorigen Abschnitt kurz skizzierten<br />

einfachen Programmiersprache als Loop-Programm nicht. Man benötigt hier unbedingt<br />

eine Sprache die über WHILE-Schleifen oder wenigstens über Sprungbefehle (GOTO) verfügt.<br />

Die Besonderheit dieser Konstrukte ist, dass im Gegensatz zu FOR-Schleifen der Laufindex<br />

innerhalb der Schleife in Abhängigkeit von Bedingungen jederzeit beliebig geändert werden<br />

kann. Dies hat notwendigerweise zur Folge, dass Endlosschleifen prinzipiell nicht ausgeschlossen<br />

werden können.<br />

Es ist allerdings nicht einfach zu beweisen, dass die Ackermann-Funktion tatsächlich nicht<br />

primitiv rekursiv ist [Her65]. Außerdem findet diese Funktion kaum praktische Anwendungen.<br />

Es gibt jedoch eine ganze Klasse von Funktionen für den täglichen Gebrauch, die nicht primitiv<br />

rekursiv sein können: nämlich Compiler und Interpreter, und zwar auch solche für so<br />

einfache Konstrukte wie Loop-Programme. Der Beweis dafür ist relativ einfach aus dem Beweis<br />

des Halteproblems herleitbar [Abe85].<br />

WHILE-Programme und der µ-Operator<br />

Man kann ferner zeigen, dass man mit einer um WHILE-Schleifen und/oder Sprungbefehle<br />

erweiterten Programmiersprache (WHILE-Programs) alle berechenbaren Funktionen programmieren<br />

kann, so dass hier im Sinne der Church-Turing-These eine Übereinstimmung<br />

mit dem Konzept der Turing-Maschinen besteht. Derartige auf das Wesentliche reduzierte<br />

Sprachen sind ferner zu Registermaschinen äquivalent. Eine solche Äquivalenz lässt sich<br />

aber auch durch eine Erweiterung des Begriffs der Rekursion erreichen. Man ergänzt dazu<br />

die fünf Axiome der primitiv rekursiven Funktionen um ein sechstes Axiom zur Definition der<br />

µ-rekursiven oder unbeschränkt rekursiven Funktionen, die dann alle tatsächlich berechenbaren<br />

Funktionen umfassen:<br />

y0 wenn f(x,y0)=c ist und f(x,y)≠c für alle y


486 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

<strong>10</strong>.1.6 Die bb-Funktion<br />

Definition der bb-Funktion<br />

Als Weiterführung der Ackermann-Funktion gab T. Radò 1961 die bb-Funktion bb(n) an, die<br />

schneller wächst als jede µ-rekursive Funktion [Mor95]. Schreibt man µ für die Klasse der µrekursiven<br />

Funktionen, so lautet diese Behauptung als Formel ausgedrückt:<br />

μ<br />

∀f ( n)<br />

∈ : ∃m<br />

∈ N 0 : bb(<br />

n)<br />

> f ( n)<br />

∀n<br />

> m<br />

Daraus folgt bereits, dass bb(n) selbst keine µ-rekursive Funktion sein kann und daher auch<br />

nicht berechenbar ist. Dennoch lässt sich bb(n) auf einfache Weise definieren:<br />

bb(0) = 0<br />

bb(n) = die maximale Anzahl von aufeinander folgenden Strichen (Einsen), die eine<br />

Turing-Maschine mit n Anweisungen auf ein leeres Band schreibt.<br />

Bekannte bb-Zahlen<br />

bb steht abkürzend für busy beaver und ist von der anschaulichen Vorstellung abgeleitet,<br />

dass jeder Strich für einen Holzstamm steht, den ein eifriger Biber zum Dammbau heranschleppt.<br />

Die Tabelle listet die bislang bekannten bb-Zahlen auf.<br />

Tabelle <strong>10</strong>.1.2: Die bislang bekannten bb-Zahlen<br />

n 0 1 2 3 4 5 >5<br />

bb(n) 0 1 4 6 13 >1915 ?<br />

Vorschrift zur Bestimmung von bb-Zahlen<br />

Offensichtlich ist bb(n) mathematisch korrekt, eindeutig und auf ganz N o definiert, da für jedes<br />

n∈N o auch bb(n) eine ganz bestimmte natürliche Zahl mit endlich vielen Stellen ist. Dazu<br />

folgende Vorschrift zur Bestimmung von bb(n):<br />

1. Man schreibe alle Turing-Maschinen mit T={0,1} der oben beschriebenen Art mit n Anweisungen<br />

auf. Jede Anweisung besteht aus zwei Teilanweisungen, so dass sich insgesamt<br />

2n Teilanweisungen ergeben. In jeder dieser Teilanweisungen gibt es nun zwei Möglichkeiten<br />

für das zu schreibende Zeichen (0 oder 1), zwei Möglichkeiten für den nächsten<br />

Schritt (Rechts oder Links) und n+1 Anweisungsnummern (einschließlich der Anweisungsnummer<br />

0 für HALT) für den folgenden Schritt. Daraus folgt, dass es bei n gegebenen<br />

Anweisungen genau [4(n+1)] 2n verschiedene Turing-Maschinen gibt. Für n=5 sind das<br />

bereits ca. 6.3 * <strong>10</strong> 13 Möglichkeiten.<br />

2. Man suche alle haltenden Turing-Maschinen aus, die auf ein mit Nullen vorbesetztes<br />

Band eine Anzahl von aufeinander folgenden Strichen schreibt. Solche gibt es für jedes n<br />

auf jeden Fall, wie weiter unten gezeigt wird. Es wird dabei übrigens nicht vorausgesetzt,<br />

dass ein allgemeines Verfahren existieren muss, welches in der Lage ist, von jeder beliebigen<br />

Turing-Maschine zu entscheiden, ob diese mit jeder beliebigen Eingabe anhalten<br />

wird oder nicht. Der Fall liegt hier einfacher: erstens ist die Eingabe nicht beliebig, es ist<br />

vielmehr vorausgesetzt, dass das Eingabeband immer mit Nullen vorbesetzt ist; zweitens<br />

ist bei der Berechnung eines bestimmten bb(n) die Anzahl der zu prüfenden Turing-<br />

Maschinen immer endlich, man könnte also für jede dieser Maschinen ein speziell angepasstes<br />

Verfahren entwickeln, um zu entscheiden, ob gerade diese anhält oder nicht.<br />

3. Man prüfe für jede der nach 2. ausgewählten Turing-Maschinen, wie viele Striche sie auf<br />

das Band schreibt, bevor sie anhält. Die größte Anzahl geschriebener Striche ist bb(n).


<strong>10</strong> Algorithmen 487<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

Trotz dieser recht einfach klingenden Vorschrift zur Bestimmung von bb-Zahlen kann man<br />

zeigen, dass bb(n) keine µ-rekursive Funktion sein kann und daher nicht berechenbar ist.<br />

Dies bedeutet nicht, dass nicht einzelne bb-Zahlen bestimmt werden könnten. Vielmehr wird<br />

dadurch ausgesagt, dass kein allgemeiner Algorithmus existiert, der bb(n) für jede beliebige<br />

natürliche Zahl n berechnen kann.<br />

Beweis der Nichtberechenbarkeit der bb-Funktion<br />

Diese Aussage soll nun bewiesen werden. Dazu wird wieder die Technik „Beweis durch Widerspruch“<br />

angewendet. Man geht also von der Annahme aus, es gäbe ein allgemeines Verfahren<br />

(C-Programm, Turing-Maschine, µ-rekursive Funktion, ...), mit dessen Hilfe für jede<br />

natürliche Zahl n der Wert bb(n) bestimmt werden könnte und zeigt, dass sich daraus ein<br />

logischer Widerspruch ergibt. Aus dem Widerspruch folgt, dass die Annahme falsch war, so<br />

dass ihre logische Negation richtig sein muss. Dazu geht man in folgenden Schritten vor:<br />

1. Schritt:<br />

Gegeben seien zwei Turing-Maschinen T1 mit n Anweisungen und T2 mit m Anweisungen.<br />

T1T2 sei die Turing-Maschine, die man erhält, wenn T2 hinten an T1 angehängt wird. Man<br />

muss dazu in T2 jede Anweisungsnummer i>0 durch n+i ersetzen und in T1 jede 0 (also<br />

HALT) durch n+1 ersetzen. T1T2 führt also in allen Fällen, in denen T1 anhält, unmittelbar<br />

danach T2 aus.<br />

2. Schritt<br />

Es gibt eine Turing-Maschine T4 mit 4 Anweisungen, die 12 Striche auf ein zunächst leeres<br />

Band schreibt, und zwar 2 davon rechts vom Startfeld. Der Schreib/Lese-Kopf bleibt nach 96<br />

Schritten 3 Felder links von den geschriebenen Strichen stehen:<br />

T4: 1<br />

0 1 L 2 0 1 R 1 0 0 R 0 0 1 R 4<br />

2 3 4<br />

1 0 L 3 1 1 L 1 1 1 L 4 1 0 R 2<br />

Startposition<br />

. . . 0 0 0 0 111111111111 0 0 0 0 . . .<br />

Endposition<br />

Abbildung <strong>10</strong>.1.3:<br />

Die angegebene Turing-Maschine schreibt<br />

12 Striche auf ein anfangs leeres Band.<br />

T4 lässt sich gemäß Schritt 1 beliebig oft hintereinander ausführen: T4T4 schreibt 24 aufeinander<br />

folgende Striche, (T4) n schreibt 12n Striche. Durch Ersetzen der Zeile 0 0 L 0 in Anweisung<br />

3 durch 0 0 R 0 modifiziert man T4 in T4'. Auch T4' schreibt 12 Striche, hält aber auf<br />

dem ersten leeren Feld links neben den Strichen. Für jede natürliche Zahl n ist dann Dn =<br />

(T4) n-1 T4' eine Turing-Maschine mit 4n Anweisungen, die 12n Striche auf ein zunächst leeres<br />

Speicherband schreibt und dann gleich links vom zuletzt geschriebenen Strich hält.<br />

3. Schritt<br />

Offenbar ist bb(n) streng monoton wachsend, d.h. es gilt bb(n+1)>bb(n) für alle n: Hat man<br />

nämlich eine Turing-Maschine T mit n Anweisungen, die bb(n) entspricht, so kann man daraus<br />

eine Turing-Maschine T' mit n+1 Anweisungen machen, die einen Strich mehr schreibt<br />

als T. Man ersetzt dazu lediglich alle 0-Anweisungsnummern (d.h. HALT) durch n+1 und fügt<br />

folgende Anweisung an, die einen zusätzlichen Strich auf das Band schreibt.<br />

n+1<br />

0 1 L 0<br />

1 1 L n+1


488 <strong>10</strong> Algorithmen<br />

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯<br />

4. Schritt<br />

Nun wird angenommen, es existiere eine Turing-Maschine Tn mit k Anweisungen, die bb(n)<br />

für jede natürliche Zahl n berechne. Man kann o.B.d.A. voraussetzen, dass sowohl die Eingabe<br />

n als auch das Ergebnis bb(n) durch direkt aufeinander folgende Striche auf einem anfangs<br />

leeren Band dargestellt werden. Tn soll nun auf dem ersten leeren Feld links von den<br />

als Eingabe dienenden n Strichen starten, diese lesen, bb(n) berechnen, das Ergebnis in<br />

Form von Strichen links anschließend an die n schon vorhandenen Striche auf das Band<br />

schreiben und schließlich gleich links neben dem letzten Strich halten. Man betrachtet nun<br />

die Turing-Maschine DnTn. Startet man DnTn auf einem leeren Band, so werden zunächst<br />

durch Dn 12n aufeinander folgende Striche geschrieben, die von Tn als Eingabe gelesen<br />

werden, so dass nun bb(12n) berechnet wird. Das Ergebnis wird in Form von Strichen an die<br />

schon vorhandenen 12n Striche angehängt, so dass nun bb(12n)+12n Striche auf dem Band<br />

stehen. In diesem Sinne ist DnTn selbst ein Kandidat für eine Biber-Turing-Maschine. Da Dn<br />

aus 4n und Tn aus k Anweisungen besteht, hat DnTn 4n+k Anweisungen. Eine Turing-<br />

Maschine, die bb(4n+k) berechnet, schreibt also mindestens so viele Striche wie DnTn, das ja<br />

bb(12n)+12n Striche schreibt. Es gilt also:<br />

bb(4n+k) > bb(12n)+12n<br />

Da k eine feste Zahl ist, muss es eine Zahl m geben, so dass 12m > 4m+k gilt. Weil aber nur<br />

k fest vorgegeben ist, nicht aber n, kann an Stelle von n auch m eingesetzt werden. Daraus<br />

folgt jedoch bb(4m+k) > bb(12m) + 12m was wegen der strengen Monotonie der bb-Funktion<br />

ein Widerspruch ist. Man macht sich dies leicht an einem Beispiel klar: Wurde k=7 gewählt,<br />

so zeigt sich bereits für m=5 der Widerspruch in Form einer offensichtlich nicht erfüllten Ungleichung:<br />

bb(27) > bb(60) + 60<br />

Die Annahme, es gebe ein Tn, das bb(n) für jede natürliche Zahl n berechnet, muss also<br />

falsch sein.<br />

1985 wurde die unten dargestellte Turing-Maschine gefunden, die 1915 Striche auf ein anfangs<br />

leeres Band schreibt. Es ist dies ein aussichtsreicher Kandidat für bb(5).<br />

0 1 R 2 0 0 L 1 0 1 L 1 0 1 L 2 0 0 R 4<br />

bb(5)?: 1 2 3 4 5<br />

1 1 L 3 1 0 L 4 1 1 L 0 1 1 R 5 1 0 R 2

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!