• Keine Ergebnisse gefunden

Beispiel: Algorithmus von Euklid

N/A
N/A
Protected

Academic year: 2022

Aktie "Beispiel: Algorithmus von Euklid"

Copied!
108
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Algorithmen und Datenstrukturen

Werner Struckmann

Wintersemester 2005/06

(2)

2. Imperative Algorithmen

2.1 Variable, Anweisungen und Zustände 2.2 Felder

2.3 Komplexität von Algorithmen 2.4 Korrektheit von Algorithmen 2.5 Konzepte imperativer Sprachen 2.6 Rekursionen

(3)

Paradigmen zur Algorithmenbeschreibung

In einem imperativen Algorithmus gibt es Variable, die

verschiedene Werte annehmen können. Die Menge aller Variablen und ihrer Werte sowie der Programmzähler beschreiben den

Zustand zu einem bestimmten Zeitpunkt. Ein Algorithmus bewirkt eine Zustandstransformation.

Ein funktionaler Algorithmus formuliert die Berechnung durch Funktionen. Die Funktionen können rekursiv sein; auch gibt es Funktionen höherer Ordnung.

(4)

Paradigmen zur Algorithmenbeschreibung

In einem objektorientierten Algorithmus werden Datenstrukturen und Methoden zu einer Klasse zusammengefasst. Von jeder

Klasse können Objekte gemäß der Datenstruktur erstellt und über die Methoden manipuliert werden.

Ein logischer (deduktiver) Algorithmus führt Berechnungen durch, indem er aus Fakten und Regeln durch Ableitungen in einem

logischem Kalkül weitere Fakten beweist.

(5)

Beispiel: Algorithmus von Euklid

Der folgende, in einer imperativen Programmiersprache formulierte Algorithmus von Euklid berechnet den größten gemeinsamen

Teiler der Zahlen x

,

y

N mit x

0 und y

>

0:

a := x;

b := y;

while b # 0

do r := a mod b;

a := b;

b := r od

Anschließend gilt a

=

ggT

(

x

,

y

)

.

(6)

Beispiel: Algorithmus von Euklid

Variable z2 z5 z8 z11 z14

r – 36 16 4 0

a 36 52 36 16 4

b 52 36 16 4 0

ggT

(

36

,

52

) =

4 Durchlaufene Zustände: z0

,

z1

,

z2

, . . . ,

z14 Zustandstransformation: z0

7−→

z14

(7)

Imperative Konzepte

Variable: Abstraktion eines Speicherbereichs, ein Wert eines gegebenen Datentyps kann gespeichert und beliebig oft

gelesen werden, solange nicht ein neuer Wert gespeichert und der alte damit überschrieben wird.

Zustand: Abstraktion des Speicherinhalts, Gesamtheit der momentanen Werte aller Variablen, ändert sich durch die Ausführung von Anweisungen.

Anweisung: Vorschrift zur Ausführung einer Operation, ändert im Allgemeinen den Zustand.

(8)

Datentypen

Datentyp: Die Zusammenfassung von Wertebereichen und Operationen zu einer Einheit.

Abstrakter Datentyp: Schwerpunkt liegt auf den

Eigenschaften, die die Operationen und Wertebereiche besitzen.

Konkreter Datentyp: Beschreibt die Implementierung eines Datentyps.

(9)

Grundlegende Datentypen

Oft werden mathematische Konzepte als Grundlage für einen Datentyp verwendet. Ein Datentyp besteht aus einem

Wertebereich und einer Menge von Operationen.

bool: die booleschen Werte „wahr“ und „falsch“

Operationen: logische Verknüpfungen, wie zum Beispiel „und“

und „oder“.

int: ganze Zahlen,

{

minint

, · · · , −

1

,

0

,

1

, · · · ,

maxint

} ⊆

Z minint und maxint besitzen etwa den gleichen Betrag.

Operationen: arithmetische und Vergleichsoperationen.

Die Rechengesetze gelten in der Regel nicht, wenn man über die Darstellungsgrenzen gerät.

(10)

Grundlegende Datentypen

real, float: Näherungswerte für reelle Zahlen, dargestellt durch Gleitpunktzahlen:

r

=

m

·

be

m

Z Mantisse, b

N Basis, e

Z Exponent Die Darstellung erschließt den Zahlenbereich mit konstanter relativer Genauigkeit.

Operationen: arithmetische und Vergleichsoperationen.

Die Rechengesetze gelten im Allgemeinen nur näherungsweise.

Gleitpunktzahlen sollten nicht auf Gleichheit überprüft

werden, stattdessen sollte

|

x

y

| < ε

für einen kleinen Wert

(11)

Grundlegende Datentypen

char: Zeichen aus einem Alphabet

Mit Vergleichsoperationen und den Funktionen

ord

:

char

int und chr

:

int

char, die eine Zuordnung

zwischen den Zeichen aus dem Alphabet und ganzen Zahlen herstellen.

(12)

Variable

Variable x : t: Abstraktion eines Speicherplatzes und Zuordnung eines Datentyps

X Menge der Variablen x

:

t x

X

t

= τ(

x

)

Typ von x v

= σ(

x

)

Wert von x

v

W

(

t

)

Wertemenge von t

Deklaration: Ein Variablenname wird einem Speicherbereich und einem Typ zugeordnet.

var i, j: int;

var r: real;

var c: char;

(13)

Zustand

Ein Zustand bezeichnet die Belegung aller Variablen zu einem Zeitpunkt.

Modellierung:

σ :

x1

7→

v1

, . . . ,

xn

7→

vn xi

X

,

vi

W

(

ti

) ∀

i

∈ {

1

, . . . ,

n

}

Tabellarisch: x1

· · ·

xn v1

· · ·

vn

Mathematisch: Abbildung

σ :

X

W

= {

v1

, . . . ,

vn

}

, wobei

σ(

xi

) ∈

W

(

ti

) ∀

i

∈ {

1

, . . . ,

n

}

σ(

x

)

ist die Belegung der Variablen x im Zustand

σ

.

(14)

Zuweisung

Syntax: x

v v

W

(τ(

x

))

Zuweisung, Grundbaustein: Nach Ausführung der Zuweisung gilt

σ(

x

) =

v

.

Ist

σ :

X

W ein Zustand und wählt man eine Variable x

X sowie einen Wert v vom passenden Typ, so ist der transformierte Zustand

σ

<xv> wie folgt definiert:

σ

<xv>

(

y

) =

( v falls x

=

y

σ(

y

)

sonst

(15)

Semantik

Die Semantik beschreibt die Bedeutung eines Algorithmus.

Sie ist eine (partielle) Funktion, die jeder Anweisung eine Zustandsänderung zuordnet:

[ ] :

A

→ ((

X

W

) → (

X

W

))

mit

[

a

](σ) = σ

Semantik einer Zuweisung:

[

x

v

](σ) = σ

<xv>

(16)

Ausdrücke/Terme

Beispiele für Ausdrücke:

Konstante: 3, 2

.

7182, A

Variable: x1, x2, y

Operatoren und Funktionsaufrufe: x1

+

3, f

(

x1

)

Zusammengesetzte Ausdrücke: f

(

x1

+

3

) −

2

.

7182

+

y

Falls ein Ausdruck t die Variablen x1, . . . , xn enthält, schreiben wir t

(

x1

, . . . ,

xn

)

.

Beispiel eines booleschen Ausdrucks: x

5

y

<

4

(17)

Wert eines Ausdrucks

Die Auswertung von Ausdrücken mit Variablen ist

zustandsabhängig. An die Stelle der Variablen wird ihr aktueller Wert gesetzt.

Beispiel: Für den Ausdruck 2

·

x

+

1 ist sein Wert im Zustand

σ

durch 2

σ(

x

) +

1 gegeben.

Der so bestimmte Wert des Ausdrucks t

(

x1

, . . . ,

xn

)

im Zustand

σ

wird mit

σ(

t

(

x1

, . . . ,

xn

))

bezeichnet.

Beispiel:

σ(

2

·

x

+

1

) =

2

σ(

x

) +

1

(18)

Zuweisungen

Diese Festlegung erlaubt Wertzuweisungen mit Ausdrücken auf der rechten Seite

y

t

(

x1

, . . . ,

xn

)

Der transformierte Zustand hierfür ist wie folgt definiert:

[

y

t

(

x1

, . . . ,

xn

)](σ) = σ

<y←σ(t(x1,...,xn))>

(19)

Zuweisungen

Beispiel: Semantik der Zuweisung

x

2

·

x

+

1

Transformation:

[

x

2

·

x

+

1

](σ) = σ

<x2·σ(x)+1>

Hier handelt es sich nicht um eine rekursive Gleichung für x, da auf der rechten Seite der „alte“ Wert von x benutzt wird.

Wertzuweisungen sind die einzigen elementaren Anweisungen imperativer Algorithmen. Aus ihnen werden zusammengesetzte Anweisungen gebildet, aus denen imperative Algorithmen

bestehen.

(20)

Zusammengesetzte Anweisungen

Elementare Anweisungen können auf unterschiedliche Arten zu komplexen Anweisungen zusammengesetzt werden:

1. sequenzielle Ausführung, 2. bedingte Ausführung,

3. wiederholte Ausführung,

4. Ausführung als Unterprogramm,

5. rekursive Ausführung eines Unterprogramms.

Diese Möglichkeiten werden als Kontrollstrukturen bezeichnet. Wir betrachten jetzt die ersten drei dieser zusammengesetzten

Anweisungen. Auf die anderen kommen wir am Schluss des Kapitels zu sprechen.

(21)

Sequenzielle Ausführung von Anweisungen

Definition: Sind a1 und a2 Anweisungen, so auch a1

;

a2

Informelle Bedeutung: „Führe erst a1, dann a2 aus.“

Semantik:

[

a1

;

a2

](σ) = [

a2

]([

a1

](σ))

Darstellung im Flussdiagramm:

a1

a2

(22)

Bedingte Ausführung von Anweisungen

Definition: Sind a1 und a2 Anweisungen und P ein boolescher Ausdruck, so ist auch

if

P

then

a1

else

a2

fi

eine Anweisung.

Voraussetzung:

σ(

P

)

kann ausgewertet werden (ansonsten ist die Anweisung undefiniert)

Informelle Bedeutung: „Falls P gilt, führe a1 aus, sonst a2.“

(23)

Bedingte Ausführung von Anweisungen

Semantik:

[ if

P

then

a1

else

a2

fi ](σ) =





[

a1

](σ)

falls

σ(

P

) =

true

[

a2

](σ)

falls

σ(

P

) =

false

Darstellung im Flussdiagramm:

a1 a2

P

true false

(24)

Wiederholte Ausführung von Anweisungen

Definition: Ist a eine Anweisung und P ein boolescher Ausdruck, so ist auch

while

P

do

a

od

eine Anweisung.

Voraussetzung:

σ(

P

)

kann ausgewertet werden (ansonsten ist die Anweisung undefiniert)

Informelle Bedeutung: „Solange P gilt, führe a aus.“

(25)

Wiederholte Ausführung von Anweisungen

Semantik:

[ while

P

do

a

od ](σ) =





σ

falls

σ(

P

) =

false

[ while

P

do

a

od ]([

a

](σ))

sonst Diese Definition ist rekursiv.

While-

Schleifen müssen nicht terminieren.

Darstellung im Flussdiagramm:

a P true

false

(26)

Flussdiagramme

Normierte Methode (DIN 66001) zur Darstellung von Programmen

Beginn: Start

Ende: Stop

Elementare Anweisung: a

Entscheidung durch booleschen Ausdruck:

P

Eingabe nach n: Eingabe n

Ausgabe von p: Ausgabe p

(27)

Konkrete Umsetzung

Sprachen mit nur diesen Anweisungen sind bereits berechnungsuniversell.

In existierenden Programmiersprachen gibt es fast immer diese Anweisungen, oft jedoch mehr.

Beispiel:

case-

oder

repeat

-Anweisung.

Schlüsselwörter und Syntax der Kontrollstrukturen variieren von Sprache zu Sprache.

Beispiel:

end if

statt

fi

.

Hierarchische Struktur der Anweisungen: Anweisungen können Bestandteil anderer Anweisungen sein.

(28)

Imperative Algorithmen

Es werden die Datentypen int, real und bool verwendet.

Aufbau imperativer Algorithmen (Syntax):

<Programmname>

var

x,y,. . . :

int

; p,q:

bool

; Variablendeklarationen

input

: x1

, . . . ,

xn

;

Eingabevariablen

a; Anweisung(en)

output

: y1

, . . . ,

ym Ausgabevariablen

Die Semantik wird durch eine Zustandsüberführungsfunktion beschrieben.

(29)

Imperative Algorithmen

Die Semantik eines imperativen Algorithmus ist eine partielle Funktion

[

PROG

] :

W1

× · · · ×

Wn

V1

× · · · ×

Vm

[

PROG

](

w1

, . . . ,

wn

) = (σ(

y1

), . . . , σ(

ym

)) σ = [

a

](σ

0

)

σ

0

(

xi

) =

wi

Wi für i

=

1

, . . . ,

n wobei

PROG: <Programmname>

Wi

:

Wertebereich des Typs von xi, für i

=

1

, . . . ,

n Vj

:

Wertebereich des Typs von yj, für j

=

1

, . . . ,

m

(30)

Imperative Algorithmen

Der Algorithmus legt eine Zustandstransformation fest. Die Eingabe befindet sich im Zustand

σ

0.

Die Ausführung imperativer Algorithmen besteht aus einer Abfolge von Zuweisungen. Diese Folge wird mittels

„Sequenz“, „bedingter Ausführung“ und „wiederholter Ausführung“ aus den Zuweisungen gebildet.

Jede Zuweisung definiert eine Transformation des Zustands.

Die Semantik des Algorithmus ist durch die Kombination all dieser Zustandstransformationen festgelegt.

Falls die Auswertung von a nicht terminiert, ist die Funktion

[

PROG

]

nicht definiert.

(31)

Algorithmus von Euklid

Der Algorithmus von Euklid lautet in dieser Notation:

EUKLID

var a, b, r, x, y: int;

input x, y;

a ← x;

b ← y;

while b # 0 do r ← a mod b;

a ← b;

b ← r;

od;

output a;

(32)

Fakultätsfunktion

k

! =

1

·

2

·

3

· · ·

k

FAK

var x, y: int;

input x;

y ← 1;

while x > 1 do y ← y * x;

x ← x - 1;

od;

output y;

Es gilt:

[

FAK

](

w

) =

(w

!

für w

>

0 1 sonst

Start Eingabe x

y ← 1

x > 1 true

y ← y · x x ← x − 1

false

Ausgabe y Stop

(33)

Auswertung der Fakultätsfunktion

Signatur der Semantikfunktion:

[

FAK

] : int → int

Das Ergebnis der Berechnung ist die Belegung der Variablen y im Endzustand:

σ(

y

)

.

Der Endzustand

σ

ist laut Definition

σ = [

a

](σ

0

)

, wobei a die Folge aller Anweisungen des Algorithmus ist.

σ

0 ist der Startzustand. Die Eingabe befindet sich in

σ

0

(

x

)

.

Da nur die zwei Variablen x und y auftreten, können wir einen Zustand

σ

als Paar

(σ(

x

), σ(

y

))

schreiben.

bedeutet, dass der Wert der Variablen keine Rolle spielt bzw. undefiniert ist.

(34)

Auswertung der Fakultätsfunktion

Gesucht:

[

FAK

](

3

)

σ = [

a

](σ

0

) = [

a

](

3

, ⊥ )

= [

y

1

; while

x

>

1

do

y

y

x

;

x

x

1

od ](

3

, ⊥ )

= [ while

x

>

1

do

y

y

x

;

x

x

1

od ]([

y

1

](

3

, ⊥ ))

=: [ while

B

do β od ]([

y

1

](

3

, ⊥ ))

= [ while

B

do β od ](

3

,

1

)

=

(

σ

falls

σ(

B

) =

false

[ while

B

do β od ]([β](σ))

sonst

=

(

(

3

,

1

)

falls

σ(

x

>

1

) =

false

[ while

B

do β od ]([

y

y

x

;

x

x

1

](σ))

sonst

(35)

Auswertung der Fakultätsfunktion

· · · = [ while

B

do β od ]([

y

y

x

;

x

x

1

](

3

,

1

))

= [ while

B

do β od ]([

x

x

1

]([

y

y

x

](

3

,

1

)))

= [ while

B

do β od ]([

x

x

1

](

3

,

3

))

= [ while

B

do β od ](

2

,

3

)

= [ while

B

do β od ]([

y

y

x

;

x

x

1

](

2

,

3

))

= [ while

B

do β od ](

1

,

6

)

=

(

(

1

,

6

)

falls

σ(

x

>

1

) =

false

[ while

B

do β od ]([

y

y

x

;

x

x

1

](

1

,

6

))

sonst

= (

1

,

6

)

[FAK](3) = 6

(36)

Fibonacci-Zahlen (iterativ)

f0

=

0

,

f1

=

1

,

fn

=

fn1

+

fn2

,

n

>

1 FIB:

var x,a,b,c: int;

input x;

a ← 0;

b ← 1;

while x > 0 do c ← a + b;

a ← b;

b ← c;

x ← x - 1;

od;

output a;

[

FIB

](

x

) =

( die x-te Fibonacci-Zahl, falls x

>

0

,

(37)

Fibonacci-Zahlen (rekursiv)

Einzelheiten zu rekursiven Programmen werden später behandelt!

f0

=

0

,

f1

=

1

,

fn

=

fn1

+

fn2

,

n

>

1 FIB:

var n, x: int;

input n;

if n = 0 then x ← 0 fi;

if n = 1 then x ← 1 fi;

if n > 1 then x ← FIB(n - 1) + FIB(n - 2) fi;

output x;

Ein rekursiv ausgedrückter Algorithmus ist häufig eleganter als sein iteratives Äquivalent. Nachteil ist evtl. eine längere Laufzeit.

(38)

Felder

Definition: Ein Feld ist eine indizierte Menge von Variablen des gleichen Typs. Felder sind generische Datentypen.

Deklaration:

var x[I]: T;

var coords[1..3]: real;

var str[0..4095]: char;

Indexmenge: I endlich, häufig I

N, aber andere Indexmengen sind möglich.

Zugriff: x

[

i

]

sowohl lesend als auch schreibend, wobei i

I. Längenänderungen von Feldern sind nicht in allen

Programmiersprachen möglich.

(39)

Verbreitete Spezialfälle von Feldern

Vektor:

var v[1..3]: real;

Matrix:

var v[1..4, 1..4]: real;

Strings:

var str[0..102]: char;

(40)

For-Schleife

Die For-Schleife ist eine weitere Kontrollstruktur zur Wiederholung von Anweisungen.

Es seien j

,

k

N Konstante,

var

i

: int

eine Variable und a eine Anweisung.

for i ← j to k do a; od

entspricht

i ← j; while i ≤ k do a; i ← i + 1; od

i wird Laufvariable der Schleife genannt.

Viele Programmiersprachen enthalten Varianten dieser Schleife.

(41)

Maximale Summe eines Teilfelds

Die folgenden Programme berechnen die maximale Summe aufeinanderfolgender Zahlen in einem Feld

var

x

[

1

..

n

] : int

.

m

=

max





j

X

k=i

x

[

k

] |

1

i

j

n





x sei das Feld:

1 2 3 4 5 6 7 8 9 10

31

41 59 26

53 58 97

93

23 84 Die maximale Summe aufeinanderfolgender Zahlen ist

x

[

3

] +

x

[

4

] +

x

[

5

] +

x

[

6

] +

x

[

7

] =

187. Keine andere Summe besitzt einen höheren Wert. Beispielsweise ist

x

[

1

] +

x

[

2

] +

x

[

3

] +

x

[

4

] =

75.

(42)

Maximale Summe eines Teilfelds

Idee: Berechne alle möglichen Summen.

MaxSum1

var x[1..n]: int;

var i,j,k,s,m: int;

input x;

m ← 0; // Wert der leeren Summe for i ← 1 to n do

for j ← i to n do s ← 0;

for k ← i to j do s ← s + x[k];

od;

m ← max(m, s);

od;

od;

output m;

(43)

Maximale Summe eines Teilfelds

Idee: Einsparen der inneren Schleife durch Wiederverwenden bereits bekannter Summen.

MaxSum2

var x[1..n]: int;

var i,j,s,m: int;

input x;

m ← 0; // Wert der leeren Summe for i ← 1 to n do

s ← x[i];

m ← max(m, s);

for j = i+1 to n do s ← s + x[j];

m ← max(m, s);

od;

od;

output m;

Dieser Algorithmus besitzt zwei ineinandergeschachtelte Schleifen.

(44)

Maximale Summe eines Teilfelds

Idee: Genau überlegen, was eine hohe Summe ausmacht. Diese Lösung stammt von M. Shamos (1977).

MaxSum3

var x[1..n]: int;

var i,s,m: int;

input x;

m ← 0;

s ← 0;

for i ← 1 to n do

s ← max(0, s + x[i]);

m ← max(m, s);

od;

output m;

Dieser Algorithmus hat nur noch eine Schleife.

(45)

Einführung

In der Regel lässt sich ein Problem durch verschiedene Algorithmen lösen (zum Beispiel „maximale Summe aufeinanderfolgender Elemente“).

Welcher Algorithmus soll gewählt werden?

Die Algorithmen müssen hinsichtlich ihres Verhaltens verglichen werden.

Man benötigt ein Maß für den Aufwand eines Algorithmus.

(46)

Komplexität von Algorithmen und Problemen

Unter der Komplexität eines Algorithmus versteht man den Aufwand, den der Algorithmus zur Lösung des Problems benötigt. Typischerweise ist hier die

Laufzeit des Algorithmus oder der

Speicherbedarf des Algorithmus gemeint. Man unterscheidet die

Komplexität im günstigsten Fall (best-case), die

Komplexität im mittleren Fall (average-case) und die

Komplexität im ungünstigsten Fall (worst-case).

Unter der Komplexität eines Problems versteht man die

Komplexität des besten Algorithmus zur Lösung des Problems im ungünstigsten Fall.

(47)

Komplexität eines Algorithmus

Umfang n eines Problems:

„Anzahl der Eingabewerte“ oder „Größe der Eingabewerte“

oder . . .

Aufwand T(n) eines Algorithmus:

„Anzahl der Schritte“ oder „Anzahl bestimmter Operationen“

oder „Anzahl der benötigten Speicherplätze“ oder . . . , die der Algorithmus braucht, um ein Problem vom Umfang n zu lösen.

Um sinnvolle Aussagen über die Komplexität eines Algorithmus zu treffen, müssen n und T

(

n

)

mit Bedacht gewählt werden.

Beispiel: Im Algorithmus „Sortieren durch Einfügen“ war n die Anzahl der zu sortierenden Zahlen und T

(

n

)

die Anzahl der benötigten Vergleiche.

(48)

Wachstum von Funktionen

In der Regel stellt die Wachstumsrate der Laufzeit ein

einfaches und geeignetes Kriterium zur Messung der Effizienz eines Algorithmus dar.

Die Wachstumsrate erlaubt es uns, die relative

Leistungsfähigkeit alternativer Algorithmen zu vergleichen.

Die Wachstumsrate von Algorithmen wird meistens mithilfe der asymptotischen Notation angegeben.

Die asymptotische Notation lässt konstante Faktoren unberücksichtigt.

(49)

Asymptotische Notation

Es sei eine Funktion g

:

N

−→

R gegeben.

Θ(

g

) = {

f

:

N

−→

R

| ∃

c1

>

0

,

c2

>

0

,

n0

>

0

n

n0

.

0

c1g

(

n

) ≤

f

(

n

) ≤

c2g

(

n

) }

O

(

g

) = {

f

:

N

−→

R

| ∃

c

>

0

,

n0

>

0

n

n0

.

0

f

(

n

) ≤

cg

(

n

) } Ω(

g

) = {

f

:

N

−→

R

| ∃

c

>

0

,

n0

>

0

n

n0

.

0

cg

(

n

) ≤

f

(

n

) }

o

(

g

) = {

f

:

N

−→

R

| ∀

c

>

0

n0

>

0

n

n0

.

0

f

(

n

) <

cg

(

n

) }

ω(

g

) = {

f

:

N

−→

R

| ∀

c

>

0

n0

>

0

n

n0

.

0

cg

(

n

) <

f

(

n

) }

(50)

Gebräuchliche Wachstumsklassen

Θ(

1

)

konstantes Wachstum

Θ(

log

(

n

))

logarithmisches Wachstum

Θ(

n

)

lineares Wachstum

Θ(

n log

(

n

))

„fast lineares“ Wachstum

Θ(

n2

)

quadratisches Wachstum

Θ(

nk

)

polynomiales Wachstum

Θ(

2n

)

exponentielles Wachstum

O

(

n2

)

höchstens quadratisches Wachstum

Ω(

n

)

mindestens lineares Wachstum

Beispiel: Die Laufzeit beim „Sortieren durch Einfügen“ beträgt im günstigsten Fall

Θ(

n

)

und im ungünstigsten Falls

Θ(

n2

)

. Die

Laufzeit kann also durch

Ω(

n

)

nach unten und durch O

(

n2

)

nach oben abgeschätzt werden. Die Laufzeit wird auch durch O

(

n3

)

nach oben abgeschätzt! In der Praxis werden häufig möglichst

(51)

Graphen einiger Funktionen

1 log(n) nlog(n)

n

(52)

Graphen einiger Funktionen

n2 n3

2n

(53)

Exemplarische Werte

n

=

1 10 100 1000 10000

log2

(

n

) ≈

0 3 7 10 13

n log2

(

n

) ≈

0 33 664 9966 132877 n2

=

1 100 10000 1000000 100000000 2n

2 103 1030 10301 103010

Maximales n bei gegebener Zeit, Ann.: 1 Schritt benötigt 1

µ

s 1 Min. 1 Std. 1 Tag 1 Woche 1 Jahr n 6

·

107 3

.

6

·

109 8

.

6

·

1010 6

·

1011 3

·

1013 n2 7750 6

·

104 2

.

9

·

105 7

.

9

·

105 5

.

6

·

106

n3 391 1530 4420 8450 31600

2n 25 31 36 39 44

(54)

Asymptotische Notationen in Gleichungen

2n2

+

3n

+

1

= Θ(

n2

)

heißt 2n2

+

3n

+

1

∈ Θ(

n2

)

.

2n2

+

3n

+

1

=

2n2

+ Θ(

n

)

heißt: Es gibt eine Funktion f

(

n

) ∈ Θ(

n

)

mit 2n2

+

3n

+

1

=

2n2

+

f

(

n

)

.

2n2

+ Θ(

n

) = Θ(

n2

)

heißt: Für jede Funktion f

(

n

) ∈ Θ(

n

)

gibt es eine Funktion g

(

n

) ∈ Θ(

n2

)

mit 2n2

+

f

(

n

) =

g

(

n

)

.

In Gleichungsketten

2n2

+

3n

+

1

=

2n2

+ Θ(

n

) = Θ(

n2

)

werden die Gleichungen einzeln gelesen: Die erste Gleichung besagt, dass es eine Funktion f

(

n

) ∈ Θ(

n

)

mit

2n2

+

3n

+

1

=

2n2

+

f

(

n

)

gibt. Die zweite Gleichung sagt aus, dass es für jede Funktion g

(

n

) ∈ Θ(

n

)

eine Funktion h

(

n

) ∈ Θ(

n2

)

mit 2n2

+

g

(

n

) =

h

(

n

)

gibt. Aus der

(55)

Eigenschaften der asymptotischen Notation

Eine Funktion f

:

N

−→

R heißt asymptotisch positiv, wenn gilt:

n0

>

0

n

n0

.

f

(

n

) >

0

.

Für alle asymptotisch positiven Funktionen f

,

g

:

N

−→

R gelten die folgenden Aussagen.

Transitivität:

f

(

n

) = Θ(

g

(

n

)) ∧

g

(

n

) = Θ(

h

(

n

)) = ⇒

f

(

n

) = Θ(

h

(

n

)),

f

(

n

) =

O

(

g

(

n

)) ∧

g

(

n

) =

O

(

h

(

n

)) = ⇒

f

(

n

) =

O

(

h

(

n

)),

f

(

n

) = Ω(

g

(

n

)) ∧

g

(

n

) = Ω(

h

(

n

)) = ⇒

f

(

n

) = Ω(

h

(

n

)),

f

(

n

) =

o

(

g

(

n

)) ∧

g

(

n

) =

o

(

h

(

n

)) = ⇒

f

(

n

) =

o

(

h

(

n

)),

f

(

n

) = ω(

g

(

n

)) ∧

g

(

n

) = ω(

h

(

n

)) = ⇒

f

(

n

) = ω(

h

(

n

)).

(56)

Eigenschaften der asymptotischen Notation

Reflexivität:

f

(

n

) = Θ(

f

(

n

)),

f

(

n

) =

O

(

f

(

n

)),

f

(

n

) = Ω(

f

(

n

)).

Symmetrie:

f

(

n

) = Θ(

g

(

n

)) ⇐⇒

g

(

n

) = Θ(

f

(

n

)).

Austausch-Symmetrie:

f

(

n

) = Θ(

g

(

n

)) ⇐⇒

f

(

n

) =

O

(

g

(

n

)) ∧

f

(

n

) = Ω(

g

(

n

)),

f

(

n

) =

O

(

g

(

n

)) ⇐⇒

g

(

n

) = Ω(

f

(

n

)),

f

(

n

) =

o

(

g

(

n

)) ⇐⇒

g

(

n

) = ω(

f

(

n

)).

(57)

Beispiele

8

= Θ(

1

)

3n2

5n

+

8

= Θ(

n2

)

3n2

5n

+

8

=

O

(

n2

)

3n2

5n

+

8

= Ω(

n2

)

loga

(

n

) =

logb

(

n

)

logb

(

a

) Θ(

loga

(

n

)) = Θ(

logb

(

n

))

12 log10

(

n

) = Θ(

log

(

n

))

12 log2

(

n

) = Θ(

log

(

n

))

(58)

Laufzeit von imperativen Algorithmen

Folgende Annahmen werden zur Analyse der Laufzeit von imperativen Algorithmen getroffen:

Zuweisung: Die Laufzeit ist konstant.

Sequenz: Die Laufzeit ist die Summe der Laufzeiten der Einzelanweisungen.

Alternative: Die Laufzeit ist im ungünstigsten Fall die Laufzeit der Bedingungsauswertung plus dem Maximum der

Laufzeiten der Alternativen.

Iteration: Die Laufzeit errechnet sich aus dem Produkt der Laufzeit der inneren Anweisung und der Anzahl der

Iterationen. Hinzu kommt die Laufzeit für einen weiteren Test.

(59)

Sortieren durch Einfügen

Code Kosten Anzahl

insertionSort(A)

j ← 2;

c1 1

while j ≤ length(A) do

c2 n

key ← A[j];

c3 n

1

i ← j - 1;

c4 n

1

while i > 0 und A[i] > key

c5 Pn j=2 tj

do

A[i + 1] ← A[i];

c6 Pn

j=2

(

tj

1

)

i ← i - 1;

c7 Pn

j=2

(

tj

1

) od;

A[i + 1] ← key;

c8 n

1

j ← j + 1;

c9 n

1

od;

(60)

Abschätzung durch die O-Notation

Code Kosten

insertionSort(A)

j ← 2;

O

(

1

)

while j ≤ length(A) do

O

(

n

)

key ← A[j];

O

(

n

)

i ← j - 1;

O

(

n

)

while i > 0 und A[i] > key;

O

(

n2

) do

A[i + 1] ← A[i];

O

(

n2

)

i ← i - 1;

O

(

n2

)

od;

A[i + 1] ← key;

O

(

n

)

j ← j + 1;

O

(

n

)

od;

(61)

Korrektheit von Softwaresystemen

In vielen Situationen ist eine korrekte Funktionsweise eines

Softwaresystems von großer Bedeutung. Dies gilt insbesondere, wenn das System

sicherheitskritisch (z. B. Atomreaktor),

kommerziell kritisch (z. B. massenproduzierte Chips) oder

politisch kritisch (z. B. Militär) ist.

(62)

Korrektheit von Softwaresystemen

Es gibt mehrere Möglichkeiten, die Zuverlässigkeit von Softwaresystemen zu erhöhen:

Software Engineering:

Maßnahmen während des gesamten Softwareentwicklungsprozesses.

Programmierung:

Beispiel: Ausnahmebehandlung, Zusicherungen.

Validation:

Systematische Tests unter Einsatzbedingungen; Tests zeigen die Anwesenheit, aber nicht die Abwesenheit von Fehlern.

Verifikation:

Mathematischer Nachweis der Korrektheit von Algorithmen.

(63)

Korrektheit von Softwaresystemen

Um ein System zu verifizieren zu können benötigt man Methoden, Werkzeuge und Sprachen, zur

Modellierung von Systemen auf hoher Abstraktionsebene,

Spezifikation nachzuweisender Eigenschaften dieser Systeme (Terminierungsverhalten, berechnete

Funktionswerte, . . . ) und zur

Verifikation, d. h. zum formalen Beweis, dass ein

implementiertes System die spezifizierten Eigenschaften hat.

In diesem Abschnitt behandeln wir eine Möglichkeit zur

Spezifikation und Verifikation imperativer Algorithmen.

(64)

Hoaresche Logik

Es seien ein Algorithmus S sowie Bedingungen p und q gegeben.

Wir schreiben in diesem Fall

{p} S {q}

und nennen

p Vorbedingung,

q Nachbedingung und

(p,q) Spezifikation von S.

(65)

Hoaresche Logik

S heißt partiell-korrekt bezüglich der Spezifikation (p,q), wenn jede Ausführung von S, die in einem Zustand beginnt, der p erfüllt und die terminiert, zu einem Zustand führt, der q erfüllt.

| =

{p} S {q}

S wird total-korrekt bezüglich der Spezifikation (p,q) genannt, wenn jede Ausführung von S, die in einem Zustand beginnt, der p erfüllt, terminiert und zu einem Zustand führt, der q erfüllt.

(66)

Beispiele

| = {

true

}

x

1

{

x

=

1

}

| = {

x

=

1

}

x

x

+

1

{

x

=

2

}

| = {

y

=

a

}

x

y

{

x

=

a

y

=

a

}

| = {

x

=

a

y

=

b

}

z

x

;

x

y

;

y

z

{

x

=

b

y

=

a

}

| = {

false

}

x

1

{

x

=

42

}

| = {

true

} while

0

=

0

do

x

1

od {

x

=

23

}

| = {

x

>

0

} while

x , 0

do

x

x

1

od {

x

=

0

}

| = {

true

} while

x , 0

do

x

x

1

od {

x

=

0

}

| = {

true

} while

p

(

x

) do α od {¬

p

(

x

) }

| = {

x

+

y

=

a

} while

x , 0

do

x

x

1

;

y

y

+

1

od {

x

=

0

x

+

y

=

a

}

(67)

Hoarescher Kalkül

Zuweisung:

{

pxt

}

x

t

{

p

}

Sequenz:

{

p

}

S1

{

r

} , {

r

}

S2

{

q

} {

p

}

S1

;

S2

{

q

}

If:

{

p

e

}

S1

{

q

} , {

p

∧ ¬

e

}

S2

{

q

} {

p

}

if e then S1 else S2 fi

{

q

}

While:

{

p

e

}

S

{

p

}

{

p

}

while e do S od

{

p

∧ ¬

e

}

Anpassungsregel: p

q

, {

q

}

S

{

r

} ,

r

s

{

p

}

S

{

s

}

(68)

Hoarescher Kalkül

Der hoaresche Kalkül besteht aus dem Axiomenschema für die Zuweisung und Ableitungsregeln.

Falls {p} S {q} mithilfe des hoareschen Kalküls hergeleitet werden kann, schreibt man

{p} S {q}.

(69)

Schleifeninvariante

Die Bedingung p in der While-Regel heißt Schleifeninvariante.

{

p

e

}

S

{

p

}

{

p

}

while e do S od

{

p

∧ ¬

e

}

Eine Schleifeninvariante gilt vor jedem Schleifendurchlauf und damit auch nach jedem Schleifendurchlauf, speziell also nach Beendigung der Wiederholungsanweisung. Dann gilt zudem

¬

e.

(70)

Schleifeninvariante

Beispiel: Für den folgenden Algorithmus ist q

=

k

1 eine Schleifeninvariante.

{

q

=

0

k

=

1

}

while

k , n

+

1

do q ← q + 1;

k ← k + 1;

od;

{

q

=

n

}

Nach Ausführung der Schleife gilt: q

=

k

1

∧ ¬ (

k , n

+

1

)

Daraus folgt: q

=

n

(71)

Eigenschaften des Kalküls

Korrektheit:

⊢ {

p

}

S

{

q

} = ⇒ | = {

p

}

S

{

q

}

relative Vollständigkeit:

⊢ {

p

}

S

{

q

} ⇐ = | = {

p

}

S

{

q

}

(72)

Beispiel: Division mit Rest

Es sollen zwei ganze Zahlen x

,

y

Z mit x

0 und y

>

0 durcheinander dividiert werden.

Das Ergebnis der Division x

/

y ist der Quotient q und der Rest r mit x

=

qy

+

r

0

r

<

y

.

var x, y, q, r: int;

input x, y;

q ← 0;

r ← x;

while r >= y do r = r - y;

q = q + 1;

od;

output q, r;

(73)

Beispiel: Division mit Rest

Mithilfe des hoareschen Kalküls leiten wir jetzt den Ausdruck

{

x

0

}

S

{

x

=

qy

+

r

0

r

<

y

}

her, wobei S der obige imperative Algorithmus ist.

Wegen der Korrektheit des Kalküls können wir dann

schließen, dass der Algorithmus bezüglich der angegebenen Vor- und Nachbedingung partiell-korrekt ist.

Die Bedingung y

>

0 wird zum Nachweis der totalen Korrektheit benötigt.

(74)

Beispiel: Division mit Rest

Wir zeigen zuerst, dass

x

=

qy

+

r

0

r

eine Schleifeninvariante ist. Dies ergibt sich aus:

x

=

qy

+

r

0

r

r

y

= ⇒

x

= (

q

+

1

)

y

+ (

r

y

) ∧

0

r

y

(75)

Beispiel: Division mit Rest

Die Behauptung folgt dann aus den beiden Aussagen x

0

= ⇒

0

x

und

x

=

qy

+

r

0

r

∧ ¬ (

r

y

) = ⇒

x

=

qy

+

r

0

r

<

y

.

(76)

Beispiel: Division mit Rest

Da nach Voraussetzung y

>

0 gilt, durchläuft die Variable r eine monoton streng fallende Folge natürlicher Zahlen:

r0

,

r1

,

r2

, . . .

Da y konstant ist, wird deshalb für ein i die Bedingung ri

<

y

wahr. Das heißt, das Programm terminiert schließlich und ist deshalb total-korrekt bezüglich der angegebenen

Bedingungen.

Bemerkung: Es gibt auch Kalküle zum Nachweis der totalen Korrektheit.

(77)

Beispiel: Division mit Rest

Vor- und Nachbedingung, Schleifeninvariante als Annotation:

static private int remainder (int x, int y) {

assert x >= 0 && y > 0;

int q = 0, r = x;

assert x == q * y + r && 0 <= r;

while (r >= y) { r = r - y;

q = q + 1;

assert x == q * y + r && 0 <= r;

}

assert x == q * y + r && 0 <= r && r < y;

return r;

}

(78)

Wunschtraum

Es ist ein Algorithmus gesucht, der für beliebige p, S und q beweist oder widerlegt, dass

{p} S {q}

gilt.

Solch ein Algorithmus kann nicht existieren!

Dann existiert natürlich erst recht kein analoger Algorithmus für die totale Korrektheit.

(79)

Totale Korrektheit

Zum Nachweis der totalen Korrektheit muss zusätzlich zur partiellen Korrektheit die Terminierung gezeigt werden.

Um die Terminierung von Schleifen nachzuweisen, gibt es keine allgemeine Methode, oft funktioniert aber die

Vorgehensweise vom obigen Beispiel:

1. Man suche einen Ausdruck u, dessen Wert eine natürliche Zahl ist.

2. Man beweise, dass u bei jedem Schleifendurchlauf echt kleiner wird.

3. Da die natürlichen Zahlen wohlgeordnet sind, muss die Schleife terminieren.

(80)

Beispiel: Insertionsort

P

(

j

)

: Das Feld A

[

1

· · ·

j

]

ist eine Permutation des Ausgangsfeldes A

[

1

· · ·

j

]

.

S

(

j

)

: Das Feld A

[

1

· · ·

j

]

ist sortiert.

A

[

j

]

: Der ursprünglich in A

[

j

]

enthaltene Wert.

Pk

(

j

)

: Das Feld A

[

1

· · ·

k

1

,

k

+

1

· · ·

j

]

ist eine Permutation des Ausgangsfeldes A

[

1

· · ·

j

1

]

.

Sk

(

j

)

: Das Feld A

[

1

· · ·

k

1

,

k

+

1

· · ·

j

]

ist sortiert.

(81)

Beispiel: Insertionsort

{

n

1

}

{

P

(

1

) ∧

S

(

1

) ∧

n

1

} j ← 2;

{

P

(

j

1

) ∧

S

(

j

1

) ∧

2

j

n

+

1

} while j ≤ n do

key ← A[j];

i ← j - 1;

while i > 0 ∧ A[i] > key do A[i+1] ← A[i];

i ← i - 1;

od;

A[i+1] ← key;

j ← j + 1;

od;

{

P

(

j

1

) ∧

S

(

j

1

) ∧

2

j

n

+

1

j

>

n

}

{

P

(

n

) ∧

S

(

n

) }

(82)

Beispiel: Insertionsort

{

P

(

j

1

) ∧

S

(

j

1

) ∧

2

j

n

+

1

j

n

} {

P

j

(

j

) ∧

S

j

(

j

) ∧

0

j

1

} key ← A[j];

i ← j - 1;

{

Pi+1

(

j

) ∧

Si+1

(

j

) ∧

key

<

A

[

i

+

2

], . . . ,

A

[

j

]

0

i

j

1

key

=

A

[

j

] }

while i > 0 ∧ A[i] > key do A[i+1] ← A[i];

i ← i - 1;

od;

A[i+1] ← key;

j ← j + 1;

{

P

(

j

1

) ∧

S

(

j

1

) ∧

2

j

n

+

1

}

(83)

Konzepte imperativer Sprachen

Anweisungen

primitive Anweisungen: Zuweisung, Block, Prozeduraufruf

zusammengesetzte Anweisungen: Sequenz, Auswahl, Iteration

Ausdrücke

primitive Ausdrücke: Konstante, Variable, Funktionsaufruf

zusammengesetzte Ausdrücke: Operanden/Operatoren

Datentypen

primitive Datentypen: Wahrheitswerte, Zeichen, Zahlen, Aufzählung

zusammengesetzte Datentypen: Felder, Verbund, Vereinigung, Zeiger

(84)

Konzepte imperativer Sprachen

Abstraktion

Anweisung: Prozedurdeklaration

Ausdruck: Funktionsdeklaration

Datentyp: Typdeklaration

Weitere Konzepte

Ein- und Ausgabe

Ausnahmebehandlung

Bibliotheken

Parallele und verteilte Berechnungen

. . .

(85)

Blöcke

Motivation: Ein Block fasst mehrere Anweisungen und Deklarationen zu einer Einheit zusammen.

Syntax: Die Abgrenzung erfolgt durch syntaktische Elemente, wie z. B. Schlüsselwörter (

begin

,

end

), Klammerung oder

Einrückung. Blöcke dürfen überall statt einer einzelnen Anweisung stehen.

Kontrollfluss: Die Ausführung eines Blocks beginnt mit der ersten Anweisung und wird im Normalfall nach der letzten beendet. Es gibt auch Anweisungen zum Verlassen des Blocks:

break, return

.

Lokale Variablen: Innerhalb eines Blocks können Variablen deklariert werden, die nur in diesem Block verfügbar sind.

(86)

Blöcke

Globale Variable: Innerhalb eines Blocks sind alle

Bezeichner aus den umschließenden Blöcken sichtbar, soweit sie nicht von einer inneren Deklaration überdeckt werden.

Gültigkeitsbereich (scope): Ein Bezeichner ist innerhalb des Blocks gültig, in dem er deklariert wurde, nicht aber

außerhalb. Die Gültigkeit ist eine statische Eigenschaft, die sich aus dem Programmtext ableitet.

Lebensdauer: Ist eine dynamische Eigenschaft und

bezeichnet den Zeitraum der Verfügbarkeit eines Wertes

während der Laufzeit. Werte von überdeckten Variablen sind nach Beendigung des überdeckenden Blocks wieder

verfügbar.

(87)

Blöcke

Code Gültig Sichtbar

var x,y: int; | | | |

· · · do | | | |

var x: int; | | | | |

· · · | | | | |

· · · | | | | |

od; x | | x | |

· · · | | | |

y x y x

Einzelheiten lernen Sie in der Vorlesung „Programmieren“.

(88)

Prozeduren

Abstraktion: Eine Prozedur fasst mehrere Anweisungen zusammen und gibt ihnen einen Namen. Der Aufruf einer

Prozedur führt die Anweisungen aus und wirkt dabei wie eine elementare Anweisung. Über Parameter können die

Anweisungen gesteuert werden.

Wiederverwertung: Eine Prozedur wird nur einmal deklariert und kann beliebig oft verwendet werden.

Modularisierung: Die Implementation der Prozedur muss dem aufrufenden Programm nicht bekannt sein.

Veränderungen innerhalb der Prozedur erfordern keine Änderung des aufrufenden Programms.

(89)

Deklaration von Prozeduren

Deklaration:

proc P(

p1

, . . . ,

pn

) begin a; end

Name: P ist der Name der Prozedur und frei wählbar.

Parameter: p1

, . . . ,

pn sind die Parameter der Prozedur. Sie sind lokale Variablen mit eigenem Typ (formale Parameter), denen beim Aufruf der Prozedur Werte (aktuelle Parameter) zugewiesen werden. Die Gültigkeit der Parameter ist auf den Rumpf der Prozedur beschränkt.

Rumpf: a ist der Rumpf der Prozedur. Er enthält die auszuführenden Anweisungen.

(90)

Werte- und Referenzparameter

Werteparameter (call by value):

Der aktuelle Parameter kann ein Ausdruck oder speziell auch eine Variable sein.

Es wird der Wert des Ausdrucks übergeben.

Die Deklaration erfolgt (zum Beispiel) ohne das Präfix

var

.

(91)

Werte- und Referenzparameter

Referenzparameter (call by reference):

Der aktuelle Parameter muss eine Variable sein.

Es wird die Variable (Adresse) übergeben.

In der Deklaration werden Referenzparameter (zum Beispiel) mit

var

bezeichnet.

Es gibt weitere Arten der Parameterübergabe.

(92)

Beispiel

Aufgabe: Vertausche die Inhalte der Variablen x und y und addiere zu beiden den Wert a.

proc vertausche(a: int; var x, y: int) begin var z: int; // lokale Variable

z ← x;

x ← y;

y ← z;

x ← x + a;

y ← y + a;

end

Die Wirkung ist die „simultane“ Ersetzung.

(

x

,

y

) ← (

y

+

a

,

x

+

a

)

(93)

Beispiel

Aufrufen lässt sich die Prozedur in unterschiedlichen Umgebungen mit verschiedenen aktuellen Parametern:

vertausche(0, i, j);

vertausche(3, a[1], a[2]);

vertausche(a[3], a[1], a[2]);

vertausche(i, i, j);

(94)

Funktionen

Abstraktion: Funktionen sind Abstraktionen von Ausdrücken.

Der Aufruf einer Funktion berechnet einen Wert eines Typs

τ

.

Deklaration:

func F(

p1

, . . . ,

pn

): τ begin a; end

Auswertung: Der Rückgabewert der Funktion wird (zum

Beispiel) durch eine spezielle

return

-Anweisung angegeben.

Diese Anweisung verlässt den Funktionsblock mit sofortiger Wirkung. Häufig wird auch der letzte Term innerhalb einer

Funktion als Rückgabewert genommen, oder eine Zuweisung zum Funktionsnamen legt den Rückgabewert fest.

Seiteneffekt: Wenn der Aufruf einer Funktion den Wert einer globalen Variablen verändert, spricht man von einem

Seiteneffekt.

(95)

Beispiel

func EUKLID(x, y: int): int begin var a,b,r: int

a ← x;

b ← y;

while b # 0 do r ← a mod b;

a ← b;

b ← r;

od;

return a;

end

Für negative Werte der Parameter x und y hängt das Verhalten der Funktion von der Implementierung des mod-Operators ab.

Referenzen

ÄHNLICHE DOKUMENTE

- Bei einer Berechnung (rechte Seite der Zuweisung) mit verschiedenen Datentypen findet eine automatische Typumwandlung an den stärkeren Typen statt.. - Dabei gilt folgende

[r]

[r]

In diesem Fall muss zusätzlich die Stetigkeit von f in 0 gezeigt werden, da sonst nicht Dierenzierbarkeit folgt!)... Daher genügt es zu zeigen, dass f genau eine Nullstelle in (1,

Genauso wie f¨ ur eine Fl¨ ache kann man auch die Schwerpunktskoordinaten eines K¨ orpers berechnen. Wir tun das f¨ ur einen Drehk¨ orper, der durch Rotation des Fl¨ achenst¨

2. Hier sollte man sich ¨ uberlegen, wann zwei Funktionen als gleich angesehen werden k¨onnen. Es ist jedoch oft zweckm¨aßig, als Argumente auch Teilmengen des

• Variablen können/dürfen nur am Anfang eines Blocks deklariert werden. I Die innerhalb des Blocks deklarierten Variablen werden nach Blockende vergessen

ƒ Abhängige Variable (Regressand, response, outcome): Diese Variable soll aus der anderen Variable berechnet werden.. Methode der Kleinsten Quadrate Methode der