Kurs:Algorithmen und Datenstrukturen/Vorlesung/Aufwandsanalyse von iterativen Algorithmen
Aufwandsanalyse von iterativen Algorithmen
BearbeitenAuf dieser Seite wird der Aufwand von iterativen Algorithmen analysiert. Als Aufwand wird die Anzahl der durchlaufenen Operationen zur Lösung des Problems bezeichnet ( Zuweisungen, Vergleiche...). Häufig ist der Aufwand abhängig vom Eingabeparameter (Problemgröße). Die Aufwandsklasse sagt, wie der Aufwand in Abhängigkeit von der Problemgröße wächst. Doch wie kann man nun bei beliebigem Java Code die Aufwandsklasse bestimmen?
Aufwand von Programmen ablesen
Bearbeitenvoid alg1(int n){
int m = 2;
int i;
int k = n;
while (n > 0){
i = k;
while (i > 0) {
m = m + i;
i = i / 2;
}
n = n - 1;
}
}
Die Aufwandsklasse ist . Die äußere Schleife wird n-mal durchlaufen und die Innere Schleife log n-mal.
void alg1(int n) {
int m = 1;
int i = 0;
while (m < n) {
while (i < m)
i = i + 1;
m = m + i;
}
}
Hier ist die Aufwandsklasse O(n+log n). In jedem Durchlauf der äußeren Schleife wird m verdoppelt, d.h. sie läuft log n Mal. Die innere Schleife läuft bis n/2, aber nicht jedes Mal, weil i nur ein Mal auf 0 gesetzt wird. Man könnte als Aufwandsklasse auch O(n) sagen, da der Summand log n nicht ins Gewicht fällt.
Bestandteile iterativer Algorithmen
BearbeitenZum einen haben wir elementare Anweisungen wie Zuweisungen und Vergleiche. Diese haben einen Aufwand von 1.
Des Weiteren haben wir Sequenzen oder auch geschrieben. Die obere Grenze ist und die untere Grenze ist . Dabei ist der Aufwand, der bei der Ausführung von entsteht.
Ein weiterer Bestandteil ist die Selektion. . Hier ist die obere Grenze und die untere Grenze .
Außerdem haben wir Iterationen . Hierbei ist die obere und die untere Grenze die Anzahl der Schleifendurchläufe, und die untere Grenze . Doch wie ist der Aufwand für eine for-Schleife? Ein Beispiel ist . Die Antwort ist die Abbildung auf eine while-Schleife.
while(B) {
}
Beispiel Sequenz
Bearbeitenpublic int berechne(int n) {
int x = 0;
x = x + 1;
return x;
}
Jede Zeile hat den Aufwand . Wie viele Operationen werden nun durchlaufen? Und ist die Anzahl abhängig vom Eingabeparameter? Der Aufwand ist
Die Aufwandsklasse ist somit
Beispiel Schleifen
Bearbeitenpublic int berechne(int n) {
int x = 0;
for (int i=0; i < n; i++) {
x = x + 1;
}
return x;
}
Die for Schleife hat den Aufwand . Die Initialisierung und das return haben jeweils den Aufwand .
Der Gesamtaufwand ist somit . Somit ist die Aufwandsklasse .
public int berechne(int n) {
int x = 0;
for (int i=0; i < n; i++) {
for (int j=0; j < n; j++) {
x = x + 1;
}
}
return x;
}
Hier hat die for-Schleife den Aufwand und die Initialisierung und das return wieder . Damit ergibt der sich Gesamtaufwand . Daraus folgt die Aufwandsklasse .
Beispiel Selektion
Bearbeitenpublic int berechne(int n) {
if (n % 2 == 0) {
int x = 0;
for (int i=0; i < n; i++) {
x = x + 1;
}
return x;
}else{
return n;
}
}
Hier hat die for-Schleife einen Aufwand von . Die Initialisierung und das return wieder .
Die obere Grenze ist somit und die untere Grenze
Faustregeln
BearbeitenZu den häufig verwendeten Faustregeln gehört, dass wenn wir keine Schleife haben, der Aufwand konstant ist. Eine weitere ist, dass bei einer Schleife immer ein linearer Aufwand vorliegt. Bei zwei geschachtelten Schleifen haben wir immer einen quadratischen Aufwand. Doch die Faustegeln gelten nicht ohne Ausnahmen. Besonders Acht geben muss man bei Aufwandsbestimmungen für Schleifen, bei mehreren Eingabevariablen, bei Funktionsaufrufen und bei Rekursionen.
Aufwandsbestimmung für Schleifen
Bearbeitenpublic int berechne(int n) {
int x = 0;
for (int i=0; i < 5; i++) {
x = x + 1;
}
return x;
}
Der Schleifenabbruch hängt nicht vom Eingabeparameter ab. Der Aufwand beträgt somit haben wir die Aufwandsklasse
public int berechne(int n) {
int x = 0;
for (int i=1; i < n; i = 2*i) {
x = x + 1;
}
return x;
}
Hier wächst die Laufvariable nicht linear an.Daher ist der Aufwand und wir haben die Aufwandsklasse .
Doch gibt es eine allgemeine Methodik zum Bestimmen des Schleifenaufwands?
for (int i=1; i < n; i=2*i) {
x = x + 1;
}
Schritt 1: Wie entwickelt sich hier die Laufvariable? Der Startwert i ist 1 und die Veränderung in jedem Schritt ist . Die Laufvariable entwickelt sich somit wie folgt:
Nach dem 1. Durchlauf
Nach dem 2. Durchlauf
Nach dem 3. Durchlauf
Nach dem k. Durchlauf
Schritt 2: Nach wie vielen Durchläufen wird die Schleife abgebrochen?
Der Abbruch erfolgt, wenn
:
Somit erfolgt ein Abbruch nach ⌈ ⌉ Durchläufen.
public int berechne(int[] f1, int[] f2) {
int result = 0;
for (int i=0; i < f1.length; i++) {
for (int j=0; j < f2.length; j++) {
if (f1[i] == f2[j]) result++;
}
}
return result;
}
Hier haben wir nun eine for Schleife mit mehreren Eingabevariablen. Die Problemgrößen sind .
public int berechne2(int[] f1, int[] f2){
f2 = mergeSort(f2);
int result = 0;
for (int i=0; i < f1.length; i++) {
if (binarySearch(f2, f1[i])) result++;
}
return result;
}
Der Aufwand ist hier . Somit ist die Aufwandsklasse .
In diesem Beispiel haben wir wieder mehreren Eingabevariablen. Diese sind die gleichen Problemgrößen .
public int berechne2(int[] f1, int[] f2){
int result = 0;
for (int i=0; i < f1.length; i++) {
for (int j=0; j < f2.length; j++) {
if (f1[i] == f2[j]) result++;
}
}
return result;
}
Der Aufwand ist hier wie folgt: . Somit ist die Aufwandsklasse .
Literatur
BearbeitenDa die Vorlesungsinhalte auf dem Buch Algorithmen und Datenstrukturen: Eine Einführung mit Java von Gunter Saake und Kai-Uwe Sattler aufbauen, empfiehlt sich dieses Buch um das hier vorgestellte Wissen zu vertiefen. Die auf dieser Seite behandelten Inhalte sind in Kapitel 7.3.4 zu finden.