Gehaxelts Blog

IT-Security & Hacking

[Uni] Aufwandsabschätzung von Programmen

In diesem Blogpost soll es um die Aufwandsabschätzung von Programmen gehen. Die Aufwandsabschätzung ist für die Klassifizierung eines Programmes von Vorteil.

Theorie

Wozu Aufwandsabschätzung?

Wenn man schon einige Programme geschrieben hat, dann fragt man sich irgendwann, ob die Implementierung eines Algorithmus zum Lösen eines Problems auch effizient ist. Vielleicht ist man sich bewusst, dass die eigene Implementierung nicht sehr performant ist, und man begründet diese mit den “schnellen” CPUs der heutigen Zeit, sodass eine längere Wartezeit von ein paar ms kaum zu merken ist.

Der Ansatz ist zwar verständlich, jedoch ist das nicht perfekt. Mit der Aufwandsabschätzung möchte man sich von der gefühlten Laufzeit eines Algorithmus lösen (Bsp: 1GhZ CPU: 1 Sek; 100 MhZ CPU: 10 Sekunden) und genau bestimmen, für welche Eingabe n der Algorithmus welche Aufwandsklasse, also Laufzeit, hat.

Man macht den Aufwand von der Eingabe abhängig, was auch logisch ist, denn wenn ein Algorithmus mehr zu tun hat, dann braucht dieser eben auch länger.

Aufwandsklassen

Es wurden einige Aufwandsklassen erstellt, welche die Programme nach ihrem Aufwand klassifizieren sollen. Man benutzt für die obere Schranke (Aufwand wächst langsamer oder gleich schnell) der Klassen die -Notation. (Worstcase)

Die Notation für die untere Schranke (Aufwand wächst gleich schnell oder schneller) ist die -Notation. (Bestcase)

Für alle Algorithmen, dessen Aufwand genauso stark wächst, nutzt man die -Notation. (Averagecase)

Ich werde mich hier auf die -Notation beschränken. Bei dieser kann man immer nach oben hin gröber werden. Man bestimmt also den Aufwand im Worstcase.

Ich bringe hier mal ein kleines Beispiel, in dem wir die folgenden Funktionen nehmen:

Wenn man sich die Graphen nicht direkt vorstellen kann, dann kann man einen Plotter nutzen. Man könnte argumentieren, dass der Aufwand f_2 geringer als der von z.B. f_1 wäre, da bis zum Schnittpunkt die Funktionswerte von f_2 unter denen von f_1 liegen. Das ist jedoch nicht ganz richtig, da wie oben schon gesagt, wir uns für große Eingaben n interessieren.

Dann kommt man auf die Lösung, dass f_4 den größten Aufwand hat, und die anderen Funktionen langsamer wachsen. Ein weiteres Beispiel wäre f_2 mit quadratischem Aufwand, bei dem nur f_1 langsamer wächst:

Welche Algorithmen werden als effizient bzw. ineffizient bezeichnet?

Diese Frage ist relativ leicht zu beantworten, denn es werden alle Algorithmen mit einem Aufwand < als effizient bezeichnet. Alles was größer oder gleich dem exponientiellen Wachstum ist, gilt als ineffizient, da schon für Eingaben ab n=100 bzw. n=1000 die zeitliche Abschätzung als hoffnungslos bezeichnet wird.

Um den Aufwand korrekt zu bestimmen, benötigt man einige wichtige Rechenregeln:

Zuletzt folgt noch eine Art Tabelle mit häufigen Rekurrenzgleichungen zur Berechnung des Aufwands:

Bisher sollte die Theorie ausreichen, um die Aufwandsberechnung in ihren Grundzügen zu verstehen.

Praxis

Wie immer möchte ich die oben vorgestellte Theorie mit einem / einigen Beispielen erklären.

Nehmen wir einfach eine einfache Funktion dessen Aufwand wir bestimmen möchten:

1
2
def mult(x,y):
        return x*y

Diese Funktion nimmt zwei Argumente x,y und gibt ihr Produkt zurück. Wenn man den Aufwand dieser Funktion bestimmen möchte, dann muss man den Aufwand der einzelnen Operationen wissen und kombinieren.

Bei diesem Beispiel ist die Aufwandsberechnung relativ einfach, denn:

Es gibt nur eine Operation (*), und egal was für eine große Eingabe n man nimmt, das Programm führt eben nur eine Operation durch. Deswegen ist der Aufwand dieser Funktion konstant.

Weiteres Beispiel:

1
2
3
4
def fak(num):
  if(num == 0):
      return 1
  return num*fak(num-1)

Dies soll eine Funktion sein, welche die Fakultät rekursiv berechnet.

Der Aufwand dafür kann so bestimmt werden:

Jetzt schauen wir in die Tabelle mit den Rekurrenzgleichungen und suchen die passende raus. In dem Fall ist es die erste, denn:

Somit hat diese Fakultätsberechnung linearen Aufwand.

Beispiel 3:

1
2
3
4
5
6
7
8
9
10
11
def calc(x):
  if(x < 0):
      return 0
  if(x == 0):
      return 1
  return calc(x-1)*help(x)
  
def help(x):
  if(x == 0):
      return x*x
  return help(x-1)

Auch wenn die Funktion kaum Sinn ergibt, da die für eine Eingabe != 0 immer 0 zurückgeben wird, wollen wir trotzdem mal den Aufwand der Funktion calc bestimmen.

An der Stelle kommen wir zunächst nicht weiter, da wir den Aufwand der Hilfsfunktion wissen müssen. Deswegen bestimmen wir den Aufwand bevor es mit der calc-Funktion weitergeht.

Ähnlich wie im zweiten Beispiel kommen wir hier auf linearen Aufwand.

Dann können wir den Aufwand von help in den Aufwand von cal einsetzen:

Und nun noch einmal in die Rekurrenzgleichungen schauen:

Die calc Funktion hat nun also einen quadratischen Aufwand.

Fazit

Ich hoffe ich konnte die Aufwandsberechnung ein wenig klarer machen bzw. näher bringen.

Uni

« WhiteLake Octopress Theme veröffentlicht Teamspeak Server Backup Script »