Theoretische Informatik

Mathematics
Computer Science
Eine Zusammenfassung von Einführung in die Theoretische Informatik.
Published

July 30, 2025

\[ \newcommand{\SIM}{\mathrm{SIM}} \newcommand{\LV}{\mathrm{LV}} \newcommand{\ZV}{\mathrm{ZV}} \newcommand{\ID}{\mathrm{ID}} \newcommand{\SUB}{\mathrm{SUB}} \newcommand{\PR}{\mathrm{PR}} \newcommand{\MIN}{\mathrm{MIN}} \newcommand{\pmd}{\mathrm{md}} \newcommand{\psum}{\mathrm{sum}} \newcommand{\pprod}{\mathrm{prod}} \newcommand{\pdiv}{\mathrm{div}} \newcommand{\pexp}{\mathrm{exp}} \]

About

Disclaimer: Diese Zusammenfassung ist nicht als Ersatz für die Vorlesung oder die Übung gedacht, sondern soll lediglich als Ergänzung dienen. Es ist möglich, dass Fehler enthalten sind. Bitte überprüft die Zusammenfassung sorgfältig und meldet eventuelle Fehler oder Unklarheiten an .

https://daniel.schwarzenbach.dev/

Mochi Flashcards

Um sich effektiv auf die Prüfung vorzubereiten, empfehle ich die App Mochi: https://mochi.cards/

Ich habe dafür eine Sammlung von Flashcards erstellt, die die wichtigsten Konzepte abdecken sollte: Download Flashcards

TheoreticalComputerScience.jl

Für Theoretische Informatik gibt es ein Julia-Package, das die meisten der in der Vorlesung behandelten Konzepte implementiert.

Install Julia: https://julialang.org/downloads/

Github Repo: https://github.com/daniel-schwarzenbach/TheoreticalComputerScience.jl

Es ist auf GitHub verfügbar und kann mit dem folgenden Befehl installiert werden:

using Pkg
Pkg.add(url="https://github.com/daniel-schwarzenbach/TheoreticalComputerScience.jl")

um es zu verwenden, können Sie es einfach importieren:

using TheoreticalComputerScience

References

Aufbauend auf der Vorlesung von Prof. Dr. Sven Kosub, Konstanz, Sommersemester 2026.

Kahoot!

Für die Übungen erstelle ich Kahoot!-Quizzes, um die wichtigsten Konzepte zu überprüfen.

Kahoot Nr.1

  • Wörter
  • Sprachen
  • Codierungen
  • Hüllen Operatoren

https://create.kahoot.it/share/ti1-worter-sprachen-dya-cod-hullen-op/3cd781db-004c-4155-9ca0-c44467558ad4

Kahoot Nr.2

  • RAM
  • Algebraische Erzeugung von Funktionen
  • \(\textbf{FUNK}\)

https://create.kahoot.it/share/ti2-funk-ram/02b7c3d5-3dbd-4e3c-b125-6b22cc196de0

Kahoot Nr.3

  • Turingmaschinen

https://create.kahoot.it/share/ti3-turingmaschinen/ca3dc653-d9e9-416c-a3d8-b2d38bec5482

Kahoot Nr.4

  • Turingmaschinen (wiederholung)
  • Algebraische Erzeugung von Funktionen (wiederholung)
  • Partiell rekursive Funktionen

https://create.kahoot.it/share/ti4-prim/b51cccae-b85e-45aa-ae69-befbd21aa05c

Kahoot Nr.5

  • PRIM

https://create.kahoot.it/share/ti5-part/5b801a03-7b3e-41c9-89bf-3616bd8624d6

Kahoot Nr.6

  • Entscheidbarkeit
  • Aufzählbarkeit

https://create.kahoot.it/share/ti6/e0764dd0-19e0-4698-b97c-04ebcf5dfe1c

Kahoot Nr.7

  • Unentscheidbare Mengen
  • many-to-one Reduktion
  • Satz von Rice

https://create.kahoot.it/share/ti7/07363b0e-148b-4ec9-b2bc-e2b82a30c3a2

Kahoot Nr.8

  • Laufzeit von Algorithmen
  • Die Klasse \(\textbf{P}\)

https://create.kahoot.it/share/ti8/e24b58cf-e922-4981-ae41-0ece5cef47f2

Kahoot Nr.9

  • Nichtdeterministische Maschienen
  • Die Klasse \(\textbf{NP}\)

https://create.kahoot.it/share/ti9/684df261-8e90-4889-adb2-b387989fbee8

Kahoot Nr.10

  • NP-Vollständigkeit
  • \(≤_m^p\)-Reduktionen

https://create.kahoot.it/share/ti10/f9abf091-9933-4e79-8c76-77993994ee9a

Kahoot Nr.11

  • \(≤_m^p\)-Reduktionen
  • Endliche Automaten
  • Reguläre Mengen

https://create.kahoot.it/share/ti11/fb6381fc-e727-4945-b901-58ed20462ae8

Kahoot Nr.12

  • Reguläre Sprachen
  • Pumping Lemma für reguläre Sprachen
  • rechtslineare Grammatiken

https://create.kahoot.it/share/ti12/d863e913-b842-4484-b494-e96ad6c1076b

Kahoot Nr.13

  • EA → REG algorithmus
  • Chomsky Hierarchie
  • Kontextfreie Sprachen
  • Chomsky Normalform
  • Pumping Lemma für kontextfreie Sprachen

https://create.kahoot.it/share/ti13/18e3f518-ea92-4fe8-94dd-c943484b8a27

Prüfungsquiz

https://create.kahoot.it/share/ti-prufungsquiz/2671a721-9184-4fc5-a5ca-e24c6d3c764f

Mathematische Grundlagen

Wörter und Sprachen

Sei \(Σ\) eine endliche nichtleere Menge von Zeichen/Characters

  • \(Σ\) heißt Alphabet
  • Elemente von \(Σ\) heißen Symbole, Buchstaben, Zeichen
  • ein Wort x(über \(Σ\)) ist eine endliche Aneinanderreihung von Buchstaben
  • \(|x|\) bezeichnet Länge von \(x\)
  • \(|x|ₐ\) bezeichnet die Anzahl des Vorkommens von \(a ∈ Σ\) in \(x\)
  • \(ε\) ist das leere Wort
  • \(Σ^*\) ist die Menge aller Wörter über \(Σ\)
  • \(Σ^+\) := \(Σ^* ∖ \{ε\}\)
  • Eine Menge \(L ⊆ Σ^*\) heisst formale Sprache über \(Σ\)
  • \(Σ = \{a, b\}\), \(x = aababb\), \(|x| = 6\), \(|x|_a = 3\), \(|x|_b = 3\)

Operationen auf Wörtern

Seien \(x = a_1 ... a_n, \quad y = b_1 ... b_n\) zwei Wörter

  • Konkatenation: \(xy = a_1a_2...a_nb_1b_2...b_n\)
  • Spiegelwort: \(x^R = a_na_{n-1}...a_1\)
  • Potenz: \(x^0 = ε\), \(x^k = x^{k-1}x\)
  • \(Σ = \{a, b\}\), \(x = aababb\), \(y = ab\), \(xy = aababbab\), \(x^R = bbabaa\), \(x^3 = aababbaababbaababb\)

Operationen auf Sprachen

  • konkatenation: \(L⋅L' = \{xy ∣ x ∈ L, y ∈ L'\}\)
  • iteration
    • \(L^0 = \{ε\}\)
    • \(L^k = L^{k-1}L\)
    • \(L^* = ⋃_{k ∈ ℕ} L^k\)
    • \(L^+ = ⋃_{k ∈ ℕ₊} L^k\)

Insbesondere gilt: \(\{x\}^k = \{x^k\} \quad ∀x ∈ Σ^*, \; k ≥ 0\)

  • \(Σ = \{a, b\}\), \(L = \{a, ab\}\), \(L^2 = \{aa, aab, aba, abab\}\), \(L^* = \{ε, a, ab, aa, aab, aba, abab,...\}\)

Zeit: 5 min

Gegeben sei die Paarungsfunktion \[⟨⋅,⋅⟩_2:(\{1,2\}^∗)^2 → \{1,2\}^*\] \[(x,y)↦1^{|x|}xy\]

  1. Bestimmen Sie
  • \(⟨1,11⟩_2\).
  • \(⟨11,1⟩_2\).
  1. Wie viele Paare \((x,y)\) erfüllen \(⟨x,y⟩_2 = 1⁷\)?
  • \(⟨1,11⟩ = 1^1 * 1 * 11 = 1⁴\)

  • \(⟨11,1⟩ = 1^2 * 11 * 1 = 1⁵\)

\(⟨111,1⟩ = 1^3 * 111 * 1 = 1⁷\)

\(⟨11,111⟩ = 1^2 * 11 * 111 = 1⁷\)

\(⟨1,1⁵⟩ = 1^1 * 1 * 1⁵ = 1⁷\)

\(⟨ε, 1⁷⟩ = 1^0 * ε * 1⁷ = 1⁷\)

Somit gibt es 4 Paare \((x,y)\), die \(⟨x,y⟩_2 = 1⁷\) erfüllen.

Zeit: 15 Minuten

Betrachten Sie die folgende Paarungsfunktion:

\[ ⟨⋅,⋅⟩_2 : (\{1,2\}^*)^2 → \{1,2\}^* \] \[ (x,y) ↦ 1^{|x|}2xy \]

Mit Hilfe von \(⟨⋅,⋅⟩_2\) wird induktiv eine Kodierung \(⟨x_1,...,x_n⟩\) beliebig langer Tupel \((x_1,...,x_n)\) von Wörtern aus \(\{1,2\}^*\) festgelegt:

\[ ⟨x_1⟩ = ⟨dya(1),x_1⟩_2, \] \[ ⟨x_1,...,x_n⟩ = ⟨⟨dya(n),x_1⟩_2,⟨x_2,...,x_{n}⟩⟩_2 \quad \text{ für } n > 1 \]

  1. Bestimmen Sie das Wort \(y\) mit \(⟨x,y⟩_2 = 111122121212221\).

\(|x| = 4\), da \(1111\) am Anfang steht. Somit ist \(x = 2121\). \[ ⇒ \underline{\underline{y = 212221}} \]

  1. Bestimmen Sie das Wort \(⟨1,2,1⟩\).

\(⟨1,2,1⟩ = ⟨\underbrace{⟨11,1⟩_2}_{11211},⟨2,1⟩⟩_2\)

\(= ⟨112111,⟨\underbrace{⟨2,2⟩_2}_{1222},⟨1⟩⟩_2⟩_2\)

\(= ⟨112111,⟨1222,\underbrace{⟨1,1⟩_2}_{1211}⟩_2⟩_2\)

\(= ⟨112111,\underbrace{⟨1222,1211⟩_2}_{1111212221211}⟩_2\)

\(= \underline{\underline{11111121121111111212221211}}\)

  1. Bestimmen Sie das Tupel \((x_1, ..., x_n)\) mit \(⟨x_1,...,x_n⟩ = 12\).

Es gibt kein solches Tupel. Angenommen, es existieren \(x, y\) mit \(⟨x,y⟩_2 = 12\). Dann führt eine Fallunterscheidung über \(|x|\) zum Widerspruch:

\(|x|\) \(⟨x,y⟩_2 = 1^{|x|}2xy\) Problem
\(0\) \(= 2y\), beginnt mit \(\mathtt{2}\) \(12\) beginnt mit \(\mathtt{1}\) ✗
\(1\) Länge \(\geq 1+1+1 = 3\) \(|12| = 2\) ✗
\(\geq 2\) Länge \(\geq 2+1+2 = 5\) \(|12| = 2\) ✗

Widerspruch in allen Fällen \(\Rightarrow\) es gibt kein Tupel \((x_1,...,x_n)\) mit \(⟨x_1,...,x_n⟩ = 12\).

\(\square\)

  1. Ist \(⟨⋅,⋅⟩_2\) injektiv? Begründen Sie Ihre Antwort.

Ja. Aus \(⟨x,y⟩_2 = 1^{|x|}2xy\) lassen sich \(x\) und \(y\) eindeutig rekonstruieren:

  1. Zähle die führenden Einsen \(\Rightarrow\) ergibt \(|x|\).
  2. Überspringe das Trennzeichen \(\mathtt{2}\).
  3. Lies die nächsten \(|x|\) Zeichen \(\Rightarrow\) ergibt \(x\).
  4. Der Rest ist \(y\).

Da die Dekodierung eindeutig ist, ist \(⟨⋅,⋅⟩_2\) injektiv.

\(\square\)

  1. Ist \(⟨⋅,⋅⟩_2\) surjektiv? Begründen Sie Ihre Antwort.

Nein. Jedes Wort im Bild von \(⟨⋅,⋅⟩_2\) enthält mindestens ein Trennzeichen \(\mathtt{2}\) (an Position \(|x|+1\)). Das Wort \(\mathtt{1} \in \{1,2\}^*\) enthält jedoch keine \(\mathtt{2}\), und liegt somit nicht im Bild.

\(\square\)

Kodierung von Zahlen

Unär

  • \(\text{un} : ℕ → \{a\}^*\)
  • \(n ↦ a^ⁿ\)
  • ist bijektiv
  • \(\text{un}(5) = \textit{IIIII}\)

Dezimal

  • \(\text{dec}: ℕ → \{\textit{0,1,...,9}\}^*\)
  • \(\text{dec}(0) = \textit{0}\)
  • \(\text{dec}(n) = a_ma_{m-1}...a_1a_0\)
  • \(\text{dec}\) ist injektiv aber nicht surjektiv

\(\text{dec}(18) = \textit{18}\)

Binär

  • \(\text{bin}: ℕ → \{\textit{0,1}\}^*\)
  • \(\text{bin}(0) = 0\)
  • \(\text{bin}(n) = a_ma_{m-1}...a_1a_0\)
  • \(\text{bin}\) ist injektiv aber nicht surjektiv

\(\text{bin}(18) = \textit{1'0010}\)

k-adisch

  • \(\text{ad}_k: ℕ → \{\textit{1,...,k}\}^*\)
  • \(\text{ad}_k(0) = ε\)
  • \(\text{ad}_k(n) = a_ma_{m-1}...a_1a_0\)
  • \(\text{ad}_k\) ist bijektiv

Dyadische Codierung

dyadisch: \(\text{dya} := \text{ad}_2\)

dec(n) dya(n) bin(n) \(\text{ad}_3(n)\)
0 ε 0 ε
1 1 1 1
2 2 10 2
3 11 11 3
4 12 100 11
5 21 101 12
6 22 110 13
7 111 111 21
8 112 1000 22

Die dyadische Kodierung natürlicher Zahlen ist die wie folgt rekursiv definierte, bijektive Abbildung \(\text{dya}: ℕ → \{\textit{1,2}\}^*\):

\[ \text{dya}(n) = \begin{cases} ε & \text{falls } n = 0 \\ \text{dya}(\frac{n-2}{2}) \textit{2} & \text{falls } n > 0 \text{ und gerade} \\ \text{dya}(\frac{n-1}{2}) \textit{1} & \text{falls } n \text{ ungerade} \\ \end{cases} \]

Die Umkehrfunktion \(\text{dya}^{-1} : \{\textit{1,2}\}^* → ℕ\) ist wie folgt definiert:

\[ \text{dya}^{-1}(a_{n-1}, ..., a_{0}) = \sum_{i = 0}^{n-1} a_i ⋅ 2^i \]

Anmerkung:

\[ \text{dya}(2^n - 1) = {\textit{1}}^n = \text{bin}(2^n - 1) \]

Als julia-Funktion könnte die dyadische Kodierung wie folgt implementiert werden:

function dya(n::Integer)::String
  if n == 0
    return ""
  elseif n % 2 == 0
    return dya((n-2) ÷ 2) * "2"
  else
    return dya((n-1) ÷ 2) * "1"
  end
end
function dya⁻¹(str::String)::Integer
  n = 0 # resultat
  i = 0 # laufindex
  for c ∈ reverse(str)
    if      c == '1'
      n += 2^i
    elseif  c == '2'
      n += 2 * 2^i
    end
    i += 1
  end
  return n
end
  • Zeit: 5 Minuten

Machen Sie sich mit den Algorithmen vertraut und:

Bestimmen Sie

  • \(\text{dya}(44)\)
  • \(\text{dya}(75)\)
  • \(\text{dya}^{-1}(112)\)
  • \(\text{dya}^{-1}(2221)\)
  • \(\text{dya}(44) = \text{dya}(21)\textit{2} = \text{dya}(10)\textit{12} = \text{dya}(4)\textit{212} = \textit{12212}\)
  • \(\text{dya}(75) = \text{dya}(37)\textit{1} = \text{dya}(18)\textit{11} = \text{dya}(8)\textit{211} = \text{dya}(3)\textit{2211} = \text{dya}(1)\textit{12211} = \textit{112211}\)
  • \(\text{dya}^{-1}(\textit{112}) = 2+2+4 = 8\)
  • \(\text{dya}^{-1}(\textit{2221}) = 1 + 4 + 8 + 16 = 29\)

Das TheoreticalComputerScience.jl Package enthält Implementierungen der dyadischen Kodierung.

@show dya⁻¹("11211")
@show dya⁻¹("2222212122")
@show dya(420)
nothing # remove output
dya⁻¹("11211") = 35
dya⁻¹("2222212122") = 2026
dya(420) = "21211212"

Hüllenoperator

Sei A eine Menge. Eine totale Funktion \(Γ:\mathcal{P}(A) → \mathcal{P}(A)\) heißt Hüllenoperator über A, falls folgende Bedingungen gelten:

  • Einbettung: \(B ⊆ Γ(B)\) für alle \(B ⊆ A\)
  • Monotonie: \(B ⊆ C ⇒ Γ(B) ⊆ Γ(C)\) für alle \(B,C ⊆ A\)
  • Abgeschlossenheit: \(Γ(Γ(B)) ⊆ Γ(B)\)

Für \(A = ℕ\) und \(|\)(Teilbarkeit) ist \(Γ(B) = \{n ∣ \text{es gibt } m ∈ B \text{ mit } n|m\}\) ein Hüllenoperator. Insbesondere gilt:

  • \(Γ(\{ 0 \}) = ℕ\)
  • \(Γ(\{24\}) = \{1,2,3,4,6,12,24\}\)
  • \(Γ(\{13\}) = \{1, 13\}\)

Ein Hüllenoperator \(Γ: \mathcal{P}(A) → \mathcal{P}(A)\) heißt algebraisch, falls gilt:

Für alle \(B ⊆ A\) und für alle \(b ∈ Γ(B)\) gibt es endliches \(B_0 ⊆ B\) mit \(b ∈ Γ(B_0)\)

Definiere \(Γ:\mathcal{P}(ℕ) → \mathcal{P}(ℕ)\) wie folgt:

\[ Γ(B) = \begin{cases} B & \text{falls } |B| < \infty \\ ℕ & \text{sonst} \end{cases} \]

Dann ist \(Γ\) ein Hüllenoperator. Es gilt \(Γ(ℕ_+) = ℕ, \; 0 ∈ Γ(ℕ_+)\)

aber: \(0 ∉ Γ(B_0) = B_0\) für endliche \(B_0 ⊆ ℕ_+\)

Abgeschlossenheit unter Operation \(O\)

Ein Funktion \(O: A^s → A\) heißt \(s\)-stellige Operation auf \(A\).

Sei \(O: A^s → A\) eine \(s\)-stellige Operation auf \(A\). Eine Menge \(C ⊆ A\) heißt abgeschlossen unter \(O\) falls gilt: \[a_1,...,a_s ∈ C \quad ⇒ \quad O(a_1,...,a_s) ∈ C\]

Seien \(O_i: A^{s_i} → A\) für \(i ∈ \{1,...,k\}\) Operationen auf \(A\), \(B ⊆ A\). Dann ist \(Γ_{O_1,...,O_k}(B)\) die kleinste Menge, die \(B ⊆ A\) enthält und abgeschlossen ist unter \(O_1,...,O_k\) d.h.

\[Γ_{O_1,..,O_k} (B) = ⋂ \{C ∣ B ⊆ C, \; C \text{ abgeschlossen unter } O_1, ..., O_k\}\] \(Γ_{O_1,..,O_k}\) heisst der durch \(O_1,...,O_k\) erzeugte Hüllenoperator.

  • \(Γ_{\text{sum}}(\{1\}) = ℕ_+\) da \(1+1=2, 1+2=3, 2+2=4,...\)
  • \(Γ_{\text{prod}}(\{1\}) = \{1\}\) da \(1⋅1=1\)
  • \(Γ_{\text{sum,prod}}(\{2, 3\}) = \{x∈ℕ ∣ x = 2^a ⋅ 3^b \text{ mit } a+b ≥ 1\}\)

Anmerkung:

\(Γ_{O_1,..,O_k}\) ist ein algebraischer Hüllenoperator.

Betrachten Sie den folgenden gerichteten Graphen \(([6], R_0)\) der das Hasse-Diagramm einer Halbordnung \(⪯_a\) darstellt:

Hasse-Diagramm

Es gilt nun: \(1 ⪯_a 6\), \(2 \not ⪯_a 4\) und \(3 ⪯_a 3\).

Ein Hasse-Diagramm enthält bereits die komplette Information über die Halbordnung. Mithin ist die Halbordnung \(⪯_a\) gegeben durch die Reflexive und Transitive Hülle der Relation \(R_0\). Das heißt, \[ Γ_{\text{reflexiv, transitiv}}(R_0) \; = \; ⪯_a \]

Der Grund warum wir für Halbordnungen Hasse-Diagramme angeben ist, dass sie viel kleiner und lesbarer sind als einer Graph einer Halbordnung.

Man betrachte den Graphen \(([6], Γ_{\text{reflexiv, transitiv}}(R_0))\):

Graph der Halbordnung
  • Zeit: 5 min

Definiere die Operatoren \(O_{\text{linksreflexiv}}\), \(O_{\text{rechtsreflexiv}}\) und \(O_{\text{transitiv}}\) so dass:

\[ Γ_{\text{reflexiv, transitiv}} = Γ_{O_{\text{linksreflexiv}}, O_{\text{rechtsreflexiv}}, O_{\text{transitiv}}} \]

Hinweis: Die Operatoren \(O\) bilden von \(R_0^n → R_0\) ab, wobei \(n\) die Anzahl der Argumente ist, die \(O\) benötigt.

\[O_{\text{linksreflexiv}}(a,b) \;:=\; (a,a)\]

\[O_{\text{rechtsreflexiv}}(a,b) \;:=\; (b,b)\]

\[ O_{\text{transitiv}}\bigl((a,b),\,(c,d)\bigr) \;:= \begin{cases} (a,d) & \text{falls } b = c \\ (a,b) & \text{sonst} \end{cases} \]

  • Zeit: 5 Minuten

Gilt: \(Γ_{\text{sum}}(B) = Γ_{\text{sum,prod}}(B) \quad\) für alle \(B ⊆ ℕ\)?

Beweisen oder wiederlegen Sie diese Aussage.

Ja.

Beweis:

\(⊆\):

\(Γ_{\text{sum,prod}}(B)\) enthält \(B\) und ist abgeschlossen unter Summe. Da \(Γ_{\text{sum}}(B)\) die kleinste Menge abgeschlossen unter Summe ist, folgt \(Γ_{\text{sum}}(B) ⊆ Γ_{\text{sum,prod}}(B)\).

\(⊇\):

Es genügt zu zeigen, dass \(Γ_{\text{sum}}(B)\) auch unter Multiplikation abgeschlossen ist. Dann ist \(Γ_{\text{sum}}(B)\) eine Menge, die \(B\) enthält und unter beiden Operationen abgeschlossen ist, und da \(Γ_{\text{sum,prod}}(B)\) die kleinste solche ist, folgt \(Γ_{\text{sum,prod}}(B) ⊆ Γ_{\text{sum}}(B)\).

z.Z.: \(Γ_{\text{sum}}(B)\) ist abgeschlossen unter Multiplikation.

Seien \(a, b ∈ Γ_{\text{sum}}(B)\) beliebig. Wir zeigen \(a ⋅ b ∈ Γ_{\text{sum}}(B)\) per Induktion über \(b ∈ ℕ\):

IA:

  • \(b = 0\):
    • Falls \(0 ∈ B\), dann \(0 ∈ Γ_{\text{sum}}(B)\) und \(a ⋅ 0 = 0 ∈ Γ_{\text{sum}}(B)\).
    • Falls \(0 ∉ B\), ist auch \(0 ∉ Γ_{\text{sum,prod}}(B)\).
  • \(b = 1\): \(a ⋅ 1 = a ∈ Γ_{\text{sum}}(B)\).

IS: \(b ≥ 2\):

  • IV: \(a ⋅ (b-1) ∈ Γ_{\text{sum}}(B)\).

Da \(a ∈ Γ_{\text{sum}}(B)\) und \(Γ_{\text{sum}}(B)\) unter der Addition abgeschlossen ist, gilt:

\[ a ⋅ b = \underbrace{a ⋅ (b-1)}_{∈\, Γ_{\text{sum}}(B)} + \underbrace{a}_{∈\, Γ_{\text{sum}}(B)} ∈ Γ_{\text{sum}}(B). \]

Damit ist \(Γ_{\text{sum}}(B)\) abgeschlossen unter Multiplikation.

\[ ⇒ \]

\(Γ_{\text{sum}}(B) = Γ_{\text{sum,prod}}(B)\) für alle \(B ⊆ ℕ\).

\(\square\)

  • Zeit: 8 Minuten

Sie haben in der Vorlesung bereits die konvexe Hülle von Punkten kennengelernt. Sei \(B ⊆ ℝ^n\) eine endliche Menge von Punkten mit \(|B|>1\).

Konvexe Hülle
  1. Definiern Sie den Konvexen Hüllenoperator \(Γ_{\text{konvex}}(B)\) formal korrekt.

\[ \Gamma_{\text{konvex}}(B) = \{x ∈ ℝ^n \mid \left(∃k ∈ ℕ_+,\; ∀i ∈ [k], \; ∃ b_i ∈ B, \; ∃ λ_i ≥ 0\right) \; \left[x = λ_1 b_1 + ... + λ_k b_k \; ∧ \;\sum_{i=1}^k λ_i = 1 \right]\} \]

  1. Können sie Operatoren \(O_1,...,O_k\) finden sodass \(\Gamma_{\text{konvex}} = \Gamma_{O_1,...,O_k}\)? Wenn ja, geben Sie die Operationen an. Wenn nein, begründen Sie warum nicht.

Nein.

Startet man mit einer endlichen Menge \(B\) und wendet endlich viele solcher Operationen an, so ist \(Γ_{O_1,...,O_k}(B)\) als algebraische Hülle abzählbar (jedes Element entsteht durch endlich viele Operationsanwendungen auf endlich viele Startelemente).

Die konvexe Hülle von bereits zwei verschiedenen Punkten \(a, b ∈ ℝ^n\) ist jedoch die Strecke \(\overline{ab}\), die überabzählbar viele Punkte enthält.

\[|\Gamma_{O_1,...,O_k}(B)| ≤ |ℕ| < |ℝ| ≤ |\Gamma_{\text{konvex}}(B)|\]

Somit kann kein Satz von Operationen \((ℝ^n)^{s_i} → ℝ^n\) den konvexen Hüllenoperator erzeugen.

\(\square\)

Algebraische Erzeugung von Funktionen

Operationen auf \(ℕ\)

Wir definieren die folgenden Operatoren \(ℕ²\to ℕ\):

\[\text{sum}(x,y) = x+y\]

\[ \text{md}(x,y) = x ∸ y = \begin{cases} x-y & \text{falls } x > y \\ 0 & \text{sonst} \end{cases} \]

\[\text{prod}(x,y) = x⋅y\]

\[\text{div}(x,y) = \begin{cases} \left\lfloor \frac{x}{y} \right\rfloor & \text{ falls } y > 0 \\ x & \text{ falls } y = 0 \end{cases} \]

\[\exp(x,y) = x^y\]

Weitere Operationen die wir definieren sind:

  • Increment:
    • \(\text{S}: ℕ → ℕ\)
    • \(\text{S}(x) = x + 1\)
  • Projektion/Identität:
    • \(\text{I}^n_i: ℕ^n → ℕ\)
    • \(\text{I}^n_i(x_1, ..., x_n) = x_i\)
  • Konstante:
    • \(\text{C}^n_c: ℕ^n → ℕ\)
    • \(\text{C}^n_c(x_1, ..., x_n) = c\)

Operatoren auf \(\textbf{FUNK}(A)\)

Sei A eine beliebige Menge. Definiere dann die Menge der Operationen auf A als: \[\textbf{FUNK}(A) := \{φ ∣ ∃ n∈ℕ \text{ mit } φ: A^n → A\}\]

\(\textbf{FUNK}(ℕ)\) ist für die Theoretische Informatik von fundamentaler Bedeutung. Unter anderem ist \(\textbf{TM} ⊆ \textbf{FUNK}(ℕ)\).

Es seien \(φ: A^n → A, \; ψ:A^m → A \quad \in \textbf{FUNK}(A)\)

  • zyklische Vertauschung:
    • \(\text{ZV}(φ): A^n → A\)
    • \(\text{ZV}(φ)(x_1,...,x_n) = φ(x_2,...,x_n,x_1)\)
  • vertauschung der letzten Variable:
    • \(\text{LV}(φ): A^n → A\)
    • \(\text{LV}(φ)(x_1,...,x_n) = φ(x_1,...,x_{n-2},x_n,x_{n-1})\)
  • Identifikation der letzten Variablen:
    • \(\text{ID}(φ): A^{n-1} → A\)
    • \(\text{ID}(φ)(x_1,...,x_{n-1}) = φ(x_1,...,x_{n-1},x_{n-1})\)
  • Substitution an letzter Stelle:
    • \(\text{SUB}(φ, ψ): A^{n-1+m} → A\)
    • \(\text{SUB}(φ, ψ)(x_1,...,x_{n-1},y_1,...,y_m) = φ(x_1,...,x_{n-1}, ψ(y_1,...,y_m))\)

\[ \begin{aligned} \text{ID}(\text{ZV}(\text{ZV}(\text{SUB}(\text{sum},\text{md})))) (x,y) & = \text{ZV}(\text{ZV}(\text{SUB}(\text{sum},\text{md}))) (x,y,y) \\ & = \text{ZV}(\text{SUB}(\text{sum},\text{md})) (y,y,x) \\ & = \text{SUB}(\text{sum},\text{md}) (y,x,y) \\ & = \text{sum}(y,\text{md}(x,y)) \\ & = y + (x ∸ y) \\ & = \max\{x,y\} \end{aligned} \]

mithin gilt dann:

\[ \max ∈ Γ_{\text{ZV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\}) \]

  • Zeit: 10 Minuten
  1. Berechnen Sie \[ \text{ZV}(\text{SUB}(\text{md}, \text{sum}))(8,13,4) \]

\[ \begin{aligned} \text{ZV}(\text{SUB}(\text{md}, \text{sum}))(8,13,4) & = \text{SUB}(\text{md}, \text{sum})(13,4,8) \\ & = \text{md}(13,\text{sum}(4,8)) \\ & = \text{md}(13,12) \\ & = 13 ∸ 12 \\ & = 1 \end{aligned} \]

  1. Zeigen Sie, dass \[ \min ∈ Γ_{\text{ZV}, \text{LV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\}) \]

Hinweis: \(\max(x,y) = y + (x \; ∸ \; y)\) und \(\max ∈ Γ_{\text{ZV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\})\)

\(\min(x,y) = x + y \; ∸ \;\max(x,y)\)

Bemeke, dass \(Γ_{\text{ZV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\}) \subseteq Γ_{\text{ZV}, \text{LV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\})\) weil die linke Seite nur eine Teilmenge der Operatoren der rechten Seite enthält, und die Ursprungsmenge \(\{\text{sum}, \text{md}\}\) gleich ist.

Da \(\max ∈ Γ_{\text{ZV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\})\) gilt:

\(\max ∈ Γ_{\text{ZV}, \text{LV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\})\)

\[\Rightarrow\]

\[Γ_{\text{ZV}, \text{LV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\}) = Γ_{\text{ZV}, \text{LV} \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}, \max\})\]

Es bleibt zu zeigen, dass \(\min\) durch die Operatoren \(\mathrm{ZV}\), \(\text{LV}\), \(\mathrm{ID}\) und \(\mathrm{SUB}\) aus \(\{\text{sum}, \text{md}, \max\}\) erzeugt werden kann:

\[ \begin{aligned} \min(x,y) & = x + y \; ∸ \; \max\{x,y\}\\ & = \text{md}(\text{sum}(x,y),\max(x,y))\\ & = \text{SUB}(\text{md}, \max) (\text{sum}(x,y), x,y)\\ & = \text{ZV}(\text{ZV}(\text{SUB}(\text{md}, \max))) (x,y,\text{sum}(x,y))\\ & = \underbrace{\text{SUB}(\text{ZV}(\text{ZV}(\text{SUB}(\text{md}, \max))),\text{sum})}_{A} (x,y,x,y)\\ & = \text{LV}(A) (x,y,y,x)\\ & = \text{ZV}(\text{LV}(A)) (x,x,y,y)\\ & = \underbrace{\text{ID}(\text{ZV}(\text{LV}(A)))}_{B} (x,x,y)\\ & = \text{ZV}(B) (y,x,x)\\ & = \text{ID}(\text{ZV}(B)) (y,x)\\ & = \text{ZV}(\text{ID}(\text{ZV}(B))) (x,y)\\ \end{aligned} \]

Damit ist \(\min ∈ Γ_{\text{ZV}, \text{ID}, \text{SUB}}(\{\text{sum}, \text{md}\})\).

\(\square\)

Sei \(A ⊆ ℕ\) und \(ψ,φ,ρ_1,...,ρ_n ∈ \textbf{FUNK}(A)\).

  1. \(ψ\) ist durch Permutation der Variablen aus \(φ\) erzeugt, falls eine permutation \(π\) existiert mit \[ ψ(x_1,...,x_n) = φ(x_{π(1)},...,x_{π(n)}) \]

  2. \(ψ\) ist durch Identifikation von Variablen aus \(φ\) erzeugt, falls \(i,j\) mit \(1≤i≤j≤n\) gibt, mit \[ ψ(x_1,...,x_{j-1},x_{j+1},...,x_n) = φ(x_1,...,x_{j-1},x_i,x_{j+1},...,x_n) \]

  3. \(ψ\) ist durch simultane Substitution von \(ρ_1,...,ρ_n\) in \(φ\) erzeugt, falls \[ ψ(x_1,...,x_m) = φ(ρ_1(x_1,...,x_m), ..., ρ_n(x_1,...,x_m)) \]

Daraus definieren wir den Operator \(\SIM\):

\[ \SIM_n: (ℕ^n → ℕ) × (ℕ^m → ℕ)^n → (ℕ^m → ℕ) \]

\[ \text{SIM}_n(φ, ψ_1,...,ψ_n)(x_1, ..., x_m) = φ(ψ_1(x_1,...,x_m), ..., ψ_n(x_1,...,x_m)) \]

  1. Ist die Funktion \(ψ\) durch Permutation der Variablen aus \(φ\) entstanden, so gilt: \(ψ ∈ Γ_{\text{ZV},\text{LV}}(\{φ\})\).

  2. Ist die Funktion \(ψ\) durch Identifikation von Variablen aus \(φ\) entstanden, so gilt: \(ψ ∈ Γ_{\text{ID},\text{ZV},\text{LV}}(\{φ\})\).

  3. Ist die Funktion \(ψ\) durch simultane Substitution von \(\rho_1,...,\rho_n\) in \(φ\) erzeugt, so gilt: \(ψ ∈ Γ_{\text{SUB},\text{ZV},\text{ID},\text{LV}}(\{φ,\rho_1,...,\rho_n\})\).

  • Zeit: 15 min

Wir definieren die Menge der einstelligen Polynome über \(ℕ\) als: \[ \textbf{POLY}_1(ℕ) = \{f:ℕ^1 → ℕ ∣ (∃n ∈ ℕ, ∀i ∈ [n], a_i ∈ ℕ)\left[f(x) = a_0 x^0 + a_1 x^1 + ... + a_n x^n \right]\} \]

  1. Begründen Sie, dass \(\textbf{POLY}_1(ℕ) ⊆ Γ_{\LV,\text{ZV},\text{ID},\text{SUB}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1, \text{sum}, \text{prod}\}) = Γ\).

Hinweis: Gehen sie Schrittweise vor:

  1. Nullstellige Konstanten: \(C^0_c ∈ Γ\) für alle \(c ∈ ℕ\).
  2. Einstellige Konstanten: \(C^1_c ∈ Γ\) für alle \(c ∈ ℕ\).
  3. Identität: \(\text{id} ∈ Γ\).
  4. Potenzen: \(x \mapsto x^k ∈ Γ\) für alle \(k\).
  5. Monome: \(x \mapsto a_k x^k ∈ Γ\) für alle \(a_k, k\).
  6. Summe von Monomen: \(f(x) = a_0 x^0 + a_1 x^1 + ... + a_n x^n ∈ Γ\) für alle \(a_k, k\).
  1. Nullstellige Konstanten: Wir nutzen vollständige Induktion:

IA \(c=0\): \(C^0_0 ∈ Γ\).

IS \(c > 0\):

  • IV: \(C^0_{c-1} ∈ Γ\). \[ \begin{aligned} C^0_c & = \text{S}(C^0_{c-1}) ∈ Γ \end{aligned} \]
  1. Einstellige Konstanten: \(I^2_2 = \text{ZV}(I^2_1) \in \Gamma\) \[C^1_c(x) = \text{SUB}(I^2_2, C^0_c)(x) = I^2_2(x, C^0_c()) = I^2_2(x, c) = c\]
  1. Identität: \(\text{SUB}(\text{sum}, \text{C}^0_0)(x) = \text{sum}(x, \text{C}^0_0()) = \text{sum}(x, 0) = x\). Also \(\text{id} \in \Gamma\).
  1. Potenzen: Wir nutzen vollständige Induktion.

IA

  • k=0: \(p_k = p_0 = \text{C}^1_1 \in \Gamma\).
  • k=1: \(p_k = p_1 = \text{id} \in \Gamma\).

IS k > 1, k → k+1:

  • IV: \(p_k(x) ∈ Γ\).

\[\begin{aligned} p_{k+1}(x) & = x \cdot p_k(x) \\ & = \text{prod}(x, p_k(x)) \\ & = \text{SUB}(\text{prod}, p_k)(x,x) \\ & = \text{ID}(\text{SUB}(\text{prod}, p_k))(x) ∈ Γ \\ \end{aligned} \]

  1. Monome: \[ \begin{aligned} a_k x^k & = \text{prod}(C^0_{a_k}, p_k(x)) \\ & = \text{SUB}(\text{prod}, p_k)(C^0_{a_k},x) \\ & = \text{ZV}(\text{SUB}(\text{prod},p_k))(x, C^0_{a_k}) \\ & = \text{SUB}(\text{ZV}(\text{SUB}(\text{prod},p_k)), C^0_{a_k})(x) ∈ Γ \end{aligned} \]
  1. Summe von einstelligen Funktionen: Seien \(g, h : \mathbb{N} \to \mathbb{N}\) in \(\Gamma\). Dann: \[ \begin{aligned} \text{sum}(g(x), h(y)) & = \text{SUB}(\text{sum}, h)(g(x),y)\\ & = \text{ZV}(\text{SUB}(\text{sum}, h))(y,g(x))\\ & = \text{SUB}(\text{ZV}(\text{SUB}(\text{sum}, h)),\; g)(y,x)\\ & = \text{ZV}(\text{SUB}(\text{ZV}(\text{SUB}(\text{sum}, h)),\; g))(x,y) ∈ Γ\\ \end{aligned} \]

In unserem Fall ist \(x = y\), also: \[ \text{sum}(g(x), h(x)) = \text{ID}(\text{SUB}(\text{ZV}(\text{SUB}(\text{sum}, h)),\; g))(x) ∈ Γ \]

Mittels vollständiger Induktion über die Anzahl der Monome eines Polynoms können wir nun zeigen, dass jedes Polynom in \(\Gamma\) liegt:

IA: \(f(x) = a_0 x^0\) ist ein Monom, also \(f ∈ Γ\). IS: \(f(x) = a_0 x^0 + a_1 x^1 + ... + a_n x^n\) mit \(n > 0\).

  • IV: \(g(x) = a_0 x^0 + a_1 x^1 + ... + a_{n-1} x^{n-1} ∈ Γ\).

\[ \begin{aligned} f(x) & = g(x) + a_n x^n \\ & = \text{sum}(g(x), a_n x^n) ∈ Γ \end{aligned} \]

\[\Rightarrow \textbf{POLY}_1(\mathbb{N}) \subseteq \Gamma_{\text{ZV},\text{ID}, \text{LV},\text{SUB}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1, \text{sum}, \text{prod}\})\]

\(\square\)

  1. Finden Sie eine Funktion \(f:ℕ→ℕ\) die sie mit ihrem Computer berechnen können (\(f∈\textbf{TM}\)) aber \(f ∉ Γ_{\text{ZV},\text{ID}, \text{LV},\text{SUB}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1, \text{sum}, \text{prod}\})\). Überlegen Sie sich warum es solche Funktionen gibt.

Wir können zwar addieren und multiplizieren aber nicht iterieren und damit ist \(f(x) = 2^x\) ist nicht in der Menge \(Γ_{\LV,\text{ZV},\text{ID},\text{SUB}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1, \text{sum}, \text{prod}\})\).

Algorithmentheorie

Random-Access-Maschinen

  • Die Steuereinheit enthält das Programm und führt es aus. Ein Programm besteht aus einer endlichen Liste von nummerierten Befehlen.
  • Das Befehlsregister enthält die Nummer des aktuellen Befehls im Programm.
  • Jedes der unendlich vielen (Daten-)Register R0, R1, R2, ... enthält eine natürliche Zahl (gespeichert in dyadischer Darstellung). Die Nummer i von Register Ri heißt Adresse.

Bezeichnungen:

<Ri>      # inhalt von Ri, d.h. R[i]
<BR>     # inhalt von BR ≃ *BR
<R> := x # Register R bekommt neuen Inhalt x, dh 

Befehle, Wirkung

# Transportbefehle
Ri ← Rj     # <Ri> := <Rj>
Ri ← RRj    # <Ri> := <R<Rj>>
RRi ← Rj    # R[R[i]] := R[j]
# Arithmetische Befehle
Ri ← k         # R[i] = k
Ri ← Rj + Rk   # R[i] =  R[j] + R[k]
Ri ← Rj - Rk   # R[i] =  R[j] - R[k]
# Sprungbefehle
IF Rj = 0 THEN GOTO x  # falls <Rj> = 0, dann gehe zu Befehl mit Nummer x
IF Rj > 0 THEN GOTO x  # falls <Rj> > 0, dann gehe zu Befehl mit Nummer x
GOTO x                 # gehe zu Befehl mit Nummer x

z.B. Die Funktion \(f(x,y,z) = x + y - z\) könnte durch folgendes RAM-Programm berechnet werden:

./test.ram:

# f(x,y,z) = x + y - z
# R0 = x, R1 = y, R2 = z
0   R0 ← R0 + R1
1   R0 ← R0 - R2
2   STOP

Wir können ein RAM-Programm mit dem Julia-Package TheoreticalComputerScience.jl ausführen:

# Make sure to set all registers needed by the RAM program
set_registers!([9, 2, 8]) 
# Execute the RAM program
execute_ramfile("./test.ram")
R = [9, 2, 8]
0: R0 ← R0 + R1        ⟹  R = [11, 2, 8]
1: R0 ← R0 - R2        ⟹  R = [3, 2, 8]
2: STOP                ⟹  Final register state: [3, 2, 8] 

Alternativ kann das RAM-Programm auch direkt in Julia eingeben:

set_registers!([8, 4, 11])
execute_ramcode([
 # f(x,y,z) = x + y - z
 "R0 <- R0 + R1", # 0
 "R0 ← R0 - R2", # 1
 "STOP" # 2
])
R = [8, 4, 11]
0: R0 <- R0 + R1       ⟹  R = [12, 4, 11]
1: R0 <- R0 - R2       ⟹  R = [1, 4, 11]
2: STOP                ⟹  Final register state: [1, 4, 11] 
  • Zeit: 5 Minuten

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x,y) = 7x + 2y \] in unter 10 Zeilen berechnet.

# R0 = x, R1 = y
# R2 = 2y
0   R2 ← R1 + R1
# R3 = 2x
1   R3 ← R0 + R0
# R4 = 4x
3   R4 ← R3 + R3
# R0 = 5x = x + 4x
4   R0 ← R0 + R4
# R0 = 7x = 5x + 2x
5   R0 ← R0 + R3
# R0 = 7x + 2y
6   R0 ← R0 + R2
7   STOP
  • Zeit: 10 Minuten

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x) = 4^x x \] in unter 10 Zeilen berechnet.

\[ 4^x x = 2^{2x} x \]

0   R1 ← 1
# i ← 2x
1   R2 ← R0 + R0
# result *= 2
2   R0 ← R0 + R0
# iteration
3   R2 ← R2 - R1
5   IF R2 > 0 GOTO 2
6   STOP
  • Zeit: 5 Minuten

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x,y) = \min\{x,y\} \] berechnet.

0  R2 ← R1 - R0
1  R3 ← R0 + R2
2  R4 ← R0 + R1
3  R0 ← R4 + R3
4  STOP

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x,y,z) = x ⋅ y + z \] berechnet.

# f(x, y, z) = x ⋅ y + z
0   R3 ← 1
# Begin der Schleife
1   IF R1 = 0 GOTO 5
2   R2 ← R2 + R0
3   R1 ← R1 - R3
4   GOTO 1
# Ende der Schleife
5   R0 ← R2
6   STOP

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x,y) = x^y \] berechnet, ohne die Multiplikation als Operation zu verwenden.

# f(x, y) = x^y
# R0 = x, R1 = y, R3 = x^i, R4 = R3 ⋅ j, R5 = 1
0   R5 ← 1
1   R3 ← 1
# Begin der Schleife Potenzierung
2   IF R1 = 0 GOTO 12
3   R2 ← R0
4   R4 ← 0
# Begin der Schleife Multiplikation
5   IF R2 = 0 GOTO 9
6   R4 ← R4 + R3
7   R2 ← R2 - R5
8   GOTO 5
# Ende der Schleife Multiplikation
9   R1 ← R1 - R5
10  R3 ← R4
11  GOTO 2
# Ende der Schleife Potenzierung
12  R0 ← R3
13  STOP

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x,y) = \left\lfloor \frac{x}{y} \right\rfloor \] berechnet.

# f(x, y) = ⌊x/y⌋
# R0 = x, R1 = y
0   IF R1 = 0 GOTO 7
1   R2 <- 1
2   R0 <- R0 + R2
3   R0 <- R0 - R1
4   R3 <- R3 + R2
5   IF R0 = 0 GOTO 7
6   GOTO 3
7   R0 <- R3 - R2
8   STOP

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x) = \left\lceil \sqrt{x} \right\rceil \] berechnet.

Hinweis: Verwenden Sie:

\[∑_{i=1}^k (2i - 1) = k^2\]

0   R2 ← 1
1   R1 ← R0
2   R0 ← 0
3   IF R1 = 0 GOTO 8
4   R1 ← R1 − R0
5   R0 ← R0 + R2
6   R1 ← R1 − R0
7   GOTO 3

Schreiben Sie ein RAM-Programm, das die Funktion \[ f(x) = \left\lceil \log_2(x) \right\rceil \] berechnet.

0   R1 ← 1
1   R2 ← 1
2   R3 ← R0 − R1
3   IF R3 = 0 GOTO 7
4   R1 ← R1 + R1
5   R4 ← R4 + R2
6   GOTO 2
7   R0 ← R4

Höhere Programmiersprachen

\(\textbf{RAM} ⊆ \textbf{Julia}\), schliesslich können wir ja auch RAM code in Julia emulieren.

Aber es gilt auch \(\textbf{Julia} ⊆ \textbf{RAM}\). Julia wird explizit compiliert in x64 Assembler, was dann in ein RAM-Programm übersetzt werden kann, d.h. es gibt einen Algorithmus, der jedes Julia-Programm in ein RAM-Programm übersetzt.

ergo: Julia ist Turing-vollständig.

Turingmaschinen

Definition: Turingmaschinen

Eine Turingmaschine mit \(k\)-Bändern ist ein Tupel \(M = (Σ, Z, f, z₀, z_1)\) bestehend aus:

  • einem Bandalphabet \(Σ\),
  • eine Menge von Zuständen \(Z\),
  • einer Überführungsfunktion \(f : Z × Σ^k \; → \; Z × Σ^k × \{L, 0, R\}^k\),
  • einem Startzustand \(z₀ ∈ Z\),
  • einem Endzustand \(z_1 ∈ Z\).

Eine Turingmaschine besteht aus folgenden Komponenten:

  • \(k\) (mit \(k ≥ 1\)) beidseitig unendliche, in Zellen unterteilte Bänder; in jeder Zelle steht genau ein Symbol aus dem Bandalphabet \(Σ\), wobei das Leerzeichen \(□ ∈ Σ\) zeigt an, dass in der Zelle eigentlich nichts steht
  • für jedes Band ein Schreib- und Lesekopf (oder kurz einfach Kopf), der sich von Zelle zu Zelle bewegen kann, um Inhalte der Zellen zu lesen und zu verändern
  • eine Steuereinheit, die sich in einem Zustand \(z\) aus einer endlichen Zustandsmenge \(Z\) befindet, die über die Köpfe Bandinhalte liest und die Aktivitäten entsprechend steuert

das Verhalten pro Takt wird durch eine (totale) Überführungsfunktion: \[ f : Z × Σ^k \quad → \quad Z × Σ^k × \{L, 0, R\}^k \] beschrieben; die Beschreibung ist unabhängig von Taktnummer und Kopfpositionen.

Definition: Turing-Berechenbarkeit

  1. Seien \(M = (Σ, Z, f, z_0, z_1)\) eine Turingmaschine, \(Σ_1 ⊆ Σ ∖ \{□,*\}, Σ_2 ⊆ Σ ∖ \{□\}, n ∈ N\). Eine Funktion \(ψ : (Σ^∗_1)^n → Σ^∗_2\) heißt von der Turingmaschine \(M\) berechnet, falls für alle \(x_1, ... , x_n ∈ Σ^*_1\) gilt:
  1. Ist \(ψ(x_1, ... , x_n)\) definiert, so gilt: Startet \(M\) in \(S(z_0, x_1 ∗ x_2 ∗ ··· ∗ x_n)\), so stoppt \(M\) in \(S(z_1, ψ(x_1, ..., x_n))\).

  2. Ist \(ψ(x_1, ... , x_n)\) nicht definiert, so gilt: Startet \(M\) in \(S(z_0, x_1∗x_2∗···∗x_n)\), so stoppt \(M\) nicht oder \(M\) stoppt aber nicht in einer Situation der Form \(S(z_1, y)\) mit \(y ∈ (Σ ∖ \{□\})^*\).

  1. Eine Funktion \(ψ : (Σ^*_1)^n → Σ^*_2\) heißt Turing-berechenbar, falls es eine Turingmaschine gibt, die \(ψ\) berechnet.

  2. Eine Funktion \(φ : ℕ^n → ℕ\) heißt Turing-berechenbar, falls sie in dyadischer Kodierung Turing-berechenbar ist, d.h. falls die durch \(ψ(x_1, ... , x_n) = \text{dya}(φ(\text{dya}^{-1}(x_1), ..., \text{dya}^{-1}(x_n)))\) definierte Funktion \(ψ : (\{1, 2\}^∗)^n → \{1, 2\}^*\) Turing-berechenbar ist.

  1. Dazu definieren wir die Menge/Klasse \(\textbf{TM}\) der Turing-berechenbaren Funktionen als: \[ \textbf{TM} = \{ φ \mid φ : ℕ^n → ℕ \text{ ist Turing-berechenbar} \} \]

Definition: 1-Turingmaschine

Eine 1-Turingmaschine ist eine Turingmaschine mit nur einem Halb-Band. Also \(M_1 = (Σ, Z, f, z₀, z_1)\). Die Überführungsfunktion hat dann die Form:

\[ f : Z × Σ \quad → \quad Z × Σ × \{L, 0, R\} \]

Das TheoreticalComputerScience Paket enthält auch eine Implementierung von Turing Maschinen mit \(k\) Bändern:

  • no_bands: The number of bands.
  • initial_state: The initial state.
  • final_state: The final state.
  • f: Tuple{String, Vector{Char}} => Tuple{String, Vector{Char}, Vector{Movement}}

□: Represents the blank symbol. Written with \Box

M = TMK(no_bands = 2, initial_state = "z0", final_state = "z1", f = Dict([
 #z0
 ("z0", ['a','□']) => ("z0", ['a','a'], [:R, :R]),
 ("z0", ['b','□']) => ("z0", ['b','b'], [:R, :R]),
 ("z0", ['□','□']) => ("z2", ['□','□'], [:O, :L]),
 # z2
 ("z2", ['□','b']) => ("z2", ['b','□'], [:R, :L]),
 ("z2", ['□','a']) => ("z2", ['a','□'], [:R, :L]),
 ("z2", ['□','□']) => ("z3", ['□','□'], [:L, :O]),
 # z3
 ("z3", ['a','□']) => ("z3", ['a','□'], [:L, :O]),
 ("z3", ['b','□']) => ("z3", ['b','□'], [:L, :O]),
 ("z3", ['□','□']) => ("z1", ['□','□'], [:R, :O])
 ])
)

um die Maschiene auszuführen, können Sie die execute_tm Funktion verwenden:

execute_tm(M, "abb")

Die Maschiene wird dann schritt für Schritt ausgeführt, bis der Endzustand erreicht ist.

abb
↑
□□□
↑
⤷ f(z0,['a', '□'])  ⟶  (z0, ['a', 'a'], [R, R]) 

abb
 ↑
a□□
 ↑
⤷ f(z0,['b', '□'])  ⟶  (z0, ['b', 'b'], [R, R])
... (continues until the final state is reached)

Turingmaschienen mit nur einem Band können genauso definiert werden:

  • initial_state: The initial state.
  • final_state: The final state.
  • f: Tuple{String, Char} => Tuple{String, Char, Movement}

□: Represents the blank symbol. Written with \Box

M = TM1(initial_state = "z0", final_state = "z1", f = Dict([
  ("z0", 'a') => ("za", '□', :R),
  ("z0", '□') => ("z1", '□', :R),
  ("z0", 'b') => ("zb", 'b', :R),
  ("za", 'a') => ("za", 'a', :R),
  ("za", 'b') => ("za", 'b', :R),
  ("za", '□') => ("z´", 'a', :L),
  ("zb", 'a') => ("za", 'a', :R),
  ("zb", 'b') => ("za", 'b', :R),
  ("zb", '□') => ("z´", 'b', :L),
  ("z´", '□') => ("z1", '□', :R),
  ("z´", 'a') => ("z´", 'a', :L),
  ("z´", 'b') => ("z´", 'b', :L)
 ])
)

Sie können diese dann mit der execute_tm Funktion ausführen oder mit der execute_1tm Funktion, je nachdem ob es sich um eine 2-∞-sided Band Turingmaschine oder eine right-∞-sided Band Turingmaschine handelt:

# two-∞-sided Band
execute_tm(M, "abbabba")
# right-∞-sided Band
execute_1tm(M, "abbabba")
  1. Zeichnen Sie eine Turingmaschine die \(I^1_1\) berechnet.

  1. Zeichnen Sie eine Turingmaschine die \(C^3_0(x,y,z)\) berechnet.

  1. Zeichnen Sie eine Turingmaschine die \(C^1_6(x)\) berechnet.

  1. Zeichnen Sie eine Turingmaschine die \(S(x)\) berechnet.

  1. Zeichnen Sie eine Turingmaschine die \(I^2_2\) berechnet.

  1. Zeichnen Sie eine Turingmaschine die \(P(x)\) berechnet.

Sei \(M = (\{1,2,□\},\{z_0,z_1,z_a,z_b,z_c\},z_0,z_1,f)\) eine Turingmaschine mit der Überführungsfunktion \(f\) gegeben durch:

\[ \begin{aligned} &z_0 1 → z_a 1 R & \quad z_a 1 → z_a 1 R & \quad z_b 1 → z_b 1 R & \quad z_c 1 → z_c 1 L \\ &z_0 2 → z_b 2 R & \quad z_a 2 → z_a 2 R & \quad z_b 2 → z_b 2 R & \quad z_c 2 → z_c 2 L \\ &z_0 □ → z_1 □ O & \quad z_a □ → z_c 1 L & \quad z_b □ → z_c 2 L & \quad z_c □ → z_1 □ R \end{aligned} \]

  1. Beschreiben Sie was die einzelnen Zustände \(z_0, z_a, z_b, z_c\) tun.

\(z_0\): Startzustand, entscheidet über die erste gelesene Ziffer, ob es sich um eine 1 oder 2 handelt. Je nachdem wechselt es in den Zustand (1 →) \(z_a\) oder (2 →) \(z_b\). Wenn es ein Leerzeichen liest, wechselt es in den Endzustand \(z_1\).

\(z_a\): Gehe bis ans rechte Ende der Zeichenfolge und füge eine 1 hinten an. Dann wechsle in \(z_c\).

\(z_b\): Gehe bis ans rechte Ende der Zeichenfolge und füge eine 2 hinten an. Dann wechsle in \(z_c\).

\(z_c\): Gehe zurück zum Anfang der Zeichenfolge und wechsle dann in den Enzustand \(z_1\).

  1. Beschreibe in einem Satz was die Turingmaschine \(M\) mit der Zeichenfolge anstellt.

\(M\) nimmt das erste Zeichen der Eingabe und hängt es an das Ende der Zeichenfolge an. Also z.B. aus \(121\) wird \(1211\) und aus \(221\) wird \(2212\).

  • Zeit: 10 Minuten
  1. Schreiben Sie eine Turingmaschine, die die Funktion \[ x \mapsto \left\lceil \frac{x}{2} \right\rceil \] berechnet.

\(M = (\{1,2,□\},\{z_0,z_1,z_a,z_b,z_c\},z_0,z_1,f)\) mit der Überführungsfunktion \(f\) gegeben durch: \[ \begin{aligned} &z_0 1 → z_0 1 R & \quad z_a 1 → z_b □ L & \quad z_b 1 → z_c 2 L & \quad z_c 1 → z_c 1 L \\ &z_0 2 → z_0 2 R & \quad z_a 2 → z_b □ L & \quad z_b 2 → z_b 1 L & \quad z_c 2 → z_c 2 L \\ &z_0 □ → z_a □ L & \quad z_a □ → z_1 □ O & \quad z_b □ → z_1 1 O & \quad z_c □ → z_c □ R \end{aligned} \]

  1. Wie viele Schritte werden von der TM benötigt, um die Funktion für eine Eingabe von Länge \(n\) zu berechnen? Geben Sie die exakte Anzahl an Schritten als Funktion von \(n\) an.

\[ t(n) = \underbrace{n}_{\text{für }z_0} + \underbrace{1}_{\text{für }z_a} + \underbrace{n}_{\text{für }z_b \text{und }z_c} + \underbrace{1}_{\text{für }z_1} = 2n + 2 \]

  • Zeit: 20 Minuten
  1. Überlegen Sie sich, wie viele Zustände eine Turingmaschine mindestens benötigt, um die Funktion \(\text{sum}(x,y) = x + y\) zu berechnen. Was tuen diese jeweils?

Wenn wir zwei dyadische Zahlen addieren wollen, müssen wir die Ziffern von rechts nach links addieren. Dabei können wir einen Übertrag von \(0-2\) haben.

Damit erhalten wir folgende Zustände:

  • \(z_0\): Startzustand, geht bis rechten Ende der Eingabe.
  • \(a_0\): Addiere die Ziffern ohne Übertrag.
  • \(a_1\): Addiere die Ziffern mit einem Übertrag von 1.
  • \(a_2\): Addiere die Ziffern mit einem Übertrag von 2.
  • \(z_1\): Endzustand, wenn die Addition fertig ist.
  1. Zeichnen Sie eine Turingmaschine, die die Funktion \[ \text{sum}(x,y) = x + y \] berechnet.

\(M = (\{1,2,□\},\{z_0,z_1,a_0,a_1,a_2\},z_0,z_1,f)\) mit der Überführungsfunktion \(f\) gegeben durch:

TM

Partiell-rekursive Funktionen

Das TheoreticalComputerScience Paket enthält auch eine Implementierung von partiell-rekursiven Funktionen:

https://github.com/daniel-schwarzenbach/TheoreticalComputerScience.jl#partially-recursive-functions

# max(x, y)
@show ID(ZV(ZV(SUB(psum, md))))(10,9)
@show ID(ZV(ZV(SUB(psum, md))))(9,10)

# P(x) = 1 ∸ x
@show PR(C(0,0), I(2,1))(5)

# x ↦ ⌈√x⌉
@show MIN(SIM2(md, I(2,1), SIM2(pprod, I(2,2), I(2,2))))(10)

output:

(ID(ZV(ZV(SUB(psum, md)))))(10, 9) = 10
(ID(ZV(ZV(SUB(psum, md)))))(9, 10) = 10
(PR(C(0, 0), I(2, 1)))(5) = 4
(MIN(SIM2(md, I(2, 1), SIM2(pprod, I(2, 2), I(2, 2)))))(10) = 4

Definition: Primitive Rekursion

  1. Es seien \(φ : ℕ^n → ℕ\) und \(ψ : ℕ^{n+2} → ℕ\) Funktionen. Dann ist die Funktion \(\text{PR}(φ, ψ) : ℕ^{n+1} → ℕ\) definiert durch:

\[ \text{PR}(φ, ψ)(x_1,...,x_n,y) = \begin{cases} φ(x_1,...,x_n) & \text{falls }y = 0 \\ ψ(x_1,...,x_n,y-1,\text{PR}(φ, ψ)(x_1,...,x_n,y-1)) & \text{sonst} \end{cases} \]

  1. \(\textbf{PRIM} = Γ_{\text{ZV},\text{ID},\text{SUB}, \text{LV}, \text{PR}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1\})\).

  2. Eine Funktion \(f : ℕ^n → ℕ\) heißt primitiv-rekursiv, falls \(f ∈ \textbf{PRIM}\).

Die Funktion \(I^n_m\), \(C^n_m\), sum, md, prod, div, exp, prim und prex sind alle primitiv-rekursiv.

Eine Funktion ist genau dann primitiv-rekursiv, wenn sie berechnet werden kann, ohne

  • While Schleifen
  • Funktionsaufrufe (rekursive)

Endliche For-Schleifen und streng nicht rekursive Funktionsaufrufe (nur einmal im Callstack) sind aber erlaubt

Methode zum Erstellen/Erkennen von primitiv-rekursiven Funktionen

Wir suchen eine Funktion \(f : ℕ^n → ℕ\) die primitiv-rekursiv ist, d.h. \(f ∈ \textbf{PRIM}\). Dafür starten wir mit dem Induktionsanfang (\(y=0\)) und finden ein passendes \(φ : ℕ^n → ℕ\) so dass gilt: \[ f(x_1,...,x_n,0) = φ(x_1,...,x_n) \]

und für den Induktionsschritt (\(y>0\)) finden wir eine Funktion \(ψ : ℕ^{n+2} → ℕ\) so dass gilt: \[ \begin{aligned} f(x_1,...,x_n,y) & = ψ(x_1,...,x_n,y-1,f(x_1,...,x_n,y-1)) \\ & = \text{PR}(φ, ψ)(x_1,...,x_n,y) ∈ \textbf{PRIM} \end{aligned} \] wobei die Induktionsvoraussetzung \(f(x_1,...,x_n,y-1) = \text{PR}(φ, ψ)(x_1,...,x_n,y-1)\) ist.

  1. Zeigen Sie, dass \(≤: ℕ^2 \to \{0,1\}\) in \(\textbf{PRIM}\) liegt, indem sie \(\text{md} \in \textbf{PRIM}\) verwenden.

\[ \begin{aligned} x ≤ y &= 1 \;∸ \;(x \;∸\; y) \\ & = \text{md}(1, \text{md}(x,y)) \\ & = \text{SUB}(\text{md}, \text{md})(1,x,y) \\ & = \text{ZV}(\text{SUB}(\text{md}, \text{md}))(x,y,1) \\ & = \text{SUB}(\text{ZV}(\text{SUB}(\text{md}, \text{md})), C_1^0)(x,y) ∈ \textbf{PRIM} \end{aligned} \]

  1. Zeigen Sie, dass die Funktion \(P(x) = x ∸ 1\) in \(\textbf{PRIM}\) liegt, ohne \(\text{md}\) zu verwenden.
  • Hinweis: Machen Sie eine vollständige Induktion über \(x\).

\[ \begin{aligned} \text{IA, x = 0: }P(0) &= 0 = C^0_0 \\ \text{IS, x > 0: }P(x) &= x - 1 \\ &= I^2_1(x - 1, P(x-1)) \\ &= \text{PR}(C^0_0, I^2_1)(x) ∈ \textbf{PRIM} \end{aligned} \]

  1. Zeigen Sie, dass die Funktion \(\text{sum}(x,y)\) in \(\textbf{PRIM}\) liegt.
  • Hinweis: Machen Sie eine vollständige Induktion über \(y\).

\[ \begin{aligned} \text{IA, y = 0: }\text{sum}(x, 0) &= x = I^1_1(x) \\ \text{IS, y > 0: }\text{sum}(x, y) &= S(\text{sum}(x,y-1)) \\ &= \text{SUB}(I^3_3, S)(x,y-1,\text{sum}(x,y-1)) \\ &= \text{PR}(I^1_1, \text{SUB}(I^3_3, S))(x,y) ∈ \textbf{PRIM} \end{aligned} \]

  1. Zeigen Sie, dass die Funktion \(\text{prod}(x,y)\) in \(\textbf{PRIM}\) liegt, indem Sie \(\text{sum}\) verwenden.
  • Hinweis: Machen Sie eine vollständige Induktion über \(y\).

\[ \begin{aligned} \text{IA, y = 0: }\text{prod}(x, 0) &= 0 = C^1_0(x) \\ \text{IS, y > 0: }\text{prod}(x, y) &= \text{sum}(x, \text{prod}(x,y-1)) \\ &= \text{SUB}(\text{sum}, I^2_2)(x, y-1, \text{prod}(x,y-1)) \\ &= \text{PR}(C^1_0, \text{SUB}(\text{sum}, I^2_2))(x,y) ∈ \textbf{PRIM} \end{aligned} \]

  1. Überlegen Sie sich welche funktionen in \(\textbf{PRIM}\) liegen und welche nicht.

Wir wollen zeigen, dass alle aussagenlogischen Funktionen in \(\textbf{PRIM}\) liegen. Dafür verwenden wir die folgenden Funktionen: \[ \pmd, \psum, \pprod, C^n_m \; ∀ n,m ∈ ℕ, I^n_m \; ∀ n,m ∈ ℕ \text{ mit } 1 ≤ m ≤ n \]

  1. Zeigen Sie, dass \(f,w\) und alle \(X_1,X_2,...,X_n\) in \(\textbf{PRIM}\) liegen.
  • \(f = C^0_0\)
  • \(w = C^0_1\)
  • \(X_i = I^n_i\)
  1. Zeigen Sie, dass die Funktion \(¬: \{0,1\} → \{0,1\}\) in \(\textbf{PRIM}\) liegt.

\[ \begin{aligned} ¬(x) &= 1 - x \\ &= \pmd (1, x) \\ &= \ZV(\pmd)(x, 1) \\ &= \SUB(\ZV(\pmd), C^0_0)(x) ∈ \textbf{PRIM} \end{aligned} \]

  1. Zeigen Sie, dass die Funktion \(∧: \{0,1\}^2 → \{0,1\}\) in \(\textbf{PRIM}\) liegt.

\[ \begin{aligned} ∧(x,y) &= x ⋅ y \\ &= \text{prod}(x,y) ∈ \textbf{PRIM} \end{aligned} \]

  1. Argumentieren Sie, warum alle aussagenlogischen Funktionen in \(\textbf{PRIM}\) liegen.

Alle aussagenlogischen Funktionen können mit \(f,w,X_i,¬,∧\) konstruiert werden, welche alle in \(\textbf{PRIM}\) liegen. Unter anderem ist:

  • \(∨(x,y) = ¬(¬x ∧ ¬y)\)
  • \(→(x,y) = ¬x ∨ y\)
  • \(↔(x,y) = (x → y) ∧ (y → x)\)
  • \(⊕(x,y) = ¬(x ↔ y)\)

In dieser Aufgabe dürfen Sie nur die Operatoren \(\ZV, \LV, \ID, \SUB, \SIM_n ∀n≥2\) und \(\PR\) verwenden.

  1. Erzeugen Sie die Funktion \(x \mapsto x^x\) mit den Grundfunktionen: \(\psum, \pmd, \pprod, C^n_m\) und \(I^n_m\).

Wir finden zuerst \(\pexp(x,y) = x^y\) und dann gilt \(\ID(\pexp)(x) = x^x\):

\[ \begin{aligned} \text{IA, y = 0: } \pexp(x,0) &= 1 = C^1_1(x) \\ \text{IS, y > 0: } \pexp(x,y) &= x ⋅ \pexp(x,y-1) \\ &= \pprod(x, \pexp(x,y-1)) \\ &= \SUB(\pprod, I^2_2)(x,y-1, \pexp(x,y-1)) \\ &= \PR(C^1_1, \SUB(\pprod, I^2_2))(x,y) \end{aligned} \]

\[ x \mapsto x^x = \ID(\PR(C^1_1, \SUB(\pprod, I^2_2))) \]

Die Akermann-Funktion \(A : ℕ^2 → ℕ\) ist nicht primitiv-rekursiv. aber in \(\textbf{TM}\). Somit gilt: \(\textbf{PRIM} \subsetneq \textbf{TM}\). \[ A(x,y) = \begin{cases} y+1 & \text{falls } x = 0 \\ x & \text{falls } y = 0 \text{ und } x > 0 \\ A(x-1, A(x, y-1)) & \text{falls } x > 0 \text{ und } y > 0 \end{cases} \]

⚠️ Auf Wikipedia und in der Literatur ist sie manchmal etwas anders definiert.

Sei \(x ∈ ℕ\). Für die Akermann-Funktion \(A: ℕ^2 → ℕ\) gilt:

  1. \(y < A(x,y) \quad ∀ y ∈ ℕ\)
  2. \(A(x,y) < A(x,y+1) \quad ∀ y ∈ ℕ\)
  3. \(A(x,y) < A(x+1,y) \quad ∀ y ∈ ℕ\)
  1. \((∃z ∈ ℕ, ∀y ∈ ℕ_+)\left[A(x,y) < A(z,y-1)\right]\)
  2. \(A(x,2y) < A(x+3,y) \quad ∀ y ∈ ℕ\)

Sei \(f:ℕ → ℕ\) eine primitiv-rekursive Funktion mit \(n ∈ ℕ_+\). Dann gibt es ein \(x ∈ ℕ\), sodass für alle \(y_1, ..., y_n ∈ ℕ\) gilt:

\[ f(y_1,...,y_n) < A(x,y_1 + ... + y_n) \]

  1. Können Sie \(A(10, 9)\) rekursiv mit ihrem eigenem Computer berechnen?

Im Leben nicht, da \(A(10, 9)\) eine extrem große Zahl ist. Die Anzahl der Rechenschritte würde weit über der Anzahl der Atome im Universum liegen.

  1. Kann man die Akermann-Funktion ohne Rekursion berechnen?

Ja, mit einer While Schleife!

# Akermann matrix
N, M = 20_000, 20_000
Ak::Matrix{Int} = fill(0, N, M) # 0 indicates uncomputed 

# wrapper function of the Ak Matrix to handle base cases
function Akw(m::Int, n::Int)::Int
    # base cases
    if m == 0
        return n + 1
    elseif n == 0
        return m
    else
        return Ak[m, n]
    end
end

# tries to fill the Ak matrix at position (m, n) if the dependent values are already computed
function try_compute_Ak(m::Int, n::Int)::Nothing
    # if the value is already computed, do nothing
    if Ak[m, n] != 0
        return
    # if the dependent value is not computed, do nothing
    elseif Akw(m,n-1) != 0 && Akw(m-1, Akw(m, n-1)) != 0
        Ak[m, n] = Akw(m-1, Akw(m, n-1))
        println("Computed A($m, $n) = ", Ak[m, n])
    end
    return
end

# ℕ → ℕ × ℕ
# bijection between ℕ and ℕ × ℕ
function nxn(n)
    i = 0
    x = 0
    while x <= n
        i = i + 1
        x = x + i
    end
    x -= i
    i -= 1
    y = n - x
    return (i-y, y)
end

function A(m::Int, n::Int)::Int
    base = 3
    e = 2
    while (Akw(m, n) == 0)
        # fill out the matrix with computed values
        k = base^e
        for i in 0:k
            x, y = nxn(i)
            try_compute_Ak(x+1, y+1)
        end
        e += 1
    end
    return Akw(m, n)
end

Definition: μ-Rekursion

\(μx(E(x)) = \arg \min_{x ∈ ℕ} E(x)\), wobei \(E(x)\) eine Aussage über \(x\) ist. Also \(μx(E(x))\) ist die kleinste natürliche Zahl \(x\), für die \(E(x)\) wahr ist. Wenn es kein \(x\) gibt, für das \(E(x)\) wahr ist, dann ist \(μx(E(x))\) undefiniert.

Daher reden wir in der Theoretischen Informatik streng genommen von partiellen Funktionen von \(ℕ → ℕ\), also nur rechtseindeutige Relationen, die nicht links-total sein müssen.

  1. Es seien \(φ : ℕ^{n} → ℕ\) eine Funktion. Dann ist die Funktion \(\text{MIN}(φ): ℕ^{n-1} → ℕ\) definiert durch: \[ \text{MIN}(φ)(x_1,...,x_{n-1}) = μy(φ(x_1,...,x_{n-1},y) = 0) \] falls es ein \(y ∈ ℕ\) gibt, für das \(φ(x_1,...,x_{n-1},y) = 0\) gilt, und undefiniert sonst.

  2. \(\textbf{PART} = Γ_{\text{ZV},\text{ID},\text{SUB}, \text{LV}, \text{PR}, \text{MIN}}(\{\text{S}, \text{C}^0_0, \text{I}^2_1\})\).

  3. Eine Funktion \(f : ℕ^n → ℕ\) heißt partiell-rekursiv, falls \(f ∈ \textbf{PART}\).

\[ \MIN(φ) \\ ⇔ \]

function MIN(φ::Function)
  return function (args...)
    y = 0
    while φ(args..., y) != 0
      y = y + 1
    end
    return y
  end
end
  1. Welche Stelligkeit hat die Funktion

\[ \SUB(\ZV(\SUB(I^3_2, S)), \PR(\PR(C^1_1, \MIN(\PR(I^3_1, I^5_5))),\SUB(C^4_4, S))) \]

\[ \SUB(\underbrace{\ZV(\SUB(I^3_2, S))}_{\text{3-stellig}}, \PR(\PR(C^1_1, \MIN(\PR(I^3_1, I^5_5))),\underbrace{\SUB(C^4_4, S)}_{\text{4-stellig}})) \]

Damit haben wir eine Substitution von einer 3-stelligen Funktion in eine 3-stellige Funktion, also ist die Stelligkeit der Funktion \(3+3-1 = 5\)

  1. Welche Funktion verbirgt sich hinter

\[ \SUB(\PR(C^0_0, \SIM_2(\mathrm{md}, I^2_1, I^2_2)), \SIM_2(\mathrm{prod}, I^1_1, \SUB(S, I^1_1))) \]

  • \(\PR(C^0_0, \SIM_2(\mathrm{md}, I^2_1, I^2_2))\)

\[ \begin{aligned} \text{IA, y = 0: } f(0) &= C^0_0 \\ \text{IS, y > 0: } f(y) &= \SIM_2(\mathrm{md}, I^2_1, I^2_2)(y-1, f(y-1)) \\ ⇒ \\ f(1) &= 0 - 0 = 0 \\ f(2) &= 1 - 0 = 1 \\ f(3) &= 2 - 1 = 1 \\ f(4) &= 3 - 1 = 2 \\ f(5) &= 4 - 2 = 2 \\ f(6) &= 5 - 2 = 3 \\ f(7) &= 6 - 3 = 3 \\ f(8) &= 7 - 3 = 4 \\ ⇒ \\ f(y) &= \left\lfloor \frac{y}{2} \right\rfloor \end{aligned} \]

  • \(\SIM_2(\mathrm{prod}, I^1_1, \SUB(S, I^1_1))\)
    • \(\SUB(S, I^1_1) = S\)
    • \(\SIM_2(\mathrm{prod}, I^1_1, S) = x \mapsto x ⋅ (x+1)\)

Final erhalten wir:

\[ \SUB(y ↦ \left\lfloor \frac{y}{2} \right\rfloor, x ↦ x ⋅ (x+1)) = \underline{\underline{x ↦ \frac{x ⋅ (x+1)}{2}}} \]

  1. Welche Funktion verbirgt sich hinter

\[ \MIN(\SIM_2(\mathrm{md}, I^2_1, \SIM_2(\mathrm{prod}, I^2_2, I^2_2))) \]

  • \(\SIM_2(\mathrm{prod}, I^2_2, I^2_2) = (x,y) \mapsto y^2\)
  • \(\SIM_2(\mathrm{md}, I^2_1, (x,y) \mapsto y^2) = (x,y) \mapsto x - y^2\)

Somit ist: \[ \MIN((x,y) \mapsto x - y^2) = x \mapsto \left\lceil \sqrt{x} \right\rceil \]

  1. Drücken Sie die Funktion \[\text{fak} : \mathbb{N} \to \mathbb{N} : n \mapsto n!\] durch eine geeignete Operatorenfolge aus.
    Als Operatoren dürfen Sie \(\text{ZV}, \text{LV}, \text{ID}, \text{SUB}\) und \(\text{PR}\) verwenden. Als Grundfunktionen dürfen Sie \(I^m_n\) (mit \(1 \leq n \leq m\)), \(C^m_n\) (mit \(n, m \in \mathbb{N}\)), \(S\) und \(\text{prod}\) verwenden.

\[ \begin{aligned} \text{IA, x = 0: }f(0) &= 1 = C^0_1 \\ \text{IS, x > 0: }f(x) &= \text{prod}(x, f(x-1)) \\ &= \SIM_2(\pprod, \SUB(S, I^2_1), I^2_2)(x-1, f(x-1)) \\ &= \text{PR}(C^0_1, \SIM_2(\pprod, \SUB(S, I^2_1), I^2_2))(x) \end{aligned} \]

  1. Drücken Sie die Funktion \[\text{kgV} : \mathbb{N}^2 \to \mathbb{N} : (n, m) \mapsto \text{kleinstes gemeinsames Vielfaches von } n \text{ und } m\] durch eine geeignete Operatorenfolge aus.
    Als Operatoren dürfen Sie \(\text{ZV}, \text{LV}, \text{ID}, \text{SIM}^2\) und \(\text{MIN}\) verwenden. Als Grundfunktionen dürfen Sie \(I^m_n\) (mit \(1 \leq n \leq m\)), \(C^m_n\) (mit \(n, m \in \mathbb{N}\)), \(\text{sum}\) und \(\text{mod}\) verwenden.

für ein kgV \(v\) von \(n\) und \(m\) gilt: \(\mod(v,n) + \mod(v,m) = 0\). Somit könnte eine Lösung aussehen wie:

\[ \MIN(\SIM_2(\text{sum}, \SIM_2(\text{mod}, I^3_3, I^3_1), \SIM_2(\text{mod}, I^3_3, I^3_2))) \]

⚠️ aber wir haben hier das Problem dass das ganze immer \(0\) ausgibt. Dafür rechnen wir das ganze für \(x+1\) aus und erhöhen dann das Ergebnis um \(1\):

\[ \SIM_2(\psum, C^2_1,\MIN( \\ \SIM_2(\text{sum}, \SIM_2(\text{mod}, \SIM_2(\psum, C^3_1, I^3_3), I^3_1), \SIM_2(\text{mod}, \SIM_2(\psum, C^3_1, I^3_3), I^3_2)))) \]

(a) Drücken Sie die Funktion \[\text{turm} : \mathbb{N} \to \mathbb{N} : n \mapsto \underbrace{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}_{n+1\text{-mal}}\] durch eine geeignete Operatorenfolge aus. Als Operatoren dürfen Sie \(\ZV\), \(\LV\), \(\ID\), \(\SIM_2\) und \(\PR\) verwenden. Als Grundfunktionen dürfen Sie \(I^m_n\) (mit \(1 \leq n \leq m\)), \(C^m_n\) (mit \(n, m \in \mathbb{N}\)) und \(\text{exp}\) verwenden.

\[ \begin{aligned} \text{IA, x = 0: }f(0) &= 2 = C^0_2 \\ \text{IS, x > 0: }f(x) &= \pexp(2, f(x-1)) \\ &= \SIM_2(\pexp, C^2_2, I^2_2)(x-1, f(x-1)) \\ &= \text{PR}(C^0_2, \SIM_2(\pexp, C^2_2, I^2_2))(x) \end{aligned} \]

(b) Drücken Sie die Funktion \[\text{invfak} : \mathbb{N} \to \mathbb{N} : n \mapsto \max\{k \mid k! \leq n\}\] durch eine geeignete Operatorenfolge aus. Als Operatoren dürfen Sie \(\ZV\), \(\LV\), \(\ID\), \(\SUB\), \(\SIM_2\) und \(\MIN\) verwenden. Als Grundfunktionen dürfen Sie \(I^m_n\) (mit \(1 \leq n \leq m\)), \(C^m_n\) (mit \(n, m \in \mathbb{N}\)), \(\text{sum}\), \(\text{md}\) und die Fakultätsfunktion \(\text{fak} : \mathbb{N} \to \mathbb{N} : n \mapsto n!\) verwenden.

wir suchen das erste \(k\), so dass gilt \(¬((k+1)! ≤ n)\) bzw. \(≤((k+1)!,n)=0\). Dazu definieren wir die Hilfsfunktionen:

  • \(≤(x,y) = 1 - (x-y) = \SIM_2(\pmd, C^2_1, \SIM_2(\pmd, I^2_1, I^2_2))\)
  • \(P(x) = x-1 = \SIM_2(\pmd, I^1_1, C^1_1)\)

Damit ist

\[ \text{invfak} = \SUB(P, \MIN(\SIM_2(≤, \SUB(\text{fak}, I^2_2), I^2_1))) \]

Hauptsatz der Algorithmentheorie

\[ ⟹ \quad \textbf{TM} = \textbf{1-TM} = \textbf{RAM} = \textbf{Julia} = \textbf{PART} \]

Church These

Jede algorithmisch berechenbare Funktion ist Turing-berechenbar.

Definition: Turing-vollständig

Ein System ist Turing-vollständig, wenn es jede berechenbare Funktion berechnen kann – also alles, was eine universelle Turingmaschine berechnen kann.

Formal: Ein Berechnungsmodell \(M\) ist Turing-vollständig, wenn gilt:

\[\forall f \text{ (Turing-berechenbar)} \Rightarrow f \text{ ist in } M \text{ berechenbar}\]

  • HTML(mit CSS) ist Turing-vollständig.
  • Powerpoint ist Turing-vollständig.
  • Minecraft ist Turing-vollständig.
  • The Game of Life ist Turing-vollständig.
  • Modernes SQL mit der Möglichkeit von rekursiven Abfragen ist Turing-vollständig.
  • C++ templates sind Turing-vollständig.

Berechenbarkeitstheorie

Entscheidbare Mengen

Definition: Entscheidbare Menge

Eine Menge \(A ⊆ ℕ^m\) heißt entscheidbar, falls die Charakteristische Funktion \(c_A\) berechenbar ist.

\[ c_A(x_1, ..., x_m) = \begin{cases} 1 & \text{falls } (x_1, ..., x_m) ∈ A \\ 0 & \text{falls } (x_1, ..., x_m) ∉ A \end{cases} \]

Bemerkung: Die Charakteristische Funktion \(c_A: ℕ^m → \{0,1\}\) ist eine totale Funktion, ergo muss ein Programm, das \(c_A\) berechnet, auf jeder Eingabe halten.

Zeigen Sie, dass für jede entscheidbare Menge \(A ⊆ ℕ^m\) die Charakteristische Funktion selbst \(c_A ⊆ ℕ^{m+1}\) wieder eine entscheidbare Menge ist.

Wir \(c_{cA}\) berechnen indem wir \(c_A\) benutzen:

function c_cA(args...)
    if c_A(args[1:end-1]...) == args[end]
        return 1
    else
        return 0
    end
end

Seien \(A, B ⊆ ℕ^m\) mit \(m ∈ ℕ_+\).

\(A\) ist endlich \(\quad ⟹ \quad\) \(A\) ist entscheidbar.

\(A\) ist entscheidbar \(\quad ⟹ \quad\) \(\bar{A}\) ist entscheidbar.

\(A\) und \(B\) sind entscheidbar \(\quad ⟹ \quad\) \(A ∪ B\) und \(A ∩ B\) sind entscheidbar.

\(A\) ist entscheidbar und \(f:ℕ^n → ℕ^m\) berechenbar \(\quad ⟹ \quad\) \(f^{-1}(A)\) ist entscheidbar.

Betrachten Sie das Polynom \(p:ℕ → ℕ\) gegeben durch \[ x ↦ x - 2x^2 + x^3 \]

Zeigen Sie, dass die Menge \(A = \{x ∈ ℕ \mid ∃y \; : \; p(y) = x\}\) entscheidbar ist.

Wir bauen uns einen Algorithmus, der \(c_A\) berechnet. Dafür suchen wir uns eine obere Schranke \(x_0\) für unser \(y ≤ p(x_0)\) und testen dann alle \(x\) bis zu dieser Schranke. \[ \begin{aligned} y = p(x) = x - 2x^2 + x^3 &≥ - 2x^2 + x^3 \\ &= x^2(x-2) \\ &≥ (x-2)^3 \end{aligned} \]

Somit erhalten wir die obere Schranke \(x_0\):

\[ \begin{aligned} &\; y &= (x_0-2)^3 \\ ⇔ \\ &\; x_0 &= \sqrt[3]{y} + 2 \end{aligned} \]

function c_A(y)
    x₀ = ceil(Int, y^(1/3) + 2)
    for x in 0:x₀
        if p(x) == y
            return 1
        end
    end
    return 0
end

Zeigen Sie, dass die Menge \[ A = \{n ∣ n ∈ ℕ \text{ und } x^n - y^n = (x-y)^n \text{ hat eine Lösung } (x,y) ∈ ℕ^2 \text{ mit } x \neq y\} \] entscheidbar ist.

Idee: Wir zeigen dass \(A = ℕ_+\) ist.

  • \(n=0\), \(\quad x^0 - y^0 = (x-y)^0\) hat keine Lösung.
  • \(n>0\), \(\quad 1^n - 0^n = (1-0)^n\) ist eine Lösung für alle \(n>0\).

Daher können wir \(c_A\) wie folgt berechnen:

function c_A(n)
    return n > 0 ? 1 : 0
end

Zeigen Sie, dass der Wertebereich \(R_{f_p}\) der Funktion \(f_p: ℕ → ℕ\) mit \[ f_p(x) = \max\{p(x), 0\} \] entscheidbar ist, wobei \(p\) ein beliebiges Polynom mit ganzzahligen Koeffizienten ist.

Mit folgendem Programm können wir \(R_{f_p}\) entscheiden:

function C_fp(y)
    x0 = gp(y)
    for i ∈ 0:x0
        if p(i) == y
            return 1
        end
    end
    return 0
end

Dafür brachen wir aber eine berechenbare obere Grenze \(g_p(y)\), die folgende Eigenschaften hat:

\[|p(y)| ≤ | p(g_p(y) + n )| \quad ∀n ∈ ℕ\]

Wir bemerken, dass das genau dann der Fall ist, wenn \(g_p^{-1}(x) ≤ |p(x)|\) ist für alle \(x ∈ ℕ\).

Das ganze wäre sehr einfach, wenn \(p\) monoton wächst oder fällt, da wir dann einfach \(g_p(y) = y\) setzen könnten. Der Trick ist es einen Wert \(x_p\) zu finden, nachdem es keine Nullstellen mehr geben kann und nachdem das Polynom sicher monoton wächst oder fällt. Dann hätten wir \(g_p(y) := x_p + y\).

Wir möchten also ein \(x_p\) finden, so dass gilt:

\(|p(x)| = |a_n x^n + ... + a_0 x^0| > 0\) für alle \(x > x_p\), es gibt keine Nullstellen mehr,

und

\(|δ_x\{p(x)\}| = |n a_n x^{n-1} + ... + 1 a_1 x^0| > 0\) für alle \(x > x_p\), das Polynom wächst oder fällt monoton.

\[⇐\]

\(|a_n x_p^n| ≥ |a_{n-1} x_p^{n-1} + ... + a_1 x_p^1| + |a_0 x_p^0|\)

\[⇐\]

\(|a_n x_p| ≥ |a_{n-1}| + ... + |a_0|\)

\[⇐\]

\(x_p = |a_{n-1}| + ... + |a_0|\)

Damit ist

\[g_p(y) = x_p + y\]

eine genügende hohe obere Schranke für \(y\).

gp(y) = sum(abs(a) for a ∈ coefficients[1:end-1]) + y

Definition: Aufzählbarkeit

\(Y ⊆ ℕ\) heißt aufzählbar, falls \(Y = ∅\) oder es eine berechenbare Funktion \(f:ℕ → ℕ\) gibt, mit \(\text{Bild}(f) = \text{Range}(f) = R_f = Y\).

\(X ⊆ ℕ^n\) heißt aufzählbar, falls es eine bijektive, berechenbare Funktion \[c : ℕ^n → ℕ\] gibt, sodass die Menge \(Y = \{ c(x_1, ..., x_n) | (x_1, ..., x_n) ∈ X \} ⊆ ℕ\) aufzählbar ist.

Die Menge der aufzählbaren Mengen nennen wir \(\textbf{RE}\) (recursively enumerable).

Die Menge der entscheidbaren Mengen nennen wir \(\textbf{R}\) (recursive).

Die Menge der deren Komplement aufzählbar ist, nennen wir \(\textbf{co-RE}\).

Eine Menge \(A ⊆ ℕ\) ist genau dann entscheidbar, wenn \(A\) und \(\overline{A}\) aufzählbar sind.

bzw.

\[ A \text{ ist entscheidbar} \quad ⇔ \quad A \in \textbf{RE} \; ∧ \; A \in \textbf{co-RE} \]

Damit ist die Menge der entscheidbaren Mengen genau \(\textbf{R} = \textbf{RE} \cap \textbf{co-RE}\).

Sei \(f : ℕ → ℕ\) berechenbar. Dann sind \(\text{Domain}(f)=D_f\) und \(\text{Range}(f) =R_f\) aufzählbar.

somit folgt, dass für eine beliebige Menge \(A ⊆ ℕ\) gilt:

\(A\) ist aufzählbar.

\(⇔\)

\(A\) ist Wertebereich einer einstelligen, berechnbaren Funktion.

\(⇔\)

\(A\) ist Definitionsbereich einer einstelligen, berechenbaren Funktion.

Sind \(A\) und \(B\) aufzählbar, so sind \(A ∪ B\) und \(A ∩ B\) aufzählbar.

Definition: Projektion

Für eine binäre Relation \(R ⊆ ℕ × ℕ\) definieren wir die Projektion von \(R\) als die Menge

\[ \text{Proj}(R) := \{ x ∈ ℕ \mid ∃y ∈ ℕ : (x, y) ∈ R \} \]

Projektionssatz

Es sei \(X ⊆ ℕ\). Dann ist \(X\) genau dann aufzählbar, wenn \(X\) Projektion einer binären entscheidbaren Relation \(R ⊆ ℕ × ℕ\) ist.

Propositionen zum Projektionssatz

  1. Jede Umkehrrelation einer entscheidbaren Relation ist entscheidbar.

Wir definieren die Projektion vom i-ten Argument einer n-ären Relation \(R ⊆ ℕ^n\) als die Menge \[ \text{Proj}_i(R) := \{ x_i ∈ ℕ \mid ∃x_1,...,x_{i-1},x_{i+1},...,x_n ∈ ℕ : (x_1,...,x_n) ∈ R \} \]

  1. Mithin ist auch \(Y = \text{Proj}_2(R)\) aufzählbar, ergo gilt der Projektionssatz auch für das zweite Argument.

  2. Sei \(R ⊆ ℕ^n\) eine entscheidbare Relation. Dann sind \(X_1 = \text{Proj}_1(R), ..., X_n = \text{Proj}_n(R)\) aufzählbar.

  1. Zeigen Sie, dass jede umkehr Relation einer entscheidbaren Relation entscheidbar ist.

Sei \(R ⊆ ℕ^2\) eine entscheidbare Relation. Dann ist die umkehr Relation \(R^{-1} = \{ (y, x) | (x, y) ∈ R \}\) entscheidbar, da die charakteristische Funktion von \(R^{-1}\) durch die charakteristische Funktion von \(R\) berechnet werden kann:

function CRinv(y, x)
    return CR(x, y)
end
  1. Zeigen sie dass für jede entscheidbare Relation \(R ⊆ ℕ^2\) auch \(Y = \text{Proj}_2(R)\) aufzählbar ist.

Sei \(R ⊆ ℕ^2\) eine entscheidbare Relation. Dann ist auch die Umkehr Relation \(R^{-1} = \{ (y, x) | (x, y) ∈ R \}\) entscheidbar. Somit ist die Projektion von \(R^{-1}\) auf das erste Argument, also die Menge \(\{ y ∈ ℕ | ∃x ∈ ℕ : (y, x) ∈ R^{-1} \} = Y\) aufzählbar.

  1. Zeigen sie folgendes: Sei \(R ⊆ ℕ^n\) eine entscheidbare Relation. Dann sind \(X_1 = \text{Proj}_1(R), ..., X_n = \text{Proj}_n(R)\) aufzählbar.

Wir machen eine Vollständige Induktion über \(n\).

IA:

  • \(n = 1\): dann ist \(X_1 = R\) und damit sogar entscheidbar, also auch aufzählbar.
  • \(n = 2\): Ist der klassische Projektionssatz. Wir haben auch bereits gezeigt, die rechte Seite ebenfalls aufzählbar ist.

IS, \(n > 2\):

IV: Für alle \(k < n\) gilt die Aussage.

Sei nun \(R ⊆ X_1 × ... × X_n\) eine entscheidbare Relation und \(c: ℕ^2 \to ℕ\) eine berechenbare bijektive Funktion, sodass es die totalen berechenbaren Funktionen \(c^{-1}_1\) und \(c^{-1}_2\) gibt mit \(c^{-1}(x,y) = (c^{-1}_1(x), c^{-1}_2(y))\). Da \(R\) entscheidbar ist, ist die charakteristische Funktion \(CR\) berechenbar. Somit ist nach der IV auch die Funktion \(CR_2: ℕ^n \to \{0,1\}\) berechenbar definiert durch

\[ CR_2(x_1,...,x_{n-1}) = CR(x_1,..., x_{n-2}, c^{-1}_1(x_{n-1}), c^{-1}_2(x_{n-1})) \]

Da \(CR_2\) berechenbar ist, ist die Relation \(R_2 = \{ (x_1,...,x_{n-1}) | CR_2(x_1,...,x_{n-1}) = 1 \}\) entscheidbar. Somit sind \(X_1,...,X_{n-2}, c(X_{n-1} × X_n)\) sicher aufzählbar. Damit ist aber nach Definition der Aufzählbarkeit auch \(X_{n-1} × X_n\) aufzählbar, da \(c\) eine bijektive Funktion ist und \(c(X_{n-1} × X_n)\) aufzählbar ist.

  1. Zeigen Sie oder widerlegen Sie folgende Aussage: Jede berechenbare (partielle) Funktion \(f: ℕ^n → ℕ\) ist eine entscheidbare Relation \(f ⊆ ℕ^{n+1}\).

Nein, wenn wir versuchen würden \(C_f\) zu berechnen könnten wir auf eine Problem stoßen, nähmlich dass \(f\) möglicherweise für einige Eingaben nicht definiert ist, also nicht hält und wir das nicht Voraussehen können. Somit ist \(f\) nur dann eine entscheidbare Relation, wenn \(D_f\) entscheidbar ist, bzw. wir wissen, für welche Eingaben \(f\) definiert ist.

function Cf(xs...,y)
    # first check if f(xs...) is defined, then check if it equals y
    if CDF(xs...)
        if f(xs...) == y
            return 1
        else
            return 0
        end
    else
        return 0
    end
end

Unentscheidbare Mengen

Das Halteproblem

Wir können jedes RAM programm \(M_x\) als eine natürliche Zahl \(x\) kodieren.

Symbol R G I → + - = ≠
Kodierung 111 112 121 122 211 212 221 222

ist eine natürliche Zahl keine gültige RAM, so wird sie als STOP kodiert.

somit können wir jeder Natürlichen Zahl \(x\) eine RAM \(M_x\) zuordnen.

Diesen Vorgang nennt man auch Gödelisierung. Wir können auch funktionen von höheren Programmiersprachen wie Julia oder Python auf diese Weise kodieren. Der Vorgang ist aber auch reversibel, d.h. wir können aus einer natürlichen Zahl \(x\) die RAM \(M_x\) rekonstruieren und damit auch eine semantisch äquivalenten Julia Funktion, wie die, die wir ursprünglich kodiert haben.

Das Halteproblem ist nicht nur für uns Informatiker ein fundamentales Problem, sondern auch für die Mathematik selbst, da es die Grenzen der Beweisbarkeit aufzeigt und zeigt das Mathematik kein abgeschlossenes System ist.

  • Wichtige Stellen: 13:34 - 20:52 und 21:19 - 27:17

Das Hilbertsche Programm, initiiert von David Hilbert zu Beginn des 20. Jahrhunderts, war ein ehrgeiziger Versuch, die gesamte Mathematik auf ein sicheres, formales Fundament zu stellen. Es zielte darauf ab, die Grundlagenkrise der Mathematik zu lösen, indem die Mathematik in ein formales System überführt wird, für das drei Haupteigenschaften gefordert wurden:

  1. Konsistenz (Widerspruchsfreiheit): In der Mathematik gibt es keine Aussage \(A\), so dass sowohl \(A\) als auch \(\neg A\) beweisbar sind.

  2. Vollständigkeit: Für jede Aussage \(A\) in der Mathematik gilt: entweder \(A\) oder \(\neg A\) ist beweisbar.

  3. Entscheidbarkeit: Gibt es ein Verfahren, um zu entscheiden, ob eine Aussage \(A\) aus den Axiomen eines formalen Systems beweisbar ist?

Proposition zu Hilberts Haupteigenschaften:

  1. Es gibt kein formales System, das zeigen kann, dass die Mathematik konsistent ist, wenn sie tatsächlich konsistent ist.

  2. Kein formales System, das die Mathematik beschreibt, ist vollständig.

  3. Es gibt kein Verfahren, um zu entscheiden, ob eine Aussage \(A\) aus den Axiomen eines formalen Systems beweisbar ist.

Der Zusammenhang zwischen Entscheidbarkeit und Beweisbarkeit

1. Gödelisierung als Brücke

Durch Gödelisierung kann jede mathematische Aussage als natürliche Zahl kodiert werden. Damit entspricht:

\[A \subseteq \mathbb{N} \quad \Longleftrightarrow \quad \text{eine Menge von mathematischen Aussagen}\]

Die Frage „ist \(x \in A\)?” ist äquivalent zur mathematischen Frage „gilt Eigenschaft \(A\) für \(x\)?“.

2. Was bedeutet Entscheidbarkeit für Beweisbarkeit?

\[A \text{ ist entscheidbar} \quad \Longleftrightarrow \quad \forall x \in \mathbb{N}: \text{ entweder „}x \in A\text{" oder „}x \notin A\text{" ist beweisbar}\]

  • Ein Algorithmus, der \(A\) entscheidet, liefert implizit für jede Instanz einen Beweis - entweder einen positiven oder einen negativen.
  • Unentscheidbarkeit heißt: es gibt Aussagen, die weder beweisbar noch widerlegbar sind.

Definition: spezielles Halteproblem

Die Menge

\[ K_0 = \{x \mid \text{ die RAM } M_x \text{ hält auf die Eingabe } x \} \]

heißt spezielles Halteproblem.

\(K_0\) ist aufzählbar, aber nicht entscheidbar.

Das spezielle Halteproblem \(K_0\) ist aufzählbar, aber nicht entscheidbar. Finden Sie eine entscheidbare Relation \(R\) mit \(\text{Proj}(R) = K_0\).

Wir definieren \(R\) als die Relation die das Programm \(\text{RAM}_x\) auf der eingabe \(x\) für \(y\) Schritte laufen lässt und prüft, ob es hält.

Damit ist \(R\) entscheidbar, da wir nur endlich viele Schritte laufen lassen und \(K_0\) ist die Projektion von \(R\), da \(\text{RAM}_x\) genau dann hält, wenn es für irgendein \(y\) hält.

Die aufzählbare Mengen sind nicht abgeschlossen unter Komplementbildung. Insbesondere ist \(\overline{K_0}\) nicht aufzählbar.

\[ K_0 ∈ \textbf{RE} \quad ∧ \quad K_0 ∉ \textbf{co-RE} \]

Definition: allgemeines Halteproblem

Die Menge

\[ K = \{(x,y) \mid \text{ die RAM } M_x \text{ hält auf die Eingabe } y \} \]

heißt allgemeines Halteproblem.

Anmerkung: \(K\) ist ebenfalls nicht entscheidbar, aber aufzählbar.

\[ K ∈ \textbf{RE} \quad ∧ \quad K ∉ \textbf{co-RE} \]

Definition: Totalitätsproblem

Die Menge

\[ \text{TOT} = \{x \mid \text{ die RAM } M_x \text{ hält auf die Eingabe } y, \quad ∀ y \in ℕ \} \]

heißt Totalitätsproblem.

Anmerkung: \(\text{TOT}\) ist nicht aufzählbar und \(\overline{\text{TOT}}\) ist ebenfalls nicht aufzählbar.

\[ \text{TOT} ∉ \textbf{RE} \quad ∧ \quad \text{TOT} ∉ \textbf{co-RE} \]

Many to One Reduzierbarkeit

Definition: m-Reduzierbarkeit oder auch Many-to-One-Reduzierbarkeit

Seien \(A, B ⊆ ℕ\). Falls eine \(f:ℕ→ℕ\) eine totale berechenbare funktion gibt mit

\[x ∈ A \quad ⟷ \quad f(x) ∈ B\]

bzw.

\[C_A(x) = C_B(f(x))\]

sagen wir: \(A\) ist m-reduzierbar auf \(B\) bzw.

\[ A ≤_m B \]

\(f\) heißt dann Reduktionsfunktion.

Für Mengen \(A, B, C ⊆ ℕ\) gilt:

  1. reflexivität: \(A ≤_m A\)
  2. transitivität: \(A ≤_m B \; ∧ \; B ≤_m C \quad ⟹ \quad A ≤_m C\)
  3. \(A ≤_m B \quad ⇔ \quad \overline{A} ≤_m \overline{B}\)
  4. \(A ≤_m ∅ \quad ⇔ \quad A = ∅\)
  5. \(A ≤_m ℕ \quad ⇔ \quad A = ℕ\)
  1. Zeigen Sie, das \(≤_m\) reflexiv ist.

Wir nutzen als Reduktionsfunktion die Identität \(f(x) = x\). Dann gilt: \[ x ∈ A \quad ⟷ \quad f(x) = x ∈ A \]

  1. Zeigen Sie, das \(≤_m\) transitiv ist.

Angenommen \(A ≤_m B\) mit Reduktionsfunktion \(f\) und \(B ≤_m C\) mit Reduktionsfunktion \(g\). Dann ist die Funktion \(h(x) = g(f(x))\) eine Reduktionsfunktion von \(A\) auf \(C\), da gilt: \[ x ∈ A \quad ⟷ \quad f(x) ∈ B \quad ⟷ \quad g(f(x)) = h(x) ∈ C \]

  1. Beweisen Sie die Komplementstabilität der m-Reduktion, d.h. zeigen Sie, dass für Mengen \(A, B ⊆ ℕ\) gilt:

\[ A ≤_m B \quad ⇔ \quad \overline{A} ≤_m \overline{B} \]

\[ \begin{aligned} A ≤_m B &\quad ⇔ \quad ∃f: ℕ → ℕ \text{ berechenbar}, ∀x ∈ ℕ: x ∈ A \leftrightarrow f(x) ∈ B \\ &\quad ⇔ \quad ∃f: ℕ → ℕ \text{ berechenbar}, ∀x ∈ ℕ: x ∉ A \leftrightarrow f(x) ∉ B \\ &\quad ⇔ \quad ∃f: ℕ → ℕ \text{ berechenbar}, ∀x ∈ ℕ: x ∈ \overline{A} \leftrightarrow f(x) ∈ \overline{B} \\ &\quad ⇔ \quad \overline{A} ≤_m \overline{B} \end{aligned} \]

  1. Zeigen sie dass

\[ ∅ ≤_m A, \quad ∀ A ⊊ ℕ \]

und

\[ ℕ ≤_m A, \quad ∀ A ⊆ ℕ \;∧\; A ≠ ∅ \]

  • \(∅ ≤_m A, \quad ∀ A ⊊ ℕ\): Da A eine echte Teilmenge von ℕ ist, gibt es ein Element \(x_0 ∈ ℕ\) mit \(x_0 ∉ A\). Wir nutzen als Reduktionsfunktion die konstante Funktion \(f(x) = x_0\). Dann gilt: \[ x ∈ ∅ \quad ⟷ \quad f(x) = x_0 ∈ A \]

  • \(ℕ ≤_m A, \quad ∀ A ⊆ ℕ \;∧\; A ≠ ∅\): Da A nicht leer ist, gibt es ein Element \(x_0 ∈ A\). Wir nutzen als Reduktionsfunktion die konstante Funktion \(f(x) = x_0\). Dann gilt: \[ x ∈ ℕ \quad ⟷ \quad f(x) = x_0 ∈ A \]

  1. Welche Eigenschaft fehlt, damit \(≤_m\) eine Äquivalenzrelation ist? Und welche Eigenschaft fehlt, damit \(≤_m\) eine Halbordnung, oder Ordnung ist? Inwiefern erzeugt \(≤_m\) Klassen von Mengen und ordnet diese?

Die Symmetrie fehlt, damit \(≤_m\) eine Äquivalenzrelation ist. Die Antisymmetrie und die Linearität fehlen, damit \(≤_m\) eine Ordnungsrelation ist.

Eine solche Relation nennen wir eine Präordnung.

Ähnlich wie bei Äquivalenzrelationen erzeugt sie Klassen von Mengen, aber ordnet diese partiell!

Mithin gibt es die Äquivalenzklassen:

  • \(\{ℕ\}\)
  • \(\{∅\}\)
  • \((\textbf{RE}\cap \textbf{co-RE}) \setminus \{ℕ, ∅\}\)
  • \([K]_{≡_m}\)
  • \([\overline{K}]_{≡_m}\)
  • \([TOT]_{≡_m}\)
  • \([\overline{TOT}]_{≡_m}\)
  • und unendlich viele weitere Äquivalenzklassen von unentscheidbaren Mengen.

\(≡_m := \{(A, B) \mid A, B ⊆ ℕ \text{ und } A ≤_m B \text{ und } B ≤_m A\}\) ist eine Äquivalenzrelation auf \(P(ℕ)\).

Mithin gibt es die Äquivalenzklassen:

  • \(\{ℕ\}\)
  • \(\{∅\}\)
  • \((\textbf{RE}\cap \textbf{co-RE}) \setminus \{ℕ, ∅\}\)
  • \([K]_{≡_m}\)
  • \([\overline{K}]_{≡_m}\)
  • \([TOT]_{≡_m}\)
  • \([\overline{TOT}]_{≡_m}\)
  • und unendlich viele weitere Äquivalenzklassen von unentscheidbaren Mengen.

Für Mengen \(A, B ⊆ ℕ\) gilt:

  1. Ist \(A\) entscheidbar und ist \(B ∉ \{∅, ℕ\}\), so gilt \(A ≤_m B\)
  2. Gilt \(A ≤_m B\) und ist \(B\) entscheidbar, so ist \(A\) entscheidbar.
  3. Gilt \(A ≤_m B\) und ist \(B\) aufzählbar, so ist \(A\) aufzählbar.

Beweisen Sie die vorherige Proposition:

  1. Ist \(A\) entscheidbar und ist \(B ∉ \{∅, ℕ\}\), so gilt \(A ≤_m B\).

Da \(A\) entscheidbar ist, ist die charakteristische Funktion \(C_A\) berechenbar. Da \(B\) nicht leer und nicht alles ist (\(B ∉ \{∅, ℕ\}\)), gibt es Elemente \(b_0 ∈ B\) und \(b_1 ∉ B\). Wir definieren die Reduktionsfunktion \(f: ℕ → ℕ\) durch:

function f(x)
    if C_A(x) == 1
        return b_0
    else
        return b_1
    end
end

Damit ist \(f\) berechenbar und erfüllt die Bedingung \(x ∈ A \leftrightarrow f(x) ∈ B\), also \(A ≤_m B\).

  1. Gilt \(A ≤_m B\) und ist \(B\) entscheidbar, so ist \(A\) entscheidbar.

Da \(A ≤_m B\) gilt, existiert eine berechenbare Funktion \(f: ℕ → ℕ\), sodass \(x ∈ A \leftrightarrow f(x) ∈ B\). Da \(B\) entscheidbar ist, existiert eine berechenbare charakteristische Funktion \(C_B\). Wir definieren die charakteristische Funktion für \(A\) durch:

function C_A(x)
    return C_B(f(x))
end

Da \(C_A\) berechenbar ist, ist \(A\) entscheidbar.

  1. Gilt \(A ≤_m B\) und ist \(B\) aufzählbar, so ist \(A\) aufzählbar.

Da \(A ≤_m B\) gilt, existiert eine berechenbare Funktion \(f: ℕ → ℕ\), sodass \(x ∈ A \leftrightarrow f(x) ∈ B\). Da \(B\) aufzählbar ist, existiert eine berechenbare Funktion \(g: ℕ → ℕ\), sodass \(D_g = B\). Wir definieren die Funktion für \(A\) durch:

function h(n)
    return g(f(n))
end

Da \(h\) berechenbar ist und \(D_h = A\), ist \(A\) aufzählbar.

Theorem von Myhill (1955)

Für jede aufzählbare Menge \(A\) gilt \(A ≤_m K_0 ≡_m K\).

Wir benötigen dafür die Gödelisierung. Die Gödelisierung ist eine injektive Funktion, die eine surjektive Umkehrfunktion hat. Diese können wie folgt in Julia deklariert werden:

# injektiv
gödelisierung(f::Function):: Integer

# surjektiv
degödelisierung(x::Integer):: Function

Wir zeigen zuerst, dass wenn \(A\) aufzählbar ist, \(A ≤_m K\) gilt:

Sei \(A\) eine aufzählbare Menge.

\(⇒ ∃f: ℕ → ℕ\) berechnbar, sodass \(D_f = A\).

function g(x)
    return (gödelisierung(f), x)
end

\(g\) ist damit berechenbar und erfüllt die Bedingung:

\[x ∈ A \; \longleftrightarrow \; g(x) ∈ K\]

Nun zeigen wir, dass \(K ≤_m K_0\) gilt:

Betrachte die berechenbare Funktion \(q:ℕ^2 → ℕ\) das für jedes Paar \((i, y) ∈ ℕ^2\) ein Programm \(M_{q(i,x)}\) konstruieren das \(M_i\) auf der Eingabe \(y\) simuliert und die eigene Eingabe ignoriert. Da für jedes Paar \((i, y) ∈ ℕ^2\) ein solches Programm \(M_{q(i,y)}\) konstuierbar ist, ist \(q\) berechenbar und total:

function q(i, y)
    function f(x)
        M_i::Function = degödelisierung(i)
        return M_i(y)
    end
    return gödelisierung(f)
end

\(q\) erfüllt damit die Bedingung:

\[ (i, y) ∈ K \quad ⟷ \quad q(i, y) ∈ K_0 \]

Die Programme, die eine Funktion in einer Klasse \(\mathcal{F}\) berechnen, bilden die Menge

\[ P(\mathcal{F}) := \{i \mid \text{ RAM } M_i \text{ berechnet eine Funktion in } \mathcal{F}\} \]

Sei \(\mathcal{F} ⊂ \textbf{TM}\) mit \(\mathcal{F} ≠ ∅\). Dann gilt:

  1. Ist \(ν¹ ∈ \mathcal{F}\), so gilt \(\overline{K_0} ≤_m P(\mathcal{F})\)

  2. Ist \(ν¹ ∉ \mathcal{F}\), so gilt \(K_0 ≤_m P(\mathcal{F})\)

Hasse Diagramm zu den \(m\)-Reduktionsklassen:

Satz von Rice (1953)

Jede nichttriviale Eigenschaft von Funktionen ist unentscheidbar.

Es sei \(\mathcal{F} ⊊ \textbf{TM}\) mit \(\mathcal{F} ≠ ∅\) und \(\mathcal{F} ≠ \textbf{TM}\). Dann gilt:

  1. Ist \(ν^1 ∈ \mathcal{F}\), so ist \(P(\mathcal{F})\) nicht aufzählbar.
  2. Ist \(ν^1 ∉ \mathcal{F}\), so ist \(\overline{P(\mathcal{F})}\) nicht aufzählbar.
  3. \(P(\mathcal{F})\) ist unentscheidbar.

⚠️ Die Aussage ist nicht: \(ν^1 ∉ \mathcal{F} \; ⇒ \; P(\mathcal{F})\) ist aufzählbar

Die Menge \[ \overline{P(\{ν^1\})} = \{x \mid \text{ die RAM } M_x \text{ hält auf irgendeiner Eingabe } y ∈ ℕ \} \] ist aufzählbar.

⚠️ Diese Aussage folgt nicht aus dem Satz von Rice

Die Menge \[ P(\{x ↦ x^2\}) = \{x \mid \text{ die RAM } M_x \text{ berechnet } y ↦ y^2 \text{ für jede Eingabe } y ∈ ℕ \} \] ist nicht aufzählbar.

⚠️ Diese Aussage folgt nicht aus dem Satz von Rice

Teilen sie die folgenden Mengen in die Klassen

  • \(\textbf{RE} ∩ \textbf{co-RE}\): entscheidbare Mengen
  • \(\textbf{RE} ∩ \overline{\textbf{co-RE}}\): aufzählbare, aber nicht entscheidbare Mengen
  • \(\overline{\textbf{RE}}\): nicht aufzählbare Mengen
  1. \(\{n ∣ n ∈ ℕ \text{ und das Polynom } x^n - n \text{ hat eine Nullstelle in den reellen Zahlen }\}\)

  2. \(\{n ∣ n ∈ ℕ \text{ und das Polynom } x^n - n \text{ hat eine Nullstelle in den ganzen Zahlen }\}\)

  3. \(\{ i ∣ \text{die von der RAM } M_i \text{ berechnete einstellige Funktion besitzt eine Nullstelle} \}\)

  4. \(\{ i ∣ \text{ die von der RAM } M_i \text{ berechnete einstellige Funktion besitzt keine Nullstelle }\}\)

  5. \(\{ i \mid \text{RAM } M_i \text{ hält auf mindestens } 2^i \text{ Eingaben aus } \mathbb{N} \}\)

  6. \(\{n ∣ n ∈ ℕ \text{ und das Polynom } x^n + n \text{ hat eine Nullstelle in den reellen Zahlen }\}\)

  7. \(P(\{f:ℕ → ℕ \mid f ∈ \textbf{PRIM} \; ∧ \; 0 ∉ D_f \})\)

  8. \(P(\{f:ℕ → ℕ \mid f ∈ \textbf{PRIM} \; ∧ \; 0 ∈ D_f \})\)

  9. \(\{ i \mid \text{RAM } M_i \text{ hat mindestens } i \text{ Befehle} \}\)

  10. \(\{ i \mid \text{RAM } M_i \text{ hält für eine ungerade Anzahl von Eingaben } \}\)

  11. \(\{n \mid n \in ℕ \text{ und } x^n + y^n = (x+y)^n \text{ hat eine Lösung } (x,y)∈ ℕ^2 \text{ mit } x ≠ y\}\)

  12. \(P(\{f:ℕ → ℕ \mid f \text{ ist berechenbar}\})\)

  13. \(P(\{f:ℕ → ℕ \mid f \text{ ist nicht berechenbar}\})\)

  14. \(P(\{x ↦ x^2\})\)

  15. \(\overline{P(\{ν^1\})}\)

  16. \(\overline{P(\{x ↦ x^2\})}\)

  17. \(\overline{P(\{x ↦ x, x ↦ x^3\;∸\;x^2, \nu^1\})}\)

  • \(\textbf{RE} ∩ \textbf{co-RE}\): (a), (b), (f), (g), (i), (j), (k), (l), (m)
  • \(\textbf{RE} ∩ \overline{\textbf{co-RE}}\): (c), (e), (o), (q)
  • \(\overline{\textbf{RE}}\): (d), (h), (j), (n), (p)

Komplexitätstheorie

Laufzeit von Algorithmen

Sei \(M\) ein Algorithmus der die totale Funktion \(f: (Σ^*)^m → Σ^*\) berechnet.

Laufzeitfunktion:

\[t_M: (Σ^*)^m → ℕ\]

Definition: Laufzeit

Sei \(t: ℕ → ℕ\) eine totale Funktion.

  1. Algorithmus M berechnet \(f:(Σ^*)^m → Σ^*\) in der Zeit \(t\) falls gilt:

    • \(M\) berechnet \(f\)
    • \(t_M(x_1, ..., x_m) ≤ t(|x_1|, ..., |x_m|)\)
  2. Ein Algorithmus \(M\) berechnet \(f : (Σ^∗)^m → Σ^∗\) in der Zeit \(\mathcal{O}(t)\), falls es ein \(c > 0\) gibt, sodass \(M\) die Funktion \(f\) in der Zeit \(c · t(n) + c\) berechnet.

  3. Ein Algorithmus \(M\) entscheidet \(A ⊆ (Σ^∗)^m\) in der Zeit \(t\) (bzw. \(\mathcal{O}(t)\)), falls \(M\) die charakteristische Funktion \(c_A\) in der Zeit \(t\) (bzw. \(\mathcal{O}(t)\)) berechnet.

Für jede dyadisch kodierte Zahl \(x\) gilt:

\[ 2^{|x|} - 1 ≤ x ≤ 2^{|x|+1} - 2 \]

zudem gilt:

\[ \log_2(x) ≤ |x| + 1 \]

Laufzeit von RAM Programmen

Jeder Befehl einer RAM ist genau ein Zeitschritt.

Betrachte das RAM Programm \(M\):

# f(x, y, z) = z + x ⋅ y
0   R3 ← 1
# Begin der Schleife
1   IF R1 = 0 GOTO 3
2   R2 ← R2 + R0
3   R1 ← R1 - R3
4   GOTO 1
# Ende der Schleife
5   R0 ← R2
6   STOP

wir sagen, dass die Schleife \(y\)-mal durchlaufen wird.

\[t_M(x,y,z) = 4y + 3\]

y ist muss noch codiert werden, damit wir die Laufzeit in Abhängigkeit von der Eingabelänge angeben können. Dafür brauchen wir \(|y|\) Stellen, also ist \(y ≤ 2^{|y|+1} - 2\).

\[≤ 4 ⋅ 2^{|y|+1} - 8 + 3\] \[≤ 8 ⋅ 2^{|x|+|y|+|z|} - 5 \] \[≤ 8 ⋅ 2^n\]

Laufzeit von Turingmaschinen

Jede Anwendung einer Übergangsfunktion einer Turingmaschine ist genau ein Zeitschritt.

Betrachte die Turingmaschine \(M\) mit einer eingabe \(x\) die die funktion \(S\) berechnet:

  • \(z_0\) hat genau \(|x|\) Schritte die auf \(→ z_0\) zeigen.
  • \(z_a\) hat \(1 + k\) Schritte, wobei \(k ≤ |x|\) die Anzahl der hinteren 2en im Wort \(x\) ist.
  • \(z_b\) hat \(|x| - k\) viele Schritte.
  • Auf \(z_1\) zeigt genau ein Schritt, entweder von \(z_a\) oder von \(z_b\).

\[t_M(x) = 1 + |x| + (1+k) + (|x| - k) = 2|x| + 2 ≤ 2n + 2\]

Laufzeit von Höheren Programmiersprachen

  • als 0 Schritte zählen 0 intialisierungen. zb z = 0, A = fill(0, 10, 10)
  • als ein schritt zählen: +,-,<,>,≤,≥,==,!=,!,&&,||, Zuweisungen und return.
  • als \(n\) schritte zählen: *,/,÷
function prod(x,y)
    z = 0 # initialisierung zählt als 0 Schritte
    while (y > 0) # y-mal durchlaufen +1 mal aufgerufen
        z += x # zählt als 2 Schritte
        y -= 1 # zählt als 2 Schritte
    end
    return z # zählt als 1 Schritt
end

\[ \begin{aligned} t_{\text{M}}(x,y) &= 5y + 1 + 1 \\ &≤ 5 ⋅ (2^{|y| + 1} - 2) + 2 \quad (\text{Länge der Codierung})\\ &≤ 5 ⋅ 2^{|y| + 1} - 8 \\ &≤ 10 ⋅ 2^{|x| + |y|} - 8 \\ &≤ 10 ⋅ 2^n - 8 \end{aligned} \]

Finden Sie einen Algorithmus, der prod(x,y) in der Zeit \(\mathcal{O}(n)\) berechnet, ohne die Verwendung von * oder ÷.

function prod(x,y)
    # julias normale Vektoren sind 1-indexiert
    a::ZeroIdxVector = zeros(y+1)
    b::ZeroIdxVector = zeros(y+1)
    i = 0
    a[0] = 1 # +1
    b[0] = x # +1
    while a[i] ≤ y # lg(y) mal
        a[i+1] = a[i] + a[i] # +3
        b[i+1] = b[i] + b[i] # +3
        i = i + 1 # +2
    end
    z = 0 
    while i > 0 # lg(y) mal
        i = i - 1 # +2
        if a[i] ≤ y # +1
            y = y - a[i] # +2
            z = z + b[i] # +2
        end
    end
    return z # +1
end

Die Laufzeit ist damit:

\[ \begin{aligned} t_{\text{P}}(x,y) &= 5 + (9 + 8) \cdot |y| \\ &= 5 + 17 \cdot |y| \\ &≤ 5 + 17n \\ \end{aligned} \]

Somit ist die Laufzeit von prod(x,y) in \(\mathcal{O}(n)\).

Die Klasse \(\textbf{P}\)

Ein Algorithmus \(M\) berechnet \(f:(Σ^*)^m → Σ^*\) in Polynomialzeit, falls ein Polynom \[ p(n) = a_0 + a_1n + ... + a_rn^r \] mit \(r, a_0, ..., a_r ∈ ℕ\) gibt, sodass \(M\) die Funktion \(f\) in der Zeit \(p\) berechnet.

Bemerkung: \(Σ^*\) kann mittels \(|Σ|\)-adischer Codierung in polynomieller Zeit in \(ℕ\) kodiert werden.

\(f\) ist durch eine RAM in Polynomialzeit berechenbar.

\(⇔\)

\(f\) ist in Julia in Polynomialzeit berechenbar.

\(⇔\)

\(f\) ist durch eine Turingmaschine in Polynomialzeit berechenbar.

\(⇔\)

\(f\) ist durch eine 1-Turingmaschine in Polynomialzeit berechenbar

\[ \textbf{FP} := \{f ∣ f \text{ ist eine in Polynomialzeit berechenbare Funktion}\} \]

\[ \textbf{P} := \{A ∣ A \text{ ist eine in Polynomialzeit entscheidbare Menge}\} \]

  1. Für alle Mengen \(A\) gilt \(A ∈ \textbf{P} \quad ⇔ \quad c_A ∈ \textbf{FP}\)
  2. sum, prod, md, div \(∈ \textbf{FP}\)
  3. \(\textbf{FP}\) ist abgeschlossen unter Hintereinanderausführung und jedem Operator in \(\textbf{FP}\).
  4. \(\textbf{P}\) ist abgeschlossen unter Komplementbildung, Vereinigung, Durchschnitt.

\(\textbf{FP}\) ist abgeschlossen unter \(\circ, \ZV, \LV, \ID, \SUB\).

Sei \(f: ℕ → ℕ\) eine totale Funktion. Dann gilt:

\[f ∈ \textbf{FP}\]

\[\Leftrightarrow\]

\[∃ p ∈ \textbf{POLY} : |f(x)| ≤ p(|x|) \; ∀x \quad ∧ \quad \{(x,y)\mid y ≤ f(x)\} ∈ \textbf{P}\]

Finden Sie einen Algorithmus, der die Funktion sqrt(x) in Polynomialzeit berechnet.

function sqrt(x)
    if x < 2
        return x
    end
    a::Vector{Integer} = zeros(x+1)
    i = 1
    a[1] = 1
    while a[i] ≤ x # log(x) mal
        a[i+1] = a[i] + a[i]
        i += 1
    end
    # Estimation
    low = a[div(i, 2)]
    high = a[div(i, 2) + 1]
    mid = div(low + high, 2)
    while mid * mid > x || (mid+1) * (mid+1) ≤ x # log²x mal
        if mid * mid ≤ x
            mid = div(mid + high, 2)
        else
            mid = div(low + mid, 2)
        end
    end
    return mid
end

damit ist die Laufzeit von sqrt(x) in \(\mathcal{O}(n^2)\) und damit polynomial.

Datentypen (nicht Prüfunsrelevant)

Da wir uns innerhalb der Klasse \(\textbf{FUNK}(ℕ)\) befinden, können wir nur Funktionen \(f: ℕ^n → ℕ\) berechnen. Höhere Datentypen lassen sich jedoch in Polynomialzeit kodieren:

  • Integer \(≅ ℤ\): bijektiv über \(z(0)=0\), \(z(2n-1)=n\), \(z(2n)=-n\)
  • AbstractFloat \(≅ ℚ = ℤ×ℕ_+\): Kantor-Kodierung von Zähler und Nenner
  • String \(≅ Σ^*\): \(|Σ|\)-adische Kodierung
  • Bool \(≅ \{0,1\}\): \(b(0)=0\), \(b(n)=1\) für \(n>0\)
  • Tupel/Arrays: mehrfache Anwendung der Kantor-Paarungsfunktion

Als zentrales Hilfsmittel dient die Kantor-Paarungsfunktion, eine bijektive Abbildung \(ℕ^2 \leftrightarrow ℕ\), berechenbar in Polynomialzeit:

# ℕ → ℕ × ℕ (Dekodierung)
function cantor_pairing(z)
    # isqrt ist die ganzzahlige Wurzel in Julia
    w = (isqrt(1 + 8*z) - 1) ÷ 2
    t = w * (w + 1) ÷ 2
    y = z - t
    return (w - y, y)
end

# ℕ × ℕ → ℕ (Kodierung)
function cantor_unpairing(i, j)
    return (i + j) * (i + j + 1) ÷ 2 + j
end

Zusammengesetzte Typen wie

struct Person
    first_name::String
    last_name::String
    age::Integer
    sex::Bool
end

können wir die jeweiligen Felder mit den oben beschriebenen Kodierungen in eine natürliche Zahl kodieren so das wir ein \(ℕ^4\) tupel erhalten, welches wir wiederum mit der Kantor-Paarungsfunktion in eine natürliche Zahl in Polynomialzeit kodieren können.

Nach dem Induktionsprinzip gilt dann:

Jeder zusammengesetzte Datentyp ist in Polynomialzeit in eine natürliche Zahl kodierbar und wieder dekodierbar.

Die Klasse \(\textbf{NP}\)

Eine Menge \(A ⊆ Σ^*\) liegt genau dann in der Klasse \(\textbf{NP}\), wenn ein Polynom \(p\) und eine Menge \(B ∈ \textbf{P}\) existieren, sodass für alle \(x ∈ Σ^*\) gilt:

\[x ∈ A ⟺ (∃y)\;\big[|y| ≤ p(|x|) \; ∧ \; (x, y) ∈ B\big]\]

\[ \textbf{P} ⊆ \textbf{NP} ⊆ \textbf{EXP} ⊆ \textbf{NEXP} \]

Offene Fragen der Komplexitätstheorie

  1. \(\textbf{P} \stackrel{?}{⊊} \textbf{NP}\)
  2. \(\textbf{NP} \stackrel{?}{⊊} \textbf{EXP}\)
  3. \(\textbf{NP} \stackrel{?}{=} \textbf{coNP}\)
  4. \(\textbf{E} \stackrel{?}{⊊} \textbf{NE}\)
  5. \(\textbf{EXP} \stackrel{?}{⊊} \textbf{NEXP}\)
  6. \(\textbf{LINTIME} \stackrel{?}{⊊} \textbf{NLINTIME}\)

Wir wissen aber dass \(\textbf{LINSPACE} ⊊ \textbf{NLINSPACE}\) gilt.

Die Komplexitätsklassen werden durch die Laufzeit einer Turingmaschine definiert, die die Menge entscheidet.

  • \(\textbf{LINTIME} ≃ \mathcal{O}(n)\)
  • \(\textbf{P} ≃ \mathcal{O}(n^c)\)
  • \(\textbf{E} ≃ \mathcal{O}(2^{cn})\)
  • \(\textbf{EXP} ≃ \mathcal{O}(2^{n^c})\)

⚠️ In der Literatur wird oft \(\textbf{LIN} := \textbf{LINSPACE}\) definiert!

Mögliche Ergebnisse des \(\textbf{P}\) vs \(\textbf{NP}\) Problems:

  1. \(\textbf{P}=\textbf{NP}\)
  2. \(\textbf{P} \subsetneq \textbf{NP}\)
  3. \(\textbf{P} \stackrel{?}{=} \textbf{NP}\) ist ein gödelsches Problem, d.h. es kann weder bewiesen noch widerlegt werden.
  4. \(\textbf{P} \subsetneq \textbf{NP}\) ist unabhängig von den Axiomen der Mengenlehre, d.h. es ist selbst ein Axiom.

Nichtdeterministische Maschienen

Wir können an bestimmten Stellen unsere Berechnungsmodelle in mehrere separate Threads aufteilen.

Nichtdeterministische Turingmaschinen

Statt einer Übergangsfunktion \(f: Z × Σ^k → Z × Σ × \{L, 0, R\}\) haben wir eine binäre Übergangsrelation die nicht rechtseindeutig sein muss: \(R ⊆ (Z × Σ^k) × (Z × Σ × \{L, 0, R\})\).

Dadurch können wir für die selbe linke Seite mehrere verschiedene rechte Seiten haben, z.B.

  • \(z_1a → z_2b\)
  • \(z_1a → z_3c\)
  • \(z_1a → z_1a\)

Nichtdeterministische RAMs

Mehrere Befehle können auf die gleiche Zeilennummer zeigen, z.B.:

0   IF R1 = 0 GOTO 2
1   R2 ← R2 + R0
2   R1 ← R1 - 1
# nichtdeterministische Verzweigung
3   STOP
3   GOTO 0

Nichtdeterministische Höhere Programmiersprachen

Wir können in julia ein macro @ndet definieren, das eine nichtdeterministische Verzweigung ermöglicht:

function f(x::Vector{Integer})
    l = length(x)
    result = 0
    for i ∈ 1:l
        # nichtdeterministische Verzweigung mit 3 Threads
        @ndet begin
            result += x[i] |
            result -= x[i] |
            result = result
        end
        if result = 10
            return true
        end
    end
    return false
end

Definition 11

Sei \(M\) ein nichtdeterministischer Algorithmus.

  1. \(M\) akzeptiert eine Eingabe \(x \in (Σ^*)^m\), falls es einen Berechnungspfad gibt, der mit \(M\) auf der Eingabe \(x\) beginnt und auf dem 1 zurückgegeben wird. Ein Pfad, auf dem 1 zurückgegeben wird, heißt akzeptierender Berechnungspfad.
  2. \(L(M) := \{x ∈ Σ^* \mid M \text{ akzeptiert } x\}\) heißt die von \(M\) akzeptierte Menge/Sprache.

Definition 12

Sei \(t:ℕ → ℕ\) total und monoton wachsend.

  1. Ein nichtdeterministischer Algorithmus \(M\) akzeptiert eine Menge \(A ⊆ Σ^*\) in der Zeit \(t\), falls gilt:
    • \(L(M) = A\)
    • Die Laufzeit jedes Berechnungspfades von \(M\) auf jeder Eingabe \(x\) ist höchstens \(t(|x|)\).
  2. Ein nichtdeterministischer Algorithmus \(M\) akzeptiert eine Menge \(A ⊆ Σ^*\) in der Zeit \(\mathcal{O}(t)\), falls es ein \(c > 0\) gibt, sodass \(M\) die Menge \(A\) in der Zeit \(c · t(n) + c\) akzeptiert.

Für jede Menge \(A \in \textbf{NP}\) gilt:

\(A\) wird von einem nichtdeterministischen Algorithmus in Polynomialzeit akzeptiert.

Betrachten Sie die folgende Menge:

\[ \begin{gathered} \text{SOS} = \\ \{ (a_1, ..., a_m, b) \mid \text{es gibt eine Teilmenge } \\ I \subseteq \{1, ..., m\} \text{ mit } \sum_{i \in I} a_i = b \} \end{gathered} \]

  1. Überlegen Sie sich in welcher Klasse \((\textbf{P}, \textbf{NP} \text{ oder } \textbf{EXP})\) die Menge \(\text{SOS}\) liegt.

\[ (a_1, ..., a_m, b) ∈ \text{SOS} \quad ⇔ \quad ∃I ⊆ \{1, ..., m\}: \sum_{i ∈ I} a_i = b \]

Daher gilt:

\[ \text{SOS} ∈ \textbf{NP} \]

  1. Schreiben Sie einen nichtdeterministischen Algorithmus in einer Höheren Programmiersprache, der \(\text{SOS}\) in polynomialzeit entscheidet.
function c_SOS(b, a::Vector{Int})
    l = length(a)
    sum = 0
    for i ∈ 1:l
        @ndet begin
            # wähle a[i] in die Teilmenge
            sum += a[i] |
            # oder ignoriere a[i]
            sum = sum
        end
        if sum == b
            return true
        elseif sum > b
            return false
        end
    end
    return false
end

Da die Schleife \(l\)-mal durchlaufen wird und \(l ≤ n\). Die Laufzeit ist damit:

\[ t(|b|, |a|) = 5l + 2 ≤ 5n + 2 \]

und damit sicher in \(\mathcal{O}(p(n))\). ⚠️ Das heißt nicht das \(\text{SOS} ∈ \textbf{NLIN}\) ist, da wir die Zeitklassen mit Turingmaschinen definieren und nicht mit RAMs.

  1. Schreiben Sie eine nichtdeterministische RAM, die \(\text{SOS}\) in polynomialzeit entscheidet. <R0> = b, <R1> = a_1, …, <R_m> = a_m
0   R(m+1) ← 0 # summe
1   R(m+2) ← 1 # index i
2   R(m+3) ← (m) # l
3   R(m+4) ← 1 # für i+1
4   R(m+5) ← R(m+2) - R(m+3) # i - l
5   IF R(m+5) > 0 GOTO 15 # i ∈ 1:l Schlaufe

# nichtdeterministische Auswahl von a[i] aus der Menge A
6   R(m+1) ← R(m+1) + RR(m+2) # summe = summe + a[i]
6   R(m+1) ← R(m+1)

7   R(m+5) ← R(m+1) - R0 # summe - b
8   R(m+6) ← R0 - R(m+1) # b - summe
9   IF R(m+5) > 0 GOTO 15 # summe > b
10  IF R(m+6) = 0 GOTO 13 # summe == b
11  R(m+2) ← R(m+2) + R(m+4) # i = i + 1
12  GOTO 3
# Resultat ausgeben
13  R0 ← 1 # summe = b
14  STOP
15  R0 ← 0 # summe != b
16  STOP

Betrachten Sie die folgende Menge:

\[ \begin{gathered} \text{KNAPSACK} = \\ \{ (v_1, ..., v_m, V, w_1, ..., w_m, W) \mid \text{es gibt eine Teilmenge } \\ I \subseteq \{1, ..., m\} \text{ mit } \sum_{i \in I} w_i \le W \text{ und } \sum_{i \in I} v_i \ge V \} \end{gathered} \]

  1. Überlegen Sie sich in welcher Klasse \((\textbf{P}, \textbf{NP} \text{ oder } \textbf{EXP})\) die Menge \(\text{KNAPSACK}\) liegt.

\[ (v_1, ..., v_m, V, w_1, ..., w_m, W) ∈ \text{KNAPSACK} \quad ⇔ \quad ∃I ⊆ \{1, ..., m\}: \sum_{i ∈ I} w_i \le W \text{ und } \sum_{i ∈ I} v_i \ge V \]

Daher gilt:

\[ \text{KNAPSACK} ∈ \textbf{NP} \]

  1. Schreiben Sie einen nichtdeterministischen Algorithmus in einer Höheren Programmiersprache, der \(\text{KNAPSACK}\) in polynomialzeit entscheidet.
function c_KNAPSACK(w::Vector, W, v::Vector, V)
    l = length(w)
    @assert length(v) == l
    value = 0
    weight = 0
    for i in 1:l
        @ndet begin
            value += v[i]
            weight += w[i]
            |
            value += 0
            weight += 0
        end
        if (weight ≤ W && value ≥ V)
            return 1
        end
    end
    return 0
end

Da die Schleife \(l\)-mal durchlaufen wird und \(l ≤ n\). Die Asymptotik der Laufzeit ist damit: \[\mathcal{O}(n)\]

  1. Schreiben Sie eine nichtdeterministische RAM, die \(\text{KNAPSACK}\) in polynomialzeit entscheidet. <Rk> = m, <Rk+1> = V, <Rk+2> = W, <Rk+3> = v_1, …, <Rk+2+m> = w_m, <Rk+3+m> = v_1, …, <Rk+2+2m> = w_m

Sie dürfen \(k\) frei wählen.

Einfachhalthalber wählen wir \(k = 100\).

  • R0 = summe der Werte
  • R1 = summe der Gewichte
  • R2 = index i
  • R3 = 0
  • R4 = 1
  • R5 = 103, anfang der Werte
  • R6 = 103 + m, anfang der Gewichte

Lösung:

0   R4 ← 1
1   R5 ← 103
2   R6 ← R5 + R100
3   R7 ← R100 - R2 
4   IF R7 = 0 GOTO 17 # while i < m
5   R8 ← R2 + R5 # Index für v_i
6   R9 ← R2 + R6 # Index für w_i

# nichtdeterministische Auswahl von i
7   R0 ← R0 + RR8
8   R1 ← R1 + RR9

7   R0 ← R0 + R3
8   R1 ← R1 + R3

# überprüfe ob wir eine Lösung gefunden haben
9   R10 ← R1 - R102 # summe der Gewichte - W
10  R11 ← R101 - R0 # V - summe der Werte
11  R12 ← R10 + R11 # 0 heisst wir haben eine Lösung gefunden
12  IF R12 = 0 GOTO 15 # Lösung gefunden

13  R2 ← R2 + R4 # i = i + 1
14  GOTO 3

# Resultat ausgeben
15  R0 ← 1 # Lösung gefunden
16  STOP
17  R0 ← 0 # keine Lösung gefunden
18  STOP

\(\textbf{NP}\)-vollständige Mengen

Sein \(A⊆Σ^*_1\) und \(B⊆Σ^*_2\) Mengen. Falls es eine totale Funktion \(f:Σ^*_1 → Σ^*_2\) mit \(f ∈ \textbf{FP}\) gibt \[ x ∈ A \quad ⟷ \quad f(x) ∈ B \] bzw. \[ C_A(x) = C_B(f(x)) \] gibt, so sagen wir: \(A\) ist in Polynomialzeit reduzierbar auf \(B\) bzw. \[ A ≤^p_m B \]

Anmerkung: \(Σ^*\) kann durch die Induzierung einer totalen Ordnung auf den Symbolen \(Σ\) zu einer \(|Σ|\)-adischen kodierung der Natürlichen Zahlen genutzt werden, daher können wir auch \(A, B ⊆ ℕ\) anstatt \(A⊆Σ^*_1\) und \(B⊆Σ^*_2\) schreiben.

  1. Die Relation \(≤_m^p\) ist reflexiv und transitiv.
  2. Für alle Mengen \(A\) und \(B\) gilt: \(A ≤_m^p B \quad \Leftrightarrow \quad \overline{A} ≤_m^p \overline{B}\)

\(\textbf{P}\) und \(\textbf{NP}\) sind abgeschlossen unter \(≤_m^p\). Daher

  1. Ist \(A ≤_m^p B\) und \(B ∈ \textbf{P}\), so ist \(A ∈ \textbf{P}\).
  2. Ist \(A ≤_m^p B\) und \(B ∈ \textbf{NP}\), so ist \(A ∈ \textbf{NP}\).

Sei \(B ⊊ ℕ\) und \(B ≠ ∅\) (nicht-trivial). Dann gilt: \(A ≤_m^p B\) für alle \(A ∈ \textbf{P}\).

Definition: NP-vollständig

Eine Menge \(B\) heißt NP-vollständig, falls \(B ∈ NP\) und \(A ≤^p_m B\) für alle \(A ∈ \textbf{NP}\).

Definition: NP-schwer

Eine Menge \(B\) heißt NP-schwer, falls \(A ≤^p_m B\) für alle \(A ∈ \textbf{NP}\).

Seien \(A, B ⊆ ℕ\).

  1. Sind \(A\) und \(B\) \(\textbf{NP}\)-vollständig, so gilt \(A ≡^p_m B\).
  2. Ist \(A\) \(\textbf{NP}\)-vollständig und \(A ∈ \textbf{P}\), so gilt \(\textbf{P} = \textbf{NP}\).
  3. Ist \(B ∈ \textbf{NP}\), ist \(A\) \(\textbf{NP}\)-vollständig und \(A ≤^p_m B\), so ist \(B\) \(\textbf{NP}\)-vollständig.

Folgende Mengen sind NP-vollständig:

\[\text{HK} = \{G \mid G \text{ ist ein hamiltonischer Graph }\}\]

Ein Graph \(G\) heißt hamiltonisch, wenn es einen Kreis gibt, der durch alle Knoten genau einmal geht und am Ende wieder zum Startknoten zurückkehrt.

\[ \begin{gathered} \text{SOS} = \\ \{ (a_1, ..., a_n, b) \mid \text{es gibt } I \subseteq [n] \text{ mit } \\ \sum_{i \in I} a_i = b \} \end{gathered} \]

Die SOS-Menge besteht aus Tupeln von Zahlen, für die es eine Teilmenge gibt, deren Summe genau \(b\) ergibt.

\[ \begin{gathered} \text{PARTITION} = \\ \{ (a_1, ..., a_n) \mid \text{ es gibt } I ⊆ [n] \text{ mit } \\ \sum_{i∈I} a_i = \sum_{j∉I} a_j\} \end{gathered} \]

Die PARTITION-Menge besteht aus Tupeln von Zahlen, für die es eine Teilmenge gibt, deren Summe gleich der Summe der restlichen Zahlen ist.

\[ \begin{gathered} 3\text{-FARB} = \\ \{G \mid G \text{ ist ein Graph, der mit } 3 \text{ Farben färbbar ist }\} \end{gathered} \]

Eine Knotenfärbung eines Graphen \(G\) mit 3 Farben ist eine Zuordnung von Farben zu den Knoten von \(G\), sodass benachbarte Knoten unterschiedliche Farben haben. Ein Graph ist 3-färbbar, wenn es eine solche Färbung mit höchstens 3 Farben gibt.

\[ \begin{gathered} 3\text{-SAT} = \\ \{\phi \mid \phi(x_1, ..., x_n) \\ \text{ ist eine erfüllbare Formel in konjunktiver Normalform mit höchstens} \\ 3 \text{ Literalen pro Klausel }\} \end{gathered} \]

Eine Formel in konjunktiver Normalform (CNF) ist eine Konjunktion von Klauseln, wobei jede Klausel eine Disjunktion von Literalen ist. Ein Literal ist entweder eine Variable oder die Negation einer Variable. Eine Formel ist erfüllbar, wenn es eine Belegung der Variablen gibt, die die Formel wahr macht.

z.B die Formel \((x_1 ∨ ¬x_2 ∨ x_3) ∧ (¬x_1 ∨ x_2 ∨ ¬x_3)\) ist in 3-CNF und hat 2 Klauseln mit jeweils 3 Literalen.

\[ \begin{gathered} \text{PUZZLE} = \\ \{ (k, t, R, T_1, \ldots, T_k) \mid k, t \ge 1, R \in (\Sigma)^{4t},\\ T_1, \ldots, T_k \in (\Sigma)^4 \\ \text{ und } R \text{ kann mit Puzzleteilen der Typen } T_1, \ldots, T_k \text{ gefüllt werden} \} \end{gathered} \]

  • \(Σ\): Symbolmenge
  • \(T_k\): Puzzleteiltyp, definiert durch die 4 Symbole an den Seiten
  • \(R\): Puzzle-Region
  • \(k\): Anzahl der Puzzleteiltypen
  • \(t\): Anzahl der Reihen und Spalten in der Puzzle-Region

\[ \begin{gathered} \text{SOS} = \\ \{ (a_1, ..., a_n, b) \mid \text{es gibt } I \subseteq [n] \text{ mit } \\ \sum_{i \in I} a_i = b \} \end{gathered} \]

\[ \begin{gathered} \text{PARTITION} = \\ \{ (a_1, ..., a_n) \mid \text{ es gibt } I ⊆ [n] \text{ mit } \\ \sum_{i∈I} a_i = \sum_{j∉I} a_j\} \end{gathered} \]

Wir möchten zeigen, dass \(\text{SOS} ≡_m^p \text{PARTITION}\) gilt.

  1. Zeigen Sie, dass \(\text{PARTITION} ≤_m^p \text{SOS}\) gilt.

Angenomen wir haben einen Löser in NP für SOS. Wir müssen nun unser Problem PARTITION in eine Instanz von SOS umwandeln, sodass die Antwort des SOS-Lösers auch die Antwort für unsere PARTITION-Instanz ist.

Wir definieren \(S := \sum_{k ∈ [n]} a_k\). Dann gilt:

\[ \sum_{i ∈ I} a_i = \sum_{j ∉ I} a_j = \frac{S}{2} \\ \]

somit können wir die Reduktionsfunktion wie folgt definieren:

function partition_to_sos(a::Vector{Integer})
    S = sum(a) # S = a_1 + ... + a_n; # lengt(a)+1 Schritte ≤ n+1 Schritte
    if S % 2 != 0 # S ist ungerade, also gibt es keine Partitionierung in ℕ
        return [0], 1 # triviale Instanz von SOS, die falsch ist
    end
    b = S ÷ 2 # n + 1 Schritte
    return a, b # 1 Schritt
end

Diese hat eine Laufzeit von \(\mathcal{O}(n)\) und ist damit in \(\textbf{FP}\).

somit gilt: \(\text{PARTITION} ≤_m^p \text{SOS}\).

  1. Zeigen Sie, dass \(\text{SOS} ≤_m^p \text{PARTITION}\) gilt.

Angenomen wir haben einen Löser in NP für PARTITION. Wir müssen nun unser Problem SOS in eine Instanz von PARTITION umwandeln, sodass die Antwort des PARTITION-Lösers auch die Antwort für unsere SOS-Instanz ist.

Dazu müssen wir sicher gehen, dass \(b\) “erkennbar” bleibt, d.h. dass die Partitionierung genau dann möglich ist, wenn es eine Teilmenge von \(a_1, ..., a_n\) gibt, die genau \(b\) ergibt. Dazu definieren wir:

  • \(S := \sum_{k ∈ [n]} a_k\)
  • \(c := S - b\)

\[ \begin{gathered} \sum_{i ∈ I} a_i = b \\ ⇒ \\ \underbrace{S - b}_{c = \sum_{j ∉ I} a_j} + \sum_{i ∈ I} a_i = \sum_{j ∉ I} a_j + \underbrace{b}_{\sum_{i ∈ I} a_i} \end{gathered} \]

Nun haben wir aber ein kleines Problem, da wir nun immer eine triviale Lösung \[ \begin{gathered} \underbrace{(S-b)}_{c} + b = \sum_{j ∉ I} a_j + \sum_{i ∈ I} a_i \\ ⇔ \\ c + b = S \end{gathered} \] für die PARTITION-Instanz haben. Um zu verhindern, dass \(c\) und \(b\) auf der selben Seite der Partition landen können, addieren wir beiden Zahlen \(S\) hinzu:

  • \(c' := c + S = 2S - b\)
  • \(b' := b + S = S + b\)

somit können wir die Reduktionsfunktion wie folgt definieren:

function sos_to_partition(a::Vector{Integer}, b::Integer)
    S = sum(a) # S = a_1 + ... + a_n; # lengt(a)+1 Schritte ≤ n+1 Schritte
    b´ = S + b # 2 Schritte
    c´ = 2S - b # n+2 Schritte
    # Erstellen der neuen Eingabe für PARTITION
    # (a_1, ..., a_n, c´, b´)
    partition_input::Vector{Integer} = vcat(a,b´,c´) # 2 Schritte
    return partition_input # 1 Schritt
end

diese Hat eine Laufzeit von \(\mathcal{O}(n)\) und ist damit in \(\textbf{FP}\).

somit gilt: \(\text{SOS} ≤_m^p \text{PARTITION}\).

Das Knotenfärbungsproblem ist definiert als: \[ k\text{-FARB} = \{G \mid G \text{ ist ein Graph, der mit } k \text{ Farben färbbar ist }\} \]

Das Kantenfärbungsproblem ist definiert als: \[ k\text{-KANTENFARB} = \{G \mid G \text{ ist ein Graph, dessen Kanten mit } k \text{ Farben färbbar sind }\} \]

Zeigen Sie, dass \(k\text{-KANTENFARB} ≤_m^p k\text{-FARB}\) gilt.

Idee: Wir verwandeln Kanten in Knoten um, sodass benachbarte Kanten in \(G\) zu benachbarten Knoten im neuen Graphen \(G'\) werden. Dann ist die Kantenfärbung von \(G\) äquivalent zur Knotenfärbung von \(G'\).

Korrektheit der Reduktion

Der Graph \(G\) ist genau dann k-kantenfärbbar, wenn der Graph \(G'\) k-knotenfärbbar ist, denn jede Kante in \(G\) wird zu einem Knoten in \(G'\), und zwei Kanten in \(G\) teilen genau dann einen Knoten, wenn die entsprechenden Knoten in \(G'\) benachbart sind. Daher kann eine k-kantenfärbung von \(G\) direkt in eine k-knotenfärbung von \(G'\) umgewandelt werden und umgekehrt.

Polynomialzeit der Reduktion

\[ \begin{aligned} &f: \underbrace{(E,V)}_{G} ↦ \underbrace{(E', V')}_{G'} \\ &V' = E \\ &E' = \{(e_1, e_2) ∈ E × E \mid |e_1 ∩ e_2| = 1\} \\ \end{aligned} \]

Wir nehmen an das die Datenstruktur wie folgt aussieht:

struct Graph{Vertex}
    V::Set{Vertex} # Knoten
    E::Set{Set2{Vertex}} # Kanten
end

wobei Set2{Vertex} ein Set mit genau 2 Elementen ist.

Das kann mit folgender Funktion in \(\textbf{FP}\) berechnet werden:

function edge_to_vertex(G::Graph)
    # Get the vertex type
    Vertex::Datatype = typeof(first(G.V))
    E::Set{Set2{Vertex}} = G.E # Kanten von G
    V::Set{Vertex} = G.V # Knoten von G
    E´ = Set{Set2{Set2{Vertex}}}() # Kanten von G´
    for e1 ∈ E # ≤ n-mal
        for e2 ∈ E # ≤ n-mal
            if length(e1 ∩ e2) == 1 # e1 und e2 teilen genau einen Knoten
                push!(E´, Set(e1,e2)) # füge die Kante (e1,e2) zu E´ hinzu
            end
        end
    end
    return Graph(E, E´) # gebe den neuen Graphen G´ zurück
end

Das hat eine Laufzeit von \(\mathcal{O}(n^2)\) und ist damit in \(\textbf{FP}\).

somit gilt: \(k\text{-KANTENFARB} ≤_m^p k\text{-FARB}\).

Sie kennen bereits das Hamiltonsche Kreisproblem: \[ \text{HK} = \{G \mid G \text{ hat einen Kreis, der durch alle Knoten genau einmal geht }\} \]

Das Hamiltonsche Pfadproblem ist definiert als: \[ \text{HP} = \{G \mid G \text{ hat einen Pfad, der durch alle Knoten genau einmal geht }\} \]

Zeigen Sie, dass \(\text{HP} ≤_m^p \text{HK}\) gilt.

Wir bemerken das für immer wenn \(G\) einen Hamiltonschen Kreis hat, es auch einen Hamiltonschen Pfad gibt. Ergo \[ \text{HK} ⊆ \text{HP} \] und damit ist das Problem \(\text{HK}\) eine echt schwierige Version von \(\text{HP}\).

Die einzigen Graphen die in \(\text{HP} ∖ \text{HK}\) sind die Graphen, bei denen die beiden Endknoten jedes Hamiltonschen Pfades nicht miteinander verbundenen sind.

Idee: Wir fügen einen Bröcken-Knoten \(b\) hinzu, der mit allen Knoten des Graphen verbunden ist.

Korrektheit der Reduktion

  • Haben wir zuvor
    • einen Hamiltonischen Pfad, so haben wir mit der Hilfe von \(b\) auch einen Hamiltonischen Kreis, denn wir können den Pfad von einem Endknoten zu \(b\) und dann von \(b\) zum anderen Endknoten gehen.
    • keinen Hamiltonischen Pfad, so haben wir auch mit der Hilfe von \(b\) keinen Hamiltonischen Kreis, denn wir können nicht alle Knoten besuchen, da wir \(b\) nur einmal besuchen dürfen.

Polynomialzeit der Reduktion

Wir nehmen an das die Datenstruktur wie folgt aussieht:

struct Graph{Vertex}
    V::Set{Vertex} # Knoten
    E::Set{Set2{Vertex}} # Kanten
end

wobei Set2{Vertex} ein Set mit genau 2 Elementen ist.

Wir setzen das in folgender Funktion um:

function hp_to_hk(G::Graph)
    # Get the vertex datatype
    Vertex::Datatype = typeof(first(G.V))
    # Copy the vertices and edges of G
    V´::Set{Vertex} = G.V
    E´::Set{Set2{Vertex}} = G.E
    # füge einen neuen Knoten b hinzu
    b::Vertex = Vertex() # neuer Knoten b
    push!(V´, b) # füge den neuen Knoten b zu V´ hinzu
    # füge Kanten von b zu allen Knoten in V´ hinzu
    for v ∈ V´ # ≤ n-mal
        push!(E´, Set(b,v)) # füge die Kante (b,v) zu E´ hinzu
    end
    return Graph(V´, E´) # gebe den neuen Graphen G´ zurück
end

Das hat eine Laufzeit von \(\mathcal{O}(n)\) und ist damit in \(\textbf{FP}\).

Somit gilt: \(\text{HP} ≤_m^p \text{HK}\).

Sie kennen bereits das 3-Sat Problem: \[ \begin{gathered} 3\text{-SAT} = \\ \{\phi \mid \phi(x_1, ..., x_n) \\ \text{ ist eine erfüllbare Formel in konjunktiver Normalform mit höchstens} \\ 3 \text{ Literalen pro Klausel }\} \end{gathered} \]

Das “Independent Set” Problem ist definiert als: \[ \text{IS} = \{ (G, k) \mid G \text{ enthält eine unabhängige Menge mit mindestens } k \text{ Knoten} \} \]

Wobei eine unabhängige Menge in einem Graphen \(G\) eine Teilmenge von Knoten ist, bei der keine zwei Knoten durch eine Kante verbunden sind.

Zeigen Sie, dass \(\text{3-SAT} ≤_m^p \text{IS}\) gilt.

Wir sagen wir haben \(m\) Klauseln. \[ \phi = C_1 ∧ C_2 ∧ ... ∧ C_m \]

Wir verwandeln jedes Literal in einen Knoten (auch wenn es die selbe variable mehrmals vorkommt), so dass wir genau \(3m\) Knoten haben.

Wir verbinden alle Knoten,

  • die in der selben Klausel vorkommen, miteinander, sodass wir genau \(m\) Dreiecke haben.
  • die Knoten, die sich gegenseitig negieren, miteinander, sodass wir keine unabhängige Menge haben können, die sowohl \(x_i\) als auch \(\neg x_i\) enthält.

zum Beispiel die Formel: \[ \phi = \underbrace{(x_1 ∨ ¬x_2 ∨ x_3)}_{C_1} ∧ \underbrace{(¬x_1 ∨ x_2 ∨ x_3)}_{C_2} ∧ \underbrace{(x_2 ∨ ¬x_3 ∨ ¬x_4)}_{C_3} \] wird zu folgendem Graphen \(G\) transformiert:

Wenn wir nun \(k = m\) wählen, haben wir eine polynomielle Reduktion von 3-SAT auf Independent Set konstruiert, den Das Independent Set wäre dann genau eine Lösung für die 3-SAT Formel.

Korrektheit der Reduktion

Eine unabhängige Menge der Größe \(m\) im Graphen \(G\) entspricht genau einer erfüllbaren Belegung der Klauseln in der 3-SAT-Formel. Jede Klausel muss genau ein Literal enthalten, das in der unabhängigen Menge ausgewählt wird, was bedeutet, dass dieses Literal wahr ist. Da keine zwei Knoten in der unabhängigen Menge durch eine Kante verbunden sind, kann kein Literal und sein Negation gleichzeitig ausgewählt werden, was die Konsistenz der Belegung sicherstellt.

Polynomielle Laufzeit der Reduktion

Wir gehen davon aus dass die Formel wie folgt aufgebaut ist:

struct Formula
    # m × 3 Matrix von Literalen
    clauses::Matrix{Literal}
end

Wobei Literal irgendein Datentyp ist, der == und ¬ nach gesundem Menschenverstand implementiert.

Wir nehmen an das die Datenstruktur für den Graphen wie folgt aussieht:

struct Graph{Vertex}
    V::Set{Vertex} # Knoten
    E::Set{Set2{Vertex}} # Kanten
end

wobei Set2{Vertex} ein Set mit genau 2 Elementen ist.

Die Reduktionsfunktion kann dann wie folgt implementiert:

function sat_to_is(φ::Formula)
    # φ = (C_1 ∧ C_2 ∧ ... ∧ C_m)
    k = size(φ.clauses)[1] # Anzahl der Klauseln
    Vetex::Datatype = Tuple{Int,Int}
    V´ = Set{Vetex}() # Knotenmenge von G´
    E´ = Set{Set2{Vetex}}() # Kantenmenge von G´
    # Erstelle Knoten für jedes Literal in jeder Klausel
    for i ∈ 1:k # ≤ k-mal
        for j ∈ 1:3 # ≤ 3-mal
            v = (i,j)
            push!(V´, v) # füge den Knoten v zu V´ hinzu
            # Verbinde alle Knoten in der selben Klausel miteinander
            for l ∈ 1:3 # ≤ 3-mal
                if l != j
                    # füge die Kante (v, (i,l)) zu E´ hinzu
                    push!(E´, Set2(v, (i,l))) 
                end
            end
            # Verbinde alle Knoten, die sich gegenseitig negieren
            for l ∈ 1:k # ≤ k-mal
                for m ∈ 1:3 # ≤ 3-mal
                    u = (l,m)
                    if φ.clauses[i][j] == ¬φ.clauses[l][m]
                        # füge die Kante (v, u) zu E´ hinzu
                        push!(E´, Set2(v, u))
                    end
                end
        end
    end
    return Graph(V´, E´), k # gebe den neuen Graphen G´ und k zurück
end

Dieser Algorithmus hat eine Laufzeit von \(\mathcal{O}(n^2)\) und ist damit in \(\textbf{FP}\).

Somit gilt: \(\text{3-SAT} ≤_m^p \text{IS}\).

Das Vertex Cover Problem ist definiert als: \[ \text{VC} = \{ (G, k) \mid G \text{ enthält eine Knotenüberdeckung mit höchstens } k \text{ Knoten} \} \] eine Knotenüberdeckung in einem Graphen \(G\) ist eine Teilmenge von Knoten, sodass jede Kante in \(G\) mindestens einen Endknoten in der Teilmenge hat.

Das Feedback Vertex Set Problem ist definiert als: \[ \text{FVS} = \{ (G, k) \mid G \text{ enthält eine Feedback-Knotenmenge mit höchstens } k \text{ Knoten} \} \] eine Feedback-Knotenmenge in einem Graphen \(G\) ist eine Teilmenge von Knoten, sodass das Entfernen dieser Knoten aus \(G\) einen azyklischen (kreisfreien) Graphen ergibt.

Zeigen Sie, dass \(\text{VC} ≤_m^p \text{FVS}\) gilt.

Geben ist der Input für das Vertex Cover Problem: Ein Graph \(G = (V, E)\) und eine Zahl \(k\).

Bemerke: Haben wir eine Knotenüberdeckung und entfernen diese Knoten aus dem Graphen, so haben wir sicherlich keine Kreise mehr.

Idee: Wir verwandeln jede Kante in einen Kreis, indem wir einen neuen Knoten für jede Kante hinzufügen.

Korrektheit der Reduktion

  • \(⇒\) Haben wir zuvor eine Knotenüberdeckung mit höchstens \(k\) Knoten, so ist diese nach anwendung der Reduktionsfunktion genau auch eine Feedback-Knotenmenge mit höchstens \(k\) Knoten.
  • \(⇐\) Haben wir nach anwendung der Reduktionsfunktion eine Feedback-Knotenmenge mit höchstens \(k\) Knoten, kann ich falls irgendwelche der neuen Knoten in der Feedback-Knotenmenge sind, diese durch einen der Endknoten der Kante ersetzen, sodass wir eine Knotenüberdeckung mit höchstens \(k\) Knoten erhalten.

Polynomialzeit der Reduktion

Wir nehmen an das die Datenstruktur wie folgt aussieht:

struct Graph{Vertex}
    V::Set{Vertex} # Knoten
    E::Set{Set2{Vertex}} # Kanten
end

Dann sieht die Reduktionsfunktion wie folgt aus:

function vc_to_fvs(G::Graph, k::Int)
    # Get the vertex datatype
    Vertex::Datatype = typeof(first(G.V))
    # Copy the vertices and edges of G
    V´::Set{Vertex} = G.V
    E´::Set{Set2{Vertex}} = G.E
    # füge für jede Kante einen neuen Knoten hinzu
    for e ∈ G.E # ≤ n-mal
        # mache aus den Knoten der Kante e einen neuen Knoten v_e
        v_e::Vertex = join(e)
        push!(V´, v_e) # füge den neuen Knoten v_e zu V´ hinzu
        # füge Kanten von v_e zu den Endknoten der Kante e hinzu
        for v ∈ e # ≤ 2-mal
            push!(E´, Set(v_e,v)) # füge die Kante (v_e,v) zu E´ hinzu
        end
    end
    return Graph(V´, E´), k # gebe den neuen Graphen G´ und k zurück
end

Dieser Algorithmus hat eine Laufzeit von \(\mathcal{O}(n)\) und ist damit in \(\textbf{FP}\).

Somit gilt: \(\text{VC} ≤_m^p \text{FVS}\).

Automatentheorie

Endliche Automaten

Definition: DEA

Ein deterministischer endlicher Automat (DEA) ist ein 5-Tupel \(A = (Σ, Z, f, z_0, Z')\), bestehend aus:

  • \(Σ\): einem endlichen, nichtleeren, Eingabealphabet
  • \(Z\): einer endlichen, nichtleeren, Zustandsmenge
  • \(f: Z × Σ → Z\): einer totalen Überführungsfunktion
  • \(z_0 ∈ Z\): einem Startzustand
  • \(Z' ⊆ Z\): einer Menge von Endzuständen

Die erweiterte Übergangsfunktion \(\overline{f}: Z' × Σ^* → Z\) ist induktiv definiert durch: \[ \begin{aligned} \overline{f}(z, ε) &:= z & \forall z ∈ Z \\ \overline{f}(z, xa) &:= f(\overline{f}(z, x), a) & ∀ z ∈ Z, x ∈ Σ^*, a ∈ Σ \\ \end{aligned} \]

  1. Ein Wort \(x ∈ Σ^*\) heißt von \(A\) akzeptiert, falls \(\overline{f}(z_0, x) ∈ Z'\).
  2. \(L(A) = \{x ∈ Σ^* ∣ \overline{f}(z_0, x) ∈ Z'\}\) heißt die von \(A\) akzeptierte Wortmenge.

\[ \textbf{DEA} = \{ L(A) \mid A \text{ ist ein deterministischer endlicher Automat}\} \]

Zeigen sie, dass für \(L(A_1), L(A_2) ∈ \textbf{DEA}\) gilt \(L(A_1) ∪ L(A_2) ∈ \textbf{DEA}\).

  • Hinweis: Verwenden Sie das Kreuzprodukt \(×\).

Seien die \(A_1 = (Σ_1, Z_1, f_1, z_{0,1}, Z'_1)\) und \(A_2 = (Σ_2, Z_2, f_2, z_{0,2}, Z'_2)\) zwei DEA zu den Sprachen \(L(A_1)\) und \(L(A_2)\).

Wir konstruieren einen DEA \(A = (Σ_1 × Σ_2, Z_1 × Z_2, f, (z_{0,1}, z_{0,2}), Z'_1 × Z_2 ∪ Z_1 × Z'_2)\), wobei die Überführungsfunktion \(f\) wie folgt definiert ist: \[ f: (Z_1 × Z_2) × (Σ_1 × Σ_2) → Z_1 × Z_2 \] \[ f((z_1, z_2), (a_1, a_2)) = (f_1(z_1, a_1), f_2(z_2, a_2)) \]

Graphische Darstellung von Endlichen Automaten

  • Zustände werden als Kreise dargestellt wobei der Name des Zustands in den Kreis geschrieben wird.
  • Pfeile zeigen die Übergänge zwischen Zuständen an.
  • Der Startzustand wird doppelt umkreist.
  • Endzustände haben ein Rechteck um sich herum.

DEA und NEA können im TheoreticalComputerSience.jl Paket emuliert werden. Siehe https://github.com/daniel-schwarzenbach/TheoreticalComputerScience.jl#finite-automata-ea

Definition: NEA

Ein nichtdeterministischer endlicher Automat (NEA) ist ein 5-Tupel \(A = (Σ, Z, f, z_0, Z')\), bestehend aus:

  • \(Σ\): einem endlichen, nichtleeren, Eingabealphabet
  • \(Z\): einer endlichen, nichtleeren, Zustandsmenge
  • \(f: Z × Σ → \mathcal{P}(Z)\): einer totalen Überführungsfunktion
  • \(z_0 ∈ Z\): einem Startzustand, (manchmal auch mehrere Startzustände erlaubt)
  • \(Z' ⊆ Z\): einer Menge von Endzuständen

Die erweiterte Übergangsfunktion \(\overline{f}: Z' × Σ^* → Z\) ist induktiv definiert durch: \[ \begin{aligned} \overline{f}(z, ε) &:= \{z\} & \forall z ∈ Z \\ \overline{f}(z, xa) &:= \bigcup_{z' ∈ \overline{f}(z, x)} f(z', a) & ∀ z ∈ Z, x ∈ Σ^*, a ∈ Σ \\ \end{aligned} \]

  1. Ein Wort \(x ∈ Σ^*\) heißt von \(A\) akzeptiert, falls \(\overline{f}(z_0, x) ∩ Z' ≠ ∅\).
  2. \(L(A) = \{x ∈ Σ^* ∣ \overline{f}(z_0, x) ∩ Z' ≠ ∅\}\) heißt die von \(A\) akzeptierte Wortmenge.

\[ \textbf{NEA} = \{L(A) \mid A \text{ ist ein nichtdeterministischer endlicher Automat}\} \]

Theorem 5.2.3 \[ \textbf{DEA} = \textbf{NEA} \]

Folgender Algorithmus konvertiert jeden NEA in einen DEA, der die gleiche Sprache akzeptiert:

function construct_DEA_from_NEA(nea::NEA, Σ::Set{Char}) ::DEA
    # data
    Q = Queue{Set{State}}() # Queue of Current States
    D = Set{Set{State}}() # Set of Discovered States
    # initialize the starting DEA
    starting_states::Set{State} = nea.inital_states
    dea_z0 = join(starting_states)
    dea_Zend = nea.accepting_states
    dea_f::Dict{Tuple{State, Char}, State} = Dict()
    # check if the starting state is an accepting state
    if !isempty(starting_states ∩ nea.accepting_states)
        push!(dea_Zend, dea_z0)
    end
    # add the starting states to Q and D
    enqueue!(Q, starting_states); push!(D, starting_states)
    while !isempty(Q)
        # z_x are the current states
        z_x::Set{State} = dequeue!(Q)
        for a ∈ Σ
            # z_ys are the next states
            z_ys::Set{State} = Set()
            # accumalate all z_ys so that f(z_x, a) = z_ys
            for current_state ∈ z_x
                next_states::Set{State} = nea.f((current_state, a))
                z_ys = z_ys ∪ next_states
            end
            # add the new explicit sub function to the DEA
            dea_f[(join(z_x),a)] = join(z_ys)
            # check if z_ys is a new Set of States
            if z_ys ∉ D
                enqueue!(Q, z_ys)
                push!(D, z_ys)
                # check if any element in z_ys is in E
                if !isempty(z_ys ∩ nea.accepting_states)
                    # add z_ys to the ending States dfa.E
                    push!(dea_Zend, join(z_ys))
                end
            end
        end
    end
    return DEA(initial_state=dea_z0, accepted_states=dea_Zend, f=dea_f)
end

\[ A = (\underbrace{\{a,b\}}_{Σ}, \underbrace{\{z_0, z_1, z_2, z_3\}}_{Z}, f, z_0, \underbrace{\{z_3\}}_{Z'}) \]

wobei wir für \(z0\) einen nichtdeterministischen Übergang haben: \[ f(z_0, a) = \{z_0, z_2\} \]

Verwandeln Sie den NEA in einen equivalenten DEA.

Theorem 5.2.4

Seien \(L_1, L_2 ∈ \textbf{DEA}\), so gilt:

  1. \(L_1 ∪ L_2 ∈ \textbf{DEA}\)
  2. \(L_1 ∩ L_2 ∈ \textbf{DEA}\)
  3. \(\overline{L_1} ∈ \textbf{DEA}\)
  4. \(L_1 ⋅ L_2 ∈ \textbf{DEA}\)
  5. \(L_1^* ∈ \textbf{DEA}\)

Reguläre Mengen

Definition: Reguläre Menge

Die regulären Mengen über einem Alphabet \(Σ\) sind wie folgt induktiv definiert:

  • IA: \(\emptyset,\{a\} \; ∀ a ∈ Σ\) sind regulär
  • IS: Sind \(L_1\) und \(L_2\) regulär, so sind auch \(L_1 ∪ L_2\), \(L_1 ⋅ L_2\) und \(L_1^*\) regulär.

bzw. \(Γ(Σ, ∅)_{∪,⋅,^*}\)

Eine Wortmenge/Sprache \(L\) heißt regulär, falls \(L = Γ(Σ, ∅)_{∪,⋅,^*}\) für ein geeignetes Alphabet \(Σ\) gilt.

\[ \textbf{REG} = \{L \mid L \text{ ist eine reguläre Menge}\} \]

Gegeben sei folgender reguläre Ausdruck: \((aa)^* ∪ a(aa)^*b\)

Geben Sie einen DEA an, der diesen regulären Ausdruck akzeptiert.

A = (\(\underbrace{\{a,b\}}_{Σ}\), \(\underbrace{\{S, T, INV, END\}}_{Z}, f, \underbrace{S}_{z_0}, \underbrace{\{S,END\}}_{Z'})\)

und die Überführungsfunktion \(f\) ist wie folgt definiert:

Regex (nicht Prüfungsrelevant)

In der Informatik werden reguläre Ausdrücke (Regex) verwendet, um Muster in Zeichenketten zu beschreiben. Sie sind eng mit endlichen Automaten und regulären Mengen verbunden.

  • a|bc: Entweder ‘a’ oder ‘bc’.
  • ab: ‘a’ gefolgt von ‘b’.
  • a*: Null oder mehr aufeinanderfolgende ’a’s.
  • (ab): ‘a’ gefolgt von ‘b’ als eine feste Gruppe.

Mit den erweiterten Operatoren können wir noch einfacher Muster beschreiben:

  • a+: Ein oder mehrere aufeinanderfolgende ’a’s.
  • a?: Null oder ein ‘a’.
  • a{n}: Genau n aufeinanderfolgende ’a’s.
  • a{n,}: Mindestens n aufeinanderfolgende ’a’s.
  • a{n,m}: Mindestens n und höchstens m aufeinanderfolgende ’a’s.
  • [abc]: Ein einzelnes Zeichen, das entweder ‘a’, ‘b’ oder ‘c’ ist.
  • [a-z]: Ein einzelnes Zeichen, das ein Kleinbuchstabe von ‘a’ bis ‘z’ ist.
  • [^abc]: Ein einzelnes Zeichen, das weder ‘a’, ‘b’ noch ‘c’ ist.

⚠️ Moderne Regex-Implementierungen unterstützen auch erweiterte Funktionen die nicht mehr zu den Regulären Mengen gehören, wie

  • Lookahead/Lookbehind mit variablen Langen, z.B. (?=...) und (?<=...)
  • Referenzen, z.B. \1, \2, etc.

Lemma 5.3.2

Seien \(B, C ⊆ Σ^*\).

Falls \(ε ∉ B\), so ist \(L = B^* ⋅ C\) die einzige Lösung der Gleichung \(L = B ⋅ L ∪ C\).

Algorithmus EA → REG

Schreibe jeden \(z\) Zustand als eigene Sprachgleichung \(L_z\):

\[ \begin{gathered} (z,a) → z_a \quad ∧ \quad (z,b) → z_b \\ ⇒ \\ L_z = \{a\} ⋅ L_{z_a} ∪ \{b\} ⋅ L_{z_b} ∪ \begin{cases} \{ε\} & \text{falls } z ∈ Z' \\ \emptyset & \text{sonst} \end{cases} \end{gathered} \]

Löse dann mit dem Lemma 5.3.2 die Gleichungen, nach \(L_{z_0}\) auf. \(L_{z_0}\) ist dann die Sprache, die vom Automaten akzeptiert wird.

Finde mittels dem EA → REG Algorithmuses einen regülären Ausdruck der den folgenden Automaten beschreibt:

Um die Gleichungen übersichtlicher zu gestalten, können sie die Mengenklammern sowie \(\cdot\) weglassen.

  1. stellen sie die Gleichungen für jeden Zustand auf.

In folgenden lassen wir die Mengenklammern sowie \(\cdot\) weg, um die Gleichungen übersichtlicher zu gestalten.

Wir schreiben die Gleichungen für jeden Zustand auf: \[ \begin{aligned} &L_{z_0} = aL_{z_1} ∪ bL_{z_2} \\ &L_{z_1} = aL_{z_1} ∪ bL_{z_3} \\ &L_{z_2} = aL_{z_3} ∪ bL_{z_2} ∪ ε\\ &L_{z_3} = aL_{z_4} ∪ bL_{z_3} \\ &L_{z_4} = aL_{z_4} ∪ bL_{z_4} ∪ ε \end{aligned} \]

  1. lösen sie die Gleichungen, so dass sie einen regulären Ausdruck für die Sprache erhalten, die vom Automaten akzeptiert wird.

Wir können gleich die Gleichung für \(L_{z_4}\) lösen: \[ L_{z_4} = (a ∪ b)^* \]

Wir setzen \(L_{z_4}\) in die Gleichung für \(L_{z_3}\) ein: \[ L_{z_3} = a(a ∪ b)^* ∪ bL_{z_3} = b^*a(a ∪ b)^* \\ \]

Wir setzen \(L_{z_3}\) in die Gleichung für \(L_{z_2}\) und \(L_{z_1}\) ein: \[ \begin{aligned} &L_{z_2} = a(b^*a(a ∪ b)^*) ∪ bL_{z_2} ∪ ε = b^*a(b^*a(a ∪ b)^*) ∪ b^* \\ &L_{z_1} = aL_{z_1} ∪ b(b^*a(a ∪ b)^*) = a^*b^+a(a ∪ b)^* \\ \end{aligned} \]

Wir setzen \(L_{z_1}\) und \(L_{z_2}\) in die Gleichung für \(L_{z_0}\) ein:

\[ L_{z_0} = aa^*b^+a(a ∪ b)^* ∪ b^+a(b^*a(a ∪ b)^*) ∪ b^+ \]

Und das ist nun der reguläre Ausdruck, der die Sprache beschreibt, die vom Automaten akzeptiert wird.

Theorem 5.3.3

\[ \textbf{REG} = \textbf{DEA} = \mathcal{L}_3 \]

Pumping-Lemma für reguläre Sprachen:

Für alle \(L ∈ \mathcal{L}_3\) gilt:

\[ \exists n_0 \in \mathbb{N} : \forall x \in L, |x| ≥ n_0, \; \exists u,v,w : x = uvw \]

mit

  • \(|uv| ≤ n_0\)
  • \(|v| ≥ 1\)
  • \(uv^iw ∈ L\) für alle \(i ≥ 0\)

Nutzung des Pumping-Lemmas

Zeige, dass eine Sprache \(L\) nicht regulär oder nicht kontextfrei ist, indem du das Pumping-Lemma anwendest:

  1. Angenommen \(L ∈ \mathcal{L}_3\) oder \(L ∈ \mathcal{L}_2\) (im kontextfreien Fall). Dann müsste nach dem Pumping-Lemma gelten:
  2. Wähle ein Wort/Worfunktion \(x_f:ℕ → L\) mit \(x_f(n_0)=x ∈ L\) und \(|x| ≥ n_0, \quad ∀ n_0\).
  3. Zeige, dass für diese Wort \(x\) nicht alle Regeln des Pumping-Lemmas erfüllt werden können.

Zeigen Sie, dass die Sprache \(L = \{a^nb^n ∣ n ≥ 0\}\) nicht regulär ist.

Angenommen \(L\) ist regulär. Dann gibt es ein \(n_0 ∈ ℕ\) wie im Pumpinglemma beschrieben.

Sei \(x = a^{n_0}b^{n_0} ∈ L\).

Nach dem Pumpinglemma lässt sich \(x\) in \(uvw\) zerlegen, sodass gilt:

  • \(|uv| ≤ n_0\)
  • \(|v| ≥ 1\)
  • \(uv^iw ∈ L\) für alle \(i ≥ 0\).

Da \(|uv| ≤ n_0\) und \(x = a^{n_0}b^{n_0}\), besteht \(v\) nur aus \(a\)’s.

Betrachten wir \(uv^2w = a^{n_0+|v|}b^{n_0}\). Dies liegt nicht in \(L\), da die Anzahl der \(a\)’s und \(b\)’s nicht mehr gleich ist.

Widerspruch ↯. Daher ist \(L\) nicht regulär.

Korollar 5.4.2

\[ \textbf{DEA} ⊂ \textbf{P} \]

Pushdown-Automaten (nicht Prüfungsrelevant)

Definition: DPDA (deterministischer Pushdown-Automat)

Ein deterministischer Pushdown-Automat (DPDA) ist ein 6-Tupel \(A = (Σ, Γ, Z, f, z_0, Z')\), bestehend aus:

  • \(Σ\): einem endlichen, nichtleeren, Eingabealphabet
  • \(Γ\): einem endlichen, nichtleeren, Stackalphabet
  • \(Z\): einer endlichen, nichtleeren, Zustandsmenge
  • \(f: Z × (Σ ∪ \{ε\}) × (Γ ∪ \{ε\}) → Z × (Γ ∪ \{ε\})\): einer Übergangsfunktion
  • \(z_0 ∈ Z\): einem Startzustand
  • \(Z' ⊆ Z\): einer Menge von Endzuständen

Definition: NPDA (nichtdeterministischer Pushdown-Automat)

Ein nichtdeterministischer Pushdown-Automat (NPDA) ist ein 7-Tupel \(A = (Σ, Γ, Z, f, z_0, z_s, Z')\), bestehend aus:

  • \(Σ\): einem endlichen, nichtleeren, Eingabealphabet
  • \(Γ\): einem endlichen, nichtleeren, Stackalphabet
  • \(Z\): einer endlichen, nichtleeren, Zustandsmenge
  • \(f: Z × (Σ ∪ \{ε\}) × (Γ ∪ \{ε\}) → \mathcal{P}(Z × (Γ ∪ \{ε\}))\): einer Übergangsfunktion
  • \(z_0 ∈ Z\): einem Startzustand
  • \(z_s ∈ Z\): einem Stackstartzustand
  • \(Z' ⊆ Z\): einer Menge von Endzuständen

\[ \textbf{DPDA} ⊊ \textbf{NPDA} = \mathcal{L}_2 \]

Mann nennt die Sprachen die von einem \(\textbf{DPDA}\) entschieden werden deterministisch kontextfreie Sprachen und es gilt:

\[ \textbf{DCFL} = \mathcal{L}_{2.5} = \textbf{DPDA} \]

Theorie der formalen Sprachen

Die Chomsky-Hierarchie

Definition: Generative Gramatik

Ein Tupel \(G = (\Sigma, N, S, R)\) heißt (generative) Grammatik, falls gilt:

  • \(\Sigma\): ein endliches, nichtleeres Terminalalphabet
  • \(N\): eine endliche, nichtleere Nichtterminalalphabet
  • \(S ∈ N\): ein Startsymbol
  • \(R ⊆ (Σ ∪ N)^+ × (Σ ∪ N)^*\): eine endliche binäre Relation von Erzeugungsregeln. Diese ist nicht rechtseindeutig, d.h. es kann mehrere Regeln mit demselben linken Teil geben, dh. ist keine Funktion!

Sei G = \((\Sigma, N, S, R)\) eine Grammatik.

  1. Ein Wort \(w ∈ (Σ ∪ N)^*\) heißt von \(G\) aus \(v ∈ (Σ ∪ N)^+\) in einem Schritt erzeugt, falls \((v, w) ∈ R\). Wir schreiben das auch als:
  • \(v ⇒_G w\)
  1. Ein Wort \(w ∈ (Σ ∪ N)^*\) heißt von \(G\) aus \(v ∈ (Σ ∪ N)^+\) in \(t\) Schritten erzeut falls es Wörter \(v_0, v_1, ..., v_t\) gibt mit \(v_0 = v\), \(v_t = w\) und \(v_{i-1} ⇒_G v_i\) für alle \(i ∈ \{1, ..., t\}\). Wir schreiben das auch als:
  • \(v ⇒^t_G w\)
  1. Ein Wort \(w ∈ (Σ ∪ N)^*\) heißt von \(G\) aus \(v ∈ (Σ ∪ N)^+\) erzeugt, falls es ein \(t ∈ ℕ\) gibt, sodass \(v ⇒^t_G w\). Wir schreiben das auch als:
  • \(v ⇒^*_G w\)
  1. \(L(G) = \{w ∈ Σ^* ∣ S ⇒^*_G w\}\) heißt die von \(G\) erzeugte Sprache.

Definition: Grammatikstypen

Sei \(G = (Σ, N, S, R)\) eine Grammatik.

Im folgenden seien \(w ∈ (Σ ∪ N)^+, \; u, u_1, u_2 ∈ (Σ ∪ N)^*, \; a ∈ Σ\) und \(A,B ∈ N\) beliebig.

  1. \(G\) ist vom Typ 0 oder eine unbeschränkte Grammatik, falls keine Einschränkung an die Regeln in \(R\) gestellt wird.

  2. \(G\) ist vom Typ 1 oder eine kontextsensitive Grammatik, falls für alle \((v, w) ∈ R\) gilt: \(|v| ≤ |w|\). Daher sind nur Regeln folgender Form erlaubt:

    • \(u_1Au_2 → u_1wu_2\)
    • \(S → ε\)
  3. \(G\) ist vom Typ 2 oder eine kontextfreie Grammatik, falls für alle \((v, w) ∈ R\) gilt: \(v ∈ N\). Daher sind nur Regeln folgender Form erlaubt:

    • \(A → u\)
  4. \(G\) ist vom Typ 3 oder eine (strenge) rechtslineare Grammatik, falls für alle \((v, w) ∈ R\) gilt: \(v ∈ N\) und \(w ∈ ΣN ∪ \{ε\}\). Daher sind nur Regeln folgender Form erlaubt:

    • \(A → aB\)
    • \(A → ε\)

\(G\) ist eine (allgemeine) rechtslineare Grammatik, falls Regeln folgender Form erlaubt sind:

  • \(A → wB\)
  • \(A → w\)

mit \(w ∈ Σ^*\) und \(A,B ∈ N\).

Jede allgemeine rechtslineare Grammatik kann in eine (strenge) rechtslineare Grammatik umgewandelt werden, die dieselbe Sprache erzeugt.

Definition: Sprachklassen

\[ \mathcal{L}_i = \{L(G) ∣ G \text{ ist eine Grammatik vom Typ } i\} \]

unterander gilt:

\[ \mathcal{L}_3 ⊂ \mathcal{L}_2 ⊂ \mathcal{L}_1 ⊂ \mathcal{L}_0 \]

  • \(\mathcal{L}_3 =\textbf{EA} = \textbf{NEA}\)
  • \(\mathcal{L}_2 =\textbf{NPDA}\)
  • \(\mathcal{L}_1 =\textbf{NLINSPACE} \subseteq \textbf{E}\)
  • \(\mathcal{L}_0 =\textbf{RE}\)

Gegeben sei folgende Grammatik: \[ G = (\underbrace{\{0,1\}}_{Σ}, \underbrace{\{S, A, B, C\}}_{N}, S, R) \] wobei die Regeln \(R\) wie folgt definiert sind: \[ \begin{aligned} S &→ 0S \; | \; 1A \\ A &→ 0B \; | \; 1A \\ B &→ 0S \; | \; 1C \\ C &→ 0C \; | \; 1C \; | \; ε \end{aligned} \]

  1. Zu welcher Sprachklasse gehört die Sprache \(L(G)\)?

Alle Regeln folgen der Form:

  • \(A → aB\)
  • \(A → ε\)

mit \(A,B ∈ N\) und \(a ∈ Σ\). Daher ist die Grammatik rechtslinear und die Sprache \(L(G)\) gehört zur Sprachklasse \(\mathcal{L}_3\).

  1. Geben sie einen Automaten/Maschine an, der die Sprache \(L(G)\) akzeptiert.

Wir konstruieren nun von den Gramatikregeln einen DEA:

Gegeben sei folgende Grammatik: \[ G = (\underbrace{\{a,b\}}_{Σ}, \underbrace{\{S, A, T\}}_{N}, S, R) \] wobei die Regeln \(R\) wie folgt definiert sind: \[ \begin{aligned} S &→ AT \; | \; ε \\ T &→ AS \; | \; b \\ A &→ a \end{aligned} \]

  1. Zu welcher Sprachklasse gehört die Sprache \(L(G)\)?

Da \(A → a\) die einzige Regel für \(A\) ist, können wir \(A\) durch \(a\) ersetzen. Zudem können wir für \(b\) ein neues Nichtterminal \(\text{END}\) einführen. Dann können wir \(R\) neu schreiben als eine rechtslineare Grammatik: \[ \begin{aligned} S &→ aT \; | \; ε \\ T &→ aS \; | \; b\text{END} \\ \text{END} &→ ε \\ \end{aligned} \]

Nun sind alle Regeln folgen der Form:

  • \(A → aB\)
  • \(A → ε\)

mit \(A,B ∈ N\) und \(a ∈ Σ\). Daher ist die Grammatik regulär und die Sprache \(L(G)\) gehört zur Sprachklasse \(\mathcal{L}_3\).

  1. Geben sie einen Automaten/Maschine an, der die Sprache \(L(G)\) akzeptiert.

Wir konstruieren nun von den Gramatikregeln einen DEA:

Kontextfreie Sprachen

Pumping-Lemma für kontextfreie Sprachen:

Für alle \(L ∈ \mathcal{L}_2\) gilt:

\[ \exists n_0 \in \mathbb{N} : \forall z \in L, |z| ≥ n_0, \; ∃u,v,w,x,y: z = uvwxy \]

mit

  • \(|vwx| ≤ n_0\)
  • \(|vx| ≥ 1\)
  • \(uv^iwx^iy ∈ L\) für alle \(i ≥ 0\)

Chomsky-Normalform

Definition: Chomsky-Normalform

Sei \(G = (Σ, N, S, R)\) eine Gramatik. Falls \(G\) nur Regeln der Form:

\[ \begin{aligned} S &→ ε \\ A &→ BC\\ A &→ a \end{aligned} \]

wobei \(A,B,C ∈ N\) und \(a ∈ Σ\) beliebig sind, so ist \(G\) in Chomsky-Normalform.

Theorem 6.3.2

Jede kontextfreie Sprache kann durch eine Grammatik in Chomsky-Normalform erzeugt werden.

CNF-Umwandlungsalgorithmus

  1. Eliminierung von Terminalen in langen Regeln \(A → abBc\):

Führe für jede Regel mit mehreren Terminalen, ersetze diese terminale durch neue Nichtterminale \(T_a → a, T_b → b, T_c → c\) und ersetze so die Regel \(A → abBc\) durch \(A → T_aT_bBT_c\).

  1. Eliminierung von Kettenregeln \(A_1 → A_2 → B_1B_2\):

Führe für jede Regel \(A_1 → A_2, ..., A_{k-1} → A_k\) und

  • \(A_k → B_1...B_m\) mit \(m ≥ 2\) die Regeln \(A_l → B_1...B_m\) mit \(l < k\) zu \(R\) hinzu
  • \(A_k → a\) die Regeln \(A_l → a\) mit \(l < k\) zu \(R\) hinzu

und entferne die Regeln \(A_1 → A_2, ..., A_{k-1} → A_k\), bis keine Kettenregeln mehr vorhanden sind.

  1. Binarisierung von langen Regeln \(A → B_1B_2...B_k\) mit \(k ≥ 3\):

Führe für jede Regel \(A → B_1B_2...B_k\) mit \(k ≥ 3\) neue Nichtterminale \(A_1, ..., A_{k-2}\) ein und ersetze die Regel durch die Regeln: \[ \begin{aligned} A &→ B_1A_1 \\ A_1 &→ B_2A_2 \\ A_2 &→ B_3A_3 \\ &... \\ A_{k-2} &→ B_{k-1}B_k \end{aligned} \]

Gegeben sei die Grammatik \(G = (\{a,b\}, \{S,A,B\}, S, R)\) mit: \[ \begin{aligned} S &→ aAb \\ A &→ aA \mid b \mid B \\ B &→ bB \mid b \end{aligned} \]

Schritt 1 — Terminale in langen Regeln ersetzen:

Neue Nichtterminale: \(T_a → a\), \(T_b → b\).

  • \(S → aAb\) wird zu \(S → T_aAT_b\) (da \(k=3\) und \(a,b ∈ Σ\))
  • \(A → aA\) wird zu \(A → T_aA\) (da \(k=2\) und \(a ∈ Σ\))
  • \(B → bB\) wird zu \(B → T_bB\) (da \(k=2\) und \(b ∈ Σ\))
  • \(A → b\) und \(B → b\) bleiben (Einzelterminale, \(k=1\))

\[ \begin{aligned} S &→ T_aAT_b \\ A &→ T_aA \mid b \mid B \\ B &→ T_bB \mid b \\ T_a &→ a, \quad T_b → b \end{aligned} \]

Schritt 2 — Kettenregeln eliminieren:

Die einzige Kettenregel ist \(A → B\). \(B\) hat die Regeln \(B → T_bB\) und \(B → b\).

  • Füge \(A → T_bB\) hinzu (von \(B → T_bB\), da \(m=2\))
  • Füge \(A → b\) hinzu (von \(B → b\), bereits vorhanden)
  • Entferne \(A → B\)

\[ \begin{aligned} S &→ T_aAT_b \\ A &→ T_aA \mid b \mid T_bB \\ B &→ T_bB \mid b \\ T_a &→ a, \quad T_b → b \end{aligned} \]

Schritt 3 — Regeln mit \(k ≥ 3\) binarisieren:

Die einzige Regel mit \(k=3\) ist \(S → T_aAT_b\). Neues Nichtterminal \(S_1\):

  • \(S → T_aAT_b\) wird zu \(S → T_aS_1\) und \(S_1 → AT_b\)

Ergebnis in Chomsky-Normalform: \[ \begin{aligned} S &→ T_aS_1 \\ S_1 &→ AT_b \\ A &→ T_aA \mid T_bB \mid b \\ B &→ T_bB \mid b \\ T_a &→ a \\ T_b &→ b \end{aligned} \]

Alle Regeln haben die Form \(A → BC\) oder \(A → a\) — die Grammatik ist in CNF. ✓

Gegeben sei die Sprache \(L = \{a^nb^n \}\).

  1. Geben Sie eine Grammatik an, die diese Sprache erzeugt.

Wir können die Grammatik wie folgt definieren:

\(G = (\underbrace{\{a,b\}}_{Σ}, \underbrace{\{S\}}_{N}, S, R)\) mit den Regeln \(R\):

\[ S → aSb \; | \; ε \]

  1. Gibt es auch eine rechtslineare Grammatik?
  • Falls ja, geben Sie diese an.
  • Falls nein, beweisen Sie, dass keine existiert.

Angenommen, es existiert eine rechtslineare Grammatik \(G' = (Σ', N', S', R')\) für die Sprache \(L\). Dann wäre \(L\) regulär. Dann gibt es ein \(n_0 ∈ ℕ\) wie im Pumpinglemma beschrieben.

Sei \(x = a^{n_0}b^{n_0} ∈ L\).

Nach dem Pumpinglemma lässt sich \(x\) in \(uvw\) zerlegen, sodass gilt:

  • \(|uv| ≤ n_0\)
  • \(|v| ≥ 1\)

somit muss \(v\) nur aus \(a\)’s bestehen. Betrachten wir \(uv^2w = a^{n_0+|v|}b^{n_0}\). Dies liegt nicht in \(L\), da die Anzahl der \(a\)’s und \(b\)’s nicht mehr gleich ist.

Sommit kann die Bedingung \(uv^iw ∈ L\) für alle \(i ≥ 0\) nicht erfüllt werden. Widerspruch ↯. Daher existiert auch keine rechtslineare Grammatik für die Sprache \(L\).

  1. Gibt es auch eine Grammatik in Chomsky-Normalform?
  • Falls ja, geben Sie diese an.
  • Falls nein, beweisen Sie, dass keine existiert.

Die Spache \(L\) ist offensichtlich kontextfrei, daher können wir sie auch in Chomsky-Normalform darstellen:

  1. Terminale in langen Regeln ersetzen:

\[ \begin{aligned} S &→ T_aST_b \; | \; ε \\ T_a &→ a, \quad T_b → b \end{aligned} \]

  1. Kettenregeln eliminieren: wir haben keine Kettenregeln.

  2. Regeln mit mehr als zwei Symbolen binarisieren:

\[ \begin{aligned} S &→ T_aS_1 \; | \; ε \\ S_1 &→ ST_b \\ T_a &→ a, \quad T_b → b \end{aligned} \]

somit haben wir eine Grammatik in Chomsky-Normalform gefunden.

Gegeben sei \(G = (\{a,b\},\{S\}, S, R)\) mit \(R\) bestehend aus den Regeln:

\[ \begin{aligned} S &→ aSBa \\ aB &→ Ba \\ bB &→ bb \\ SB &→ b \\ \end{aligned} \]

  1. Schreiben Sie die Grammatik als eine Sprache/Wortmenge auf, ohne innerhalb der definition, die Gramatikregeln zu verwenden.

Die Grammatik erzeugt die Sprache: \[ L(G) = \{a^n b^n a^n ∣ n ≥ 0\} \]

  1. Können Sie eine Grammatik finden in die Chomsky-Normalform die dieselbe Sprache erzeugt?
  • Falls ja, geben Sie diese an.
  • Falls nein, beweisen Sie, wieso nicht.

Angenommen, es existiert eine Grammatik \(G' = (Σ', N', S', R')\) in Chomsky-Normalform für die Sprache \(L(G)\). Dann wäre \(L(G)\) kontextfrei. Dann gibt es ein \(n_0 ∈ ℕ\) wie im Pumping-Lemma beschrieben.

Sei \(x = a^{n_0}b^{n_0}a^{n_0} ∈ L(G)\).

Dann müsste sich \(x\) in \(uvwxy\) zerlegen, sodass gilt:

  • \(|vwx| ≤ n_0\)
  • \(|vx| ≥ 1\)
  • \(uv^iwx^iy ∈ L(G)\) für alle \(i ≥ 0\).

Da \(|vwx| \leq n_0\) gilt, kann \(vwx\) nicht gleichzeitig Symbole aus dem ersten \(a^{n_0}\)-Block und dem letzten \(a^{n_0}\)-Block enthalten: die beiden \(a\)-Blöcke sind durch \(b^{n_0}\) getrennt, sodass ein Teilwort der Länge \(\leq n_0\) höchstens einen der beiden \(a\)-Blöcke berühren kann. Da \(|vx| \geq 1\) gilt, muss mindestens einer der Blöcke \(a^{n_0}\), \(b^{n_0}\), \(a^{n_0}\) beim Pumpen verändern. Das bedeutet, dass die drei Blöcke nicht mehr gleich lang sein werden, wenn wir \(v\) und \(x\) pumpen. Daher wird das Wort \(uv^iwx^iy\) für \(i \neq 1\) nicht mehr in der Sprache \(L(G)\) liegen.

Somit haben wir einen Widerspruch gefunden. Daher existiert keine Grammatik in Chomsky-Normalform, die dieselbe Sprache erzeugt.