def add(n, m) :
if m == 0 : return n
return 1 + add(n, m - 1)
def mult(n, m) :
if m == 0 : return 0
return add(n, mult(n, m - 1))
def exp(n, m) :
if m == 0 : return 1
return mult(n, exp(n, m - 1))
def f(n) : return exp(n, n)
1 Blatt 1
Aufgabe 2
\[\begin{align*} \log(n!) &= \log(\prod_{i=1}^{n}i) \tag{Def $n!$} \\ &= \sum_{i=1}^{n}\log(i) \tag{Eig $\log(\bullet)$} \\ &\leq \sum_{i=1}^{n}\log(n) \tag{Eig $\log(\bullet)$} \\ &= n\log(n) \end{align*}\]
Waehle nun \(c_0 := 1\) und \(n_0 := 1\). Es folgt somit:
\[\begin{align*} &\log(n!) \leq 1\cdot n\log(n), \quad \forall n \geq 1 \\ \iff &\log(n!) \in \mathcal{O}(n\log(n)) \tag{Def $\mathcal{O}$} \end{align*}\]
Zuerst bemerken wir die folgende Eigenschaft
\[\begin{align*} n\log(n) &\leq c\log(n!) \\ \iff c &\geq \frac{n\log(n)}{\log(n!)} \\ &= \frac{n\log(n)}{\sum_{i=1}^{n}\log(i)} \end{align*}\]
Wir definieren die Folge:
\[\begin{align*} c(n) &:= \frac{n\log(n)}{\sum_{i=1}^{n}\log(i)} \\ &= \frac{\overbrace{\log(n) + \dots + \log(n)}^\text{n-mal}}{\log(1) + \dots + \log(n)} \end{align*}\]
Wir behaupten ohne Beweis, dass \(c(n)\) eine monoton fallende Folge ist. D.h. es gilt:
\[ c(n) \leq c(m), \quad \forall n \geq m \]
Setze nun \(n_0 := 10, c_0 := c(10) = \frac{10\log(10)}{\sum_{i = 1}^{10}\log(i)}\). Somit folgt:
\[\begin{align*} n\log(n) &\leq \left(\frac{n\log(n)}{\sum_{i=1}^{n}\log(i)}\right)\log(n!) \\ &= c(n)\log(n!) \\ &\leq c_0\log(n!), \quad \forall n\geq n_0 = 10 \tag{$c(n)$ monoton fallend} \end{align*}\]
Aufgabe 3
a)
Da \(f_1 \in \mathcal{O}(g_1)\) und \(f_2 \in \mathcal{O}(g_2)\) existieren \(n_1, n_2, c_1, c_2\) s.d:
\[\begin{align*} f_1(n) \leq c_1 g_1(n), \quad \forall n\geq n_1 \\ f_2(n) \leq c_2 g_2(n), \quad \forall n\geq n_2 \end{align*}\]
Setze \(c_0 := max\{c_1, c_2\}, n_0 := max\{n_1, n_2\}\). Dann gilt
\[\begin{align*} (f_1 + f_2)(n) &= f_1(n) + f_2(n) \\ &\leq c_1g_1(n) + c_2g_2(n), \quad \forall n \geq n_0 \tag{$n \geq n_0 \Rightarrow n \geq n_1 \wedge n \geq n_2$} \\ &\leq c_0g_1(n) + c_0g_2(n), \quad \forall n \geq n_0 \\ &= c_0(g_1 + g_2)(n) \quad \forall n \geq n_0 \\ \iff f_1 + f_2 &\in \mathcal{O}(g_1 + g_2) \tag{Def $\mathcal{O}$} \end{align*}\]
b)
mit \(f_1 \in \Theta(g_1), f_2 \in \Theta(g_2)\) existieren \(a_1, a_2, b_1, b_2, n_1, n_2\), s.d.:
\[\begin{align*} a_1f_1(n) \leq g_1(n) \leq a_2f_1(n), \forall n \geq n_1 \\ b_1f_2(n) \leq g_2(n) \leq b_2f_2(n), \forall n \geq n_2 \\ \end{align*}\]
Setze \(n_0 := max\{n_1, n_2\}, \, c_1 := a_1b_1, \, c_2 := a_2b_2\). Dann gilt:
\[ c_1(f_1f_2)(n) = a_1f_1(n)b_1f_2(n) \leq (g_1g_2)(n) \leq a_2f_1(n)b_2f_2(n) = c_2(f_1f_2)(n), \quad \forall n \geq n_0 \]
Somit \(f_1f_2 \in \Theta(g_1g_2)\).
c) Falsch:
Betrachte \(f(n) := n\) und \(g(n) := 10n\). Offensichtlicht gilt \(f \in \Omega(g)\) mit \(c_0 := 1/10, n_0 := 1\). Aber \(2^{n} \notin \Omega(2^{10n})\), da \(2^n\) langsamer als \(2^{10n}\) waechst. (Setze z.B. \(2^n := x\). Dann \(2^{10n} = (2^{n})^{10} = x^{10}\), und \(x^{10}\) ist offensichtlich schneller als \(x\))
d) Falsch:
Sei \(g(n) := 2^n\). Dann \(f(n) = g(2n) = 2^{2n} = (2^n)^2\). \((2^n)^2\) ist offensichtlich schneller als \(2^n\)
e) Falsch:
Seien \(f(n) := n^2, f_1(n) := n^3, f_2(n) := n\). Es gilt:
\[\begin{align*} f\in \mathcal{O}(f_1) \tag{$n^2 \in \mathcal{O}(n^3)$} \\ f_1\in \Omega(f_2) \tag{$n^3 \in \Omega(n)$} \end{align*}\]
aber
\[ f \notin \mathcal{O}(f_2) \tag{$n^2 \notin \mathcal{O}(n)$} \]
f)
Es gilt:
\[\begin{align*} \lim_{n\to\infty}\frac{f(n)}{f_2(n)} &= \lim_{n\to\infty}\left(\frac{f(n)}{f_1(n)}\cdot\frac{f_1(n)}{f_2(n)}\right) \\ &= \lim_{n\to\infty}\left(\frac{f(n)}{f_1(n)}\right) \cdot \lim_{n\to\infty}\left(\frac{f_1(n)}{f_2(n)}\right) \\ &= 0\cdot c, \text{fuer ein} c \tag{$f \in \mathcal{o}(f_1), f_1 \in\mathcal{O}(f_2)$} \\ &= 0 \\ \iff f \in \mathcal{o}(f_2) \tag{Def $\mathcal{o}$} \end{align*}\]
Wobei wir die alternativen Definitionen von \(\mathcal{o}(\bullet)\) und \(\mathcal{O}(\bullet)\) benutzt haben.
Auafgabe 4
a)
\(\mathcal{O}(n^2\log(n))\):
read(n) //input
for i := 1 to n :
for j := 1 to n:
k := 1
// O(log(n))
while (k < n) : k := 2 * k
b)
\(\mathcal{O}((log(n))^2)\):
read(n) //input
i := 1
while (i < n) :
j := 1
while (j < n) :
j := 2 * j i := 2 * i
c)
Wir ‘simulieren’ Exponentiation durch einzelne Additionsoperationen. Somit ist \(n^n\) in \(n^n\) Additionen berechnet - Python Implementierung:
Wir testen diese Funktion fuer einige Werte:
for i in range(5) :
print(f(i))
1
1
4
27
256
Alternativ betrachte folgende rekursive Funktionsdefinition:
function recursiveLoops(n : Nat, m : Nat) :
if m > 0 then :
for i = 1 ... n do : recursiveLoops(n, m - 1)
Dann erzeugt der Aufruf recursiveLoops(n, n)
eine Anzahl von \(\mathcal{O}(n^n)\) rekursiven Aufrufe.
d)
\(\Theta{2^n}\) - Wir ‘simulieren’ binaeres Zaehlen:
read(n)
base := 0
count := 0
k := 1
// invariant: k == 2^b, count < k
while (base < n) :
k := 2 * k
base := base + 1
while (count < k) :
count := count + 1
// post-condition: count == k //post-condition b == n => count == 2^n
Python Implementierung:
def binary_count(n) :
= count = 0
base = 1
k while (base < n) :
= 2 * k
k = base + 1
base while (count < k) :
= count + 1
count return count
Wir testen diese Funktion fuer einige Werte. Das Ergebniss ist die Anzahl der Schritte fuer die jeweilige Eingabe:
for i in range(11) :
print(binary_count(i))
0
2
4
8
16
32
64
128
256
512
1024
Alternativ:
function f(n) :
if n == 0 : return 1 return f(n - 1) + f(n - 1)
Diese rekursive Funktion ruft sich selbst zweimal fuer jeden Wert von \(n\) auf, was zu einer Laufzeit von \(2^n\) fuehrt.