1. ¨Ubungsblatt zu Algorithmen I im SS 2010

Werbung
Karlsruher Institut für Technologie
Institut für Theoretische Informatik
Prof. Dr. Peter Sanders
G.V. Batz, C. Schulz, J. Speck
1. Übungsblatt zu Algorithmen I im SS 2010
http://algo2.iti.kit.edu/AlgorithmenI.php
{sanders,batz,christian.schulz,speck}@kit.edu
Musterlösungen
Aufgabe 1
(8 Punkte)
Beweisen oder widerlegen Sie:
√
a) n = O( n), n2 = O n3 ,
b) 2n = O(3n ),
2n+1 = O(2n ),
c) log2 n = Θ(log10 n),
2loga (n)
n3 = O n2 .
(n + 1)! = O(n!).
= Θ 2logb (n) für a 6= b
d) f (n)+g(n) = O(min(f (n), g(n))), f (n)+g(n) = O(max(f (n), g(n))), f (n)g(n) = O(f (n)g(n))
e) f (n) = O(n) ⇒ 2f (n) = O(2n ), f (n) = O(n) ⇒ f (n)2 = O n2
f) Auf der Menge der asymptotisch positiven Funktionen ist
f ∼Θ g gdw. f ∈ Θ(g)
eine Äquivalenzrelation.
Musterlösung:
a)
b)
√
• Die Behauptuung n = O( n) gilt nicht. Beweis durch Widerspruch.
√
√
Angenommen, es gilt n = O( n). Dann ∃c > 0 : ∃n0 ∈ N : ∀n ≥ n0 : n ≤ c · n. Also gilt
√ √
nach teilen durch n: n ≤ c ∀n ≥ n0
• Die Behauptung n2 = O n3 gilt: wähle c = 1, n0 = 1
3 = O n2 gilt nicht. Beweis durch Widerspruch. Angenommen, es gilt
• Die Behauptuung
n
n3 = O n2 . Dann ∃c > 0 : ∃n0 ∈ N : ∀n ≥ n0 : n3 ≤ c · n2 . Also gilt nach teilen durch n2 :
n ≤ c ∀n ≥ n0
• Die Behauptung 2n = O(3n ) gilt: 2 ≤ 3 ⇒ 2n ≤ 3n ∀n ∈ N. Wähle also c = 1, n0 = 1.
• Die Behauptung 2n+1 = O(2n ) gilt: 2n+1 = 2 · 2n , wähle also n0 = 1, c = 2
• Die Behauptung (n + 1)! = O(n!) gilt nicht. Beweis durch Widerspruch. Angenommen, es
gilt (n + 1)! = O(n!). Dann ∃c > 0 : ∃n0 ∈ N : ∀n ≥ n0 : (n + 1)! ≤ c · n!. Also gilt nach
teilen durch n!: n + 1 ≤ c ∀n ≥ n0
c)
d)
10 n
• Die Behauptung log2 n = Θ(log10 n) gilt, da log2 n = log
log10 2
• Die Behauptung 2loga n = Θ 2logb n für a 6= b gilt nicht: wähle a = 2, b = 10. Dann ist
2loga n = n und
2logb n = 2log10 n = 2log2 n∗log10 2 = n0.30... . Analog zu a) oder b) zeigt man
n 6∈ Θ nlog10 2
• Die Behauptung f (n) + g(n) = O(min(f (n), g(n))) gilt nicht. Gegenbeispiel: f (n) := n,
√
g(n) := n.
• Die Behauptung f (n)+g(n) = O(max(f (n), g(n))) gilt, da f (n)+g(n) ≤ 2 max(f (n), g(n)).
1
• Die Behauptung f (n) · g(n) = O(f (n) · g(n)) gilt: wähle c = 1, n0 = 1 .
e)
• Die Behauptung f (n) = O(n) ⇒ 2f (n) = O(2n ) gilt nicht. Gegenbeispiel: f (n) = 2n. Dann
f (n) ∈ O(n), aber 4n 6∈ O(2n )
• Die Behauptung f (n) = O(n) ⇒ f (n)2 ∈ O n2 gilt. Beweis: f (n) = O(n) ⇒ ∃c > 0∃n0 >
0 : f (n) ≤ cn ∀n ≥ n0 . Quadrieren liefert: ∃c > 0∃n0 > 0 : f (n)2 ≤ c2 n2 ∀n ≥ n0 .
Wähle also n˜0 = n0 und c̃ = c2 und erhalte mit diesen Konstanten die Behauptung.
f) Behauptung gilt. Beweis:
• Reflexivität: zu zeigen f ∼Θ f .
Beweis: c1 f (n) ≤ f (n) ≤ c2 f (n) ist trivialerweise erfüllt für c1 = c2 = 1, und n0 beliebig.
• Symmetrie: zu zeigen f ∼Θ g gdw. g ∼Θ f .
Beweis: Es sei f ∼Θ g mit den Konstanten c1 , c2 , n0 . D.h. es gilt c1 g(n) ≤ f (n) ≤ c2 g(n).
Umstellen liefert uns: c12 f (n) ≤ g(n) ≤ c11 f (n). Also ist g(n) = Θ(f (n)) mit den Konstanten
1 1
c2 , c1 , n0 . Also g ∼Θ f
• Transitivität: zu zeigen f ∼Θ g und g ∼Θ h impliziert f ∼Θ h
Beweis: Es sei f ∼Θ g und g ∼Θ h.
Also gilt c1 g(n) ≤ f (n) ≤ c2 g(n) und d1 h(n) ≤ g(n) ≤ d2 h(n) für entsprechende Konstanten und n0 . Daraus folgt: c1 d1 h(n) ≤ f (n) ≤ c2 d2 h(n) ∀n ≥ n0 , was zu zeigen war.
Aufgabe 2
(Zeitaufwands von Algorithmen, 2 + 1 + 1 + 2 Punkte)
Gehen Sie davon aus, dass Sie mit einem Rechner arbeiten, der nur die Rechenoperationen + und
−, sowie die Vergleiche <, ≤, =, >, ≥ in Zeit Θ(1) ausführen kann. Zuweisungen an Variablen oder
an Einträge von Arrays sind ebenfalls möglich und benötigen ebenfalls Θ(1) Zeit; ebenso auch das
Auslesen von Variablen und Array-Einträgen. Ansonsten unterstützt der Rechner nur die übliche Ablaufsteuerung (Schleifen, Fallunterscheidung, Unterprogrammaufruf) und keine weiteren Operationen.
Der einzige verfügbare Basisdatentyp ist zudem die Menge N≥0 . Im folgenden müssen Sie nun einige
Algorithmen für diesen Rechner angeben. Dabei spielt nur die Laufzeit eine Rolle. Ihr Algorithmus
könnte z.B. immer den gleichen Wert als Ergebnis zurückgeben. Wichtig ist nur, dass er dabei das
gewünschte Laufzeitverhalten aufweist.
a) Geben Sie einen Algorithmus an, der als Eingabe eine Zahl n ∈ N≥0 hat und Θ(log n) Zeit
benötigt.
b) Geben Sie einen Algorithmus an, der als Eingabe eine Zahl n ∈ N≥0 hat und Θ(n4 log n) Zeit
benötigt.
c) Geben Sie einen Algorithmus an, der als Eingabe eine Zahl n ∈ N≥0 hat und Θ(2n ) Zeit benötigt.
d) Geben Sie einen Algorithmus an, der als Eingabe eine Zahl n ∈ N≥0 hat und Θ(nn ) Zeit benötigt.
Zeigen Sie jeweils, dass Ihr Algorithmus tatsächlich die geforderte Laufzeit hat.
Hinweis: Soweit sinnvoll können Sie dabei die Erkenntnisse aus Aufgabe 1 verwenden.
Musterlösung:
a) Ein möglicher Algorithmus:
1: procedure A(n: N≥0 )
2: a := 1 : N≥0
3: while a < n do a := a + a
4: return
2
In der While-Schleife hat a nach i Schritten den Wert 2i . Also gilt nach i ≥ log n Schritten
a = 2i ≥ 2log n = n, was zum Abbruch der While-Schleife führt. Insgesamt werden also Θ(logn)
Additionen, Zuweisungen und Vergleiche ausgeführt. Die Gesamtlaufzeit ist also in Θ(log n)
b) Ein möglicher Algorithmus:
1: procedure B (n: N≥0 )
2: for i1 := 1, . . . , n do
3:
for i2 := 1, . . . , n do
4:
for i3 := 1, . . . , n do
5:
for i4 := 1, . . . , n do
6:
a := 1 : N≥0
7:
while a < n do a := a + a
8:
end for
9:
end for
10:
end for
11: end for
12: return
Die Schachtelung von vier For-Schleife bewirkt, dass die innere While-Schleife n4 -mal ausgeführt
wird. Die For-Schleifen selbst verursachen dabei einen Zeitaufwand von Θ(n4 ) Die Zeitkomplexität der While-Schleife liegt, wie bereits in a) gezeigt, in Θ(log n). Insgesamt ergibt sich die
gewünschte Laufzeit von Θ(n4 log n).
c) Ein möglicher Algorithmus:
1: procedure C (n: N≥0 )
2: if n = 0 then return
3: C(n − 1)
4: C(n − 1)
5: return
Ein Aufruf von C verursacht einen gewissen eigenen Aufwand zuzuzüglich dem Aufwand der
Kindaufrufe, also insgesamt den Zeitaufwand
Tgesamt (0) = Teigen ,
Tgesamt (n) = Teigen + 2Tgesamt (n − 1)
Der Eigenaufwand ist aber offenbar in Θ(1), da er nicht von n abhängt. D.h. es gibt eine Konstante c > 0 mit Teigen ≤ c. Dann gilt
Tgesamt (n) ≤ c + 2Tgesamt (n − 1)
≤ c + 2(c + 2Tgesamt (n − 2))
= c + 2c + 4Tgesamt (n − 2))
≤ · · · ≤ c + 2c + . . . 2n c
n
X
=c
2i = c2n+1 − c
i=0
Also gilt Tgesamt (n) = O 2n+1 = O(2n ). Analog zeigt man Tgesamt (n) = Ω(2n ), in diesem Fall
mit einer Abschätzung nach unten. Allerdings bekommt man dabei
0
Tgesamt (n) ≥ c
n
X
2i = c0 2n+1 − c0
i=0
für ein geeignetes c0 > 0, was aber nicht ganz zur definition des Ω-Symbols passt. Allerdings gibt
es eine Konstante c00 > 0 mit c0 2n+1 − c0 ≥ c00 2n+1 , was dieses Problem löst. Ein Aufruf von C(n)
hat also einen Zeitaufwand von Θ(2n ).
3
d) Ein möglicher Algorithmus (rekursiver Algorithmus mit Wrapper“-Prozedur, wird benutzt weil
”
die Rekursion eine hässliche“ Parameterliste erfordert):
”
1: procedure D(n: N≥0 )
2: D rec(n, n)
3: return
Hilfsprozedur, in der die Rekursion stattfindet:
1: procedure D rec(n, k: N≥0 )
2: if n ≤ 1 then return
3: if k = 0 then return
4: for i := 1, . . . , n do D rec(n, k − 1)
5: return
Abgesehen von dem Aufwand, der durch rekursive Aufrufe verursacht wird, entsteht in D rec
höchstens konstanter Zeitaufwand. Sei n > 1, k > 0. Dann gilt für den Zeitaufwand T (n, k) eines
Aufrufes D rec(n, k) also T (n, k) ≤ c + nT (n, k − 1) und T (n, 0) ≤ c für ein geeignetes c > 0.
Damit gilt
T (n, n) ≤ c + nT (n, n − 1)
≤ c + n c + nT (n, n − 2)
= c + nc + n2 T (n, n − 2)
≤ · · · ≤ c + nc + n2 c + · · · + nn−1 c + nn c
n
X
nn+1 − 1
nn+1 − 1
≤ 2c
≤ 2cnn
=c
ni = c
n−1
n
i=0
für eine Konstante c > 0 und ausreichend große n. Analog zeigt man
T (n, n) ≥ c0
nn+1 − 1
n−1
für eine Konstante c0 > 0 und schätzt ab
T (n, n) ≥ c0
nn+1 − 1
nn+1 − 1
1
≥ c0
= c0 nn − c0 ≥ c0 nn − c0 .
n−1
n
n
Es gibt aber eine weitere Konstante c00 > 0 mit c0 nn − c0 > c00 n. Also ist insgesamt T (n, n) =
Θ(nn ), was der Zeitaufwand ist, den ein Aufruf von D(n) verursacht.
Aufgabe 3
(Finden einer fehlenden ganzen Zahl, 4 + 1 + 1 Punkte)
Ein Feld A[0..n − 2] enthält alle ganzen Zahlen von 0 bis n − 1 außer einer. In der vorliegenden Problemstellung können wir auf die vollständige ganze Zahl nicht mit einer einzigen Operation zugreifen.
Die Elemente von A sind binär dargestellt und die einzige Operation, die wir benutzen können, um
auf sie zuzugreifen, ist ”rufe das j-te Bit von A[i] auf”. Dies benötigt konstante Zeit.
Gehen Sie dabei davon aus, dass n = 2k für ein k ∈ N und die Codierung pro Zahl k Bits verwendet,
also führende Nullen vorhanden sind. Weiter können Sie davon vereinfachend davon ausgehen, dass
die Zahlen aufsteigend sortiert sind.
a) Geben Sie einen rekursiven Algorithmus an, der die fehlende ganze Zahl immer noch in Zeit
O(n) findet.
b) Geben Sie für den Algorithmus eine Rekursion für die Laufzeitabschätzung an.
c) Lösen Sie die Rekursion und beweisen Sie damit, dass ihr Algorithmus in Zeit O(n) läuft.
4
Musterlösung:
a) Man startet mit dem k-ten Bit und zählt für alle Wörter wie oft dieses Bit 0 bzw. 1 ist. Da eine
Zahl fehlt, muss entweder die 0 oder die 1 häufiger als k-tes Bit vorkommen. Die fehlende Zahl ist
unter den Zahlen enthalten, dessen k-tes Bit weniger häufig war. Damit muss die fehlende Zahl
mit diesem Bit beginnen. Nun macht man das ganze rekursiv auf den Zahlen, dessen k-tes Bit
weniger häufig war (≤ n/2 Elemente) und zwar mit dem k −1-ten Bit und so weiter. So bekommt
man Stück für Stück die Bits der fehlenden Zahl, während die Anzahl der zu untersuchenden
Elemente sich immer halbiert.
1: procedure search(A : Array [0..n − 2] of N≥0 )
2:
left := 0; right := n − 2, bit = k
3:
call findMissingNumber(A, left, right, bit)
4: return
1: procedure findMissingNumber (A : Array [0..n − 2] of N≥0 , left, right, bit : N≥0 )
2:
zeros := 0, ones := 0;
3:
for i :=left to right do
4:
if Bit bit of A[i] is 0 then zeros++ else ones++
5:
if zeros > ones then
6:
print 1, left := b left+right
c+1
2
7:
else
8:
print 0, right := b left+right
c−1
2
9:
if left ! = right then call findMissingNumber(A, left, right, bit−−)
10: return
b) Aus der Beschreibung des Algorithmus ergibt sich sofort die folgende Abschätzung für die Laufzeit T (n) ≤ T ( n2 ) + n.
c) Das Mastertheorem liefert uns mit d = 1, b = 2, c = 1 eine Gesamtlaufzeit von Θ(n). Alternativ
kann man die Rekursion auch
lösen: T (n) ≤ T ( n2 ) + n = T ( n4 ) + n2 + n =
P∞folgendermaßen
n
n
n
1
T ( 8 ) + 4 + 2 + n = · · · ≤ n i=1 2i = 2n, wobei die letzte Reihe gerade die geometrische Reihe
ist.
Aufgabe 4
(Karatsuba-Ofman Multiplikation, 4 Punkte)
Multiplizieren Sie 1242 mit 3163 (das sind Dezimalzahlen!) nach dem Algorithmus von Karatsuba und
Ofman.
Hinweis: Führen Sie genau die einstelligen Operationen direkt aus.
Musterlösung: Der Algorithmus:
1: Function recMult(a, b)
2:
assert a und b haben n = 2k Ziffern
3:
if n = 1 then return a · b
4:
Schreibe a als a1 · B k + a0
5:
Schreibe b als b1 · B k + b0
6:
c11 := recMult(a1 , b1 )
7:
c00 := recMult(a0 , b0 )
8:
c0110 := recMult((a1 + a0 ), (b1 + b0 ))
9:
e:= c11 · B 2k + (c0110 − c11 − c00 )B k + c00
10: return e
Schema zum Aufschrieb eines Multiplikationsaufrufs:
a·b
a1
· b1 =
recMult(a1 , b1 )
a0
· b0 =
recMult(a0 , b0 )
e
(a1 + a0 )·(b1 + b0 )=recMult((a1 + a0 ), (b1 + b0 ))
5
1242·3163
3928446
12·31
372
12·31= 372
42·63=2646
54·94=5076
s
HHH
HH
HH
HH
HH
HH
s
s
Hs
1·3= 3
2·1= 2
3·4=12
42·63
4·6=24
2·3= 6
6·9=54
2646
54·94
5076
5· 9 = 45
4· 4 = 16
9·13=117
s
s
9·13
117
Abbildung 1: Berechnungsbaum
Berechnung:
Aufbau als Rekursionsbaum.
6
0·1= 0
9·3=27
9·4=36
Herunterladen