Algorithmen und Datenstrukturen – ¨Ubung 9

Werbung
TU Ilmenau, Fakultät für Informatik und Automatisierung
FG Komplexitätstheorie und Effiziente Algorithmen
Univ.-Prof. Dr. M. Dietzfelbinger, M. Sc. Philipp Schlag, M. Sc. Stefan Walzer
https://www.tu-ilmenau.de/iti/lehre/lehre-ss-2017/aud/
Algorithmen und Datenstrukturen – Übung 9 — Lösungen
Aufgabe 1 (Laufzeitabschätzung von D-a-C-Algorithmen)
Für ein Problem P sind drei Algorithmen bekannt, die einen Input der Länge n wie folgt lösen:
• Algorithmus A spaltet das Problem in fünf Teilprobleme der Größe n/2, löst diese rekursiv, und kombiniert
die Lösungen der Teilprobleme zu einer Lösung des Gesamtproblems in Zeit O(n).
• Algorithmus B spaltet das Problem in zwei Teilprobleme der Größe n − 1, löst diese rekursiv, und kombiniert
die Lösungen der Teilprobleme zu einer Lösung des Gesamtproblems in Zeit O(n).
• Algorithmus C spaltet das Problem in neun Teilprobleme der Größe n/3, löst diese rekursiv, und kombiniert
die Lösungen der Teilprobleme zu einer Lösung des Gesamtproblems in Zeit O(n2 ).
Schätzen Sie die Laufzeit jedes Algorithmus entweder mit dem Master-Theorem (wenn anwendbar) oder durch
direktes Ausrechnen ab. Welcher Algorithmus ist asymptotisch am schnellsten?
Lösung:
Seien A(n), B(n) und C(n) die Laufzeiten von A, B und C bei Eingabegröße n. Für A(n) gilt:
(
cA
falls n ≤ nA
A(n) ≤
n
5A( 2 ) + O(n) sonst
wobei cA und nA Konstanten sind. Nach dem ersten Fall des Mastertheorem (a = 5, b = 2 und f (n) = O(n1 ))
gilt A(n) = O(nlog2 5 ) = O(n2,322 ).
Für C(n) gilt:
(
cC
falls n ≤ nC
C(n) ≤
n
9A( 3 ) + O(n2 ) sonst
wobei cC und nC Konstanten sind. Nach dem zweiten Fall des Mastertheorems (a = 9, b = 3 und f (n) = O(n2 ))
gilt C(n) = O(nlog3 9 log n) = O(n2 log n).
Für B(n) ist das Mastertheorem nicht anwendbar, wir müssen Rekurrenz zu Fuß lösen:
(
cB
falls n ≤ nB
B(n) ≤
′
2B(n − 1) + cB · n sonst
wobei cB , c′B , nB Konstanten sind. Um einfacher rechnen zu können, und weil es für das Ergebnis in O-Notation
irrelevant ist, nehmen wir an nB = cB = c′B = 1. Dann ist der Rekursionsbaum bei Eingabegröße n ein
vollständiger binärer Baum der Tiefe n, wobei die Kosten in einem Knoten der Ebene ℓ genau n − ℓ sind, wenn
2
Algorithmen und Datenstrukturen – Übung 9
die Ebenen von oben nach unten von 0 bis n − 1 durchnummeriert sind. Da es auf Ebene ℓ genau 2ℓ Knoten gibt,
ergibt sich:
B(n) ≤
0
2
| {z· n}
Wurzelknoten
+21 · (n − 1) + 22 · (n − 2) + . . . + 2n−2 · 2 + 2| n−1
{z · 1} .
Blätter
Die rechte Seite ist offensichtlich größer als 2n und kleiner als 2n · n, also gilt sicherlich B(n) = O(2n · n).
Tatsächlich ist es aber auch nicht schwierig B(n) = O(2n ) zu zeigen.
Aufgabe 2 (Suche eines Gipfels)
Wir nennen ein Array A[1..n] aus n verschiedenen natürlichen Zahlen unimodal, wenn ein Index i ∈ {1, . . . , n}
existiert, so dass A[1..i] aufsteigend und A[i..n] absteigend sortiert ist. Das Element A[i] nennen wir den Gipfel
von A. Geben Sie einen Divide-and-Conquer-Algorithmus an, der folgendes Problem in Zeit O(log n) löst:
Gipfelbestimmung
Input: Ein unimodales Array A[1..n].
Output: Der Gipfel von A.
Lösung:
Die Idee ist einfach: An benachbarten Einträgen kann man erkennen in welche Richtung (links oder rechts) die
Einträge steigen, also auch in welcher Richtung der Gipfel liegt.
Formal heißt das: Für ein beliebiges i ∈ {1, . . . , n − 1} können wir in Zeit O(1) prüfen ob A[i] < A[i + 1].
Trifft das zu, so liegt der Gipfel von A im Bereich {i + 1, . . . , n}, sonst liegt der Gipfel in {1, . . . , i}. Setzen
wir i = n/2 müssen wir anschließend also in einem halb so großen Array weitersuchen. So wird in konstanter
Zeit das Problem auf ein gleichartiges Problem halber Größe reduziert. Der zweite Fall des Mastertheorems (mit
f (n) = 1, a = 1, b = 2) liefert eine Laufzeit von O(log n).
(Im Wesentlichen passiert hier eine binäre Suche.)
Aufgabe 3 (Bestimmung der maximalen Differenz)
Geben Sie einen Divide-and-Conquer-Algorithmus mit Laufzeit O(n log n) für folgendes Problem an:
Maximale Differenz
Input: Ein Array A[1..n] mit n natürlichen Zahlen.
Output: Indizes i, j, mit 1 ≤ i ≤ j ≤ n, so dass A[j] − A[i] = max{A[l] − A[k] | 1 ≤ k ≤ l ≤ n}.
Für Interessierte: Geben Sie einen Algorithmus mit Laufzeit O(n) an.
Lösung:
Betrachte das Array B[1 . . . n−1] mit B[k] = A[k + 1] − A[k] für 1 ≤ k ≤ n − 1. Nun gilt für alle 1 ≤ i ≤ j ≤ n:
A[j] − A[i] =
j−1
X
k=i
A[k + 1] − A[k] =
j−1
X
k=i
B[k].
3
Algorithmen und Datenstrukturen – Übung 9
Mit anderen Worten: Eine maximale Differenz in A zu finden ist gleichbedeutend damit eine maximale Teilsumme
in B zu finden. Wie letzteres geht haben Sie im Praktikum 0 gelernt (unter anderem wurde dort ein Divideand-Conquer Algorithmus vorgestellt). Da sich B aus A in Linearzeit berechnen lässt sind alle Algorithmen für
maximale Teilsumme problemlos auf maximale Differenz übertragbar.
Aufgabe 4 (Majority Element)
Ein Element x hat die Mehrheit in einem Array A[1 . . . n], wenn es in mehr als der Hälfte der Einträge von A
vorkommt. Geben sie einen rekursiven Algorithmus an, der folgendes Problem in Zeit O(n) löst.
Majority Element
Input: Ein Array A[1..n].
Output: Dasjenige x, das die Mehrheit in A hat, falls ein solches Element existiert. None sonst.
Lösung:
Gehen wir zunächst vereinfachend davon aus, dass n gerade ist.
Definitionen. Wir paaren die Elemente in A, betrachten also die n2 Paare (A[1], A[2]), (A[3], A[4]), . . . , (A[n −
1], A[n]). Für ein beliebiges Element x können wir nun zählen, in wievielen Paaren p0 (x) dieses x nicht vorkommt, in wievielen Paaren p1 (x) es genau einmal vorkommt und inwievielen Paaren p2 (x) es zweimal vorkommt. Dann ist die Anzahl c(x) der Vorkommen von x in A gegeben durch p1 (x) + 2p2 (x).
Beobachtung. Wegen p0 (x) + p1 (x) + p2 (x) =
c(x) −
n
2
n
2
(weil es
n
2
Paare gibt), gilt nun:
= p1 (x) + 2p2 (x) − p0 (x) − p1 (x) − p2 (x) = p2 (x) − p0 (x).
Nach Definition ist x genau dann ein Majority Element wenn c(x) >
p2 (x) > p0 (x).
n
2
(⋆)
und nach (⋆) ist das äquivalent zu
Konstruktion von A′ . Betrachte folgende Konstruktion:
• A′ ist zu Begin leer.
• Für jedes Paar (A[i], A[i + 1]): Falls A[i] = A[i + 1], dann füge A[i] an A′ an.
Für A = [1, 1, 4, 3, 3, 3, 4, 2, 2, 1] gilt zum Beispiel: A′ = [1, 3]. In A′ wird x genau p2 (x) mal vorkommen.
Ansonsten enthält A′ maximal p0 (x) Elemente (die p1 (x) Paare, in denen x genau einmal vorkommt erzeugen
keinen Eintrag in A′ ). Daher gilt: Wenn x in A die Mehrheit hatte, so gilt (nach (⋆)) dass x auch in A′ die
Mehrheit hat1 ! Das begründet folgenden rekursiven Algorithmus. In ihm haben wir den Fall von ungeradem n
behandelt indem für das letzte Element von A einfach manuell geprüft wird, ob es sich um ein Mehrheitselement
handelt.
1 Die
Umkehrung gilt allerdings nicht.
4
Algorithmen und Datenstrukturen – Übung 9
Algorithm 1: MajorityElement(A[1 . . . n])
Eingabe : Array A[1 . . . n]
Ausgabe: Majority Element x von A, oder None, wenn es keines gibt.
1 if n = 0 then return None
2 if n = 1 then return A[1]
// Ab hier: Mindestens zwei Elemente
3
if n ist ungerade then
// letztes Element einmal separat durchzählen
4
5
6
7
8
9
if #{i | 1 ≤ i ≤ n, A[i] = A[n]} >
return A[n]
n
2
then
A′ ← empty()
for i ← 1 to ⌊ n2 ⌋ do
if A[2 · i − 1] = A[2 · i] then
A′ .append(A[2 · i])
12
x ← MajorityElement(A′ )
if x ̸= None and #{i | 1 ≤ i ≤ n, A[i] = x} >
return x
13
return None
10
11
n
2
then
Der Algorithmus macht einen rekursiven Aufruf auf einer Instanz halber Größe. Zusätzlich dazu wird linear viel Zeit verbraucht (in der for-Schleife) sowie jeweils in den Zähl-Befehlen (#). Nach dem ersten Fall des
Mastertheorems (f (n) = O(n), a = 1, b = 2) ist die Laufzeit von MajorityElement O(n).
Herunterladen