Kapitel 6 Algorithmen auf Arrays

Transcription

Kapitel 6 Algorithmen auf Arrays
Kapitel 6
Algorithmen auf Arrays
Wir behandeln jetzt drei Aufgabenstellungen mit zunehmendem Schwierigkeitsgrad, deren Lösung zu
Algorithmen auf Arrays führt:
• Suchen eines Wertes in einem Array,
• Lösung linearer Gleichungssysteme,
• Berechnung kürzester Wege in Graphen.
6.1
Suchen einer Komponente vorgegebenen Wertes
Hier handelt es sich um folgendes Basisproblem in Arrays:
Gegeben: Ein Array mit n ganzen Zahlen, eine ganze Zahl x.
Gesucht: Der Index i einer Komponente mit Wert x (falls vorhanden).
6.1.1
Sequentielle Suche
Eine einfache Lösung dieses Problems basiert auf folgender Idee. Durchlaufe die Komponenten des
Arrays sequentiell bis die aktuelle Komponente i den Wert x enthält. Gebe dann i aus. Ist x in keiner
Komponente, gebe −1 aus.
Diese Methode heißt sequentielle Suche. Sie erfordert im schlimmsten Fall n Vergleiche bis der Index
i gefunden ist, bzw. festgestellt wird, dass keine Komponente den Wert x hat.
Eine Java Methode hierfür ist:
Programm 6.1 sequentialSearch
Version vom 27. November 2004
115
116
KAPITEL 6. ALGORITHMEN AUF ARRAYS
/**
* searches for an element in an int array
* by sequential search
*
* @param a the int array
* @param x the int to search for
* @return the index of the first component containing <code>x</code>
*
or <code>-1</code> if <code>x</code> is not in the array
*/
public int sequentialSearch(int[] a, int x){
int k = 0;
// variable to hold the index or -1
while (k < a.length) {
if (a[k] == x) {
return k; // found x
}
k++;
}
return -1;
}
Beim i-ten Wiedereintritt in die for Schleife gilt die Zusicherung (Assertion)
i < n und a[0], . . . , a[i − 1] 6= x
Beim Austritt aus der Schleife gilt
a[i] = x oder i = a.length
Ist i = n, so ist x nicht im Array.
Diese Überlegungen zeigen die Korrektheit des Algorithmus. Zusicherungen bei Schleifen nennt man
auch Schleifeninvarianten. Sie spielen bei Korrektheitsbeweisen eine wichtige Rolle.
6.1.2
Binäre Suche
Liegt das Array a in sortierter Form vor (mit n = a.length), gilt also a[0] ≤ a[1] ≤ . . . ≤ a[n−1], so lässt
sich die Suche wesentlich beschleunigen durch die Verwendung der binären Suche oder Bisection.
Die Idee der binären Suche ist wie folgt:
– Wähle den mittleren Index i und vergleiche x und a[i].
– Ist a[i] = x, so ist der Index i gefunden.
– Ist a[i] < x, so kann x nur in der “linken Hälfte” von a, d. h. in a[0] . . . a[i − 1] sein. Wende das
Verfahren auf die linke Hälfte an.
6.1. SUCHEN EINER KOMPONENTE VORGEGEBENEN WERTES
117
– Ist a[i] > x, so kann x nur in der “rechten Hälfte” von a, d. h. in a[i + 1] . . . a[n − 1] sein. Wende
das Verfahren auf die rechte Hälfte an.
– Verfahre so weiter bis x gefunden, oder der noch zu durchsuchende Teil, in dem x sein könnte,
leer ist.
Als Beispiel betrachten wir a mit den Werten
3
5
6
8
10
12
13
16
0
1
2
3
4
5
6
7
und x = 10. Zuerst wird i in der Mitte gewählt, etwa i = 3. Dann ist a[i] = 8 < x = 10, und es wird in
der rechten Hälfte von a weitergesucht.
10
12
13
16
4
5
6
7
i wird wieder in der Mitte gewählt, etwa i = 5. Dann ist a[i] = 12 > x = 10, und es wird in der linken
Hälfte des verbleibenden Arrays weiter gesucht, also in
10
4
Die einzig verbleibende Wahl von i = 4 findet dann den gesuchten Wert. Wäre jetzt x 6= a[i], so stellte
man an dieser Stelle fest dass x nicht im Array enthalten sein kann.
Eine Java Methode hierfür ist:
Programm 6.2 binarySearch
/**
* searches for an element in an int array
* by binary search
*
* @param a the int array
* @param x the int to search for
* @return the index of the first component containing <code>x</code>
*
or <code>-1</code> if <code>x</code> is not in the array
*/
public int binarySearch(int[] a, int x){
int k;
// variable to hold the index or -1
int i, j; // lower and upper array bounds
i = 0;
// initial array bounds
j = a.length - 1; // initial array bounds
while (i <= j) {
k = (i + j) / 2; // choose k in the middle
if (a[k] == x) {
return k; // found x
118
KAPITEL 6. ALGORITHMEN AUF ARRAYS
}
if (x > a[k]) {
i = k + 1;
} else {
j = k - 1; // update bounds
}
}
return -1;
}
Vor jedem Eintritt in die Schleife gilt (falls x im Array ist) die Invariante
a[i] < x ≤ a[ j] und 0 ≤ i ≤ j ≤ n − 1.
Da in jedem Schleifendurchlauf i erhöht oder j erniedrigt wird, terminiert das Programm mit a[k] = x
oder i > j. Im ersten Fall wird der richtige Index k zurückgegeben. Im zweiten Fall (also bei i > j) ist
der Bereich, in dem x sein könnte, wegen der Schleifeninvariante leer und es wird −1 zurückgegeben.
Also arbeitet der Algorithmus korrekt. Bezüglich der nötigen Anzahl von Vergleichen ergibt sich:
Satz 6.1 (Aufwand der binären Suche) Für die Anzahl C(n) von Vergleichen bei der binären Suche
in einem Array mit n Komponenten gilt1 C(n) ≤ blog nc + 1.
Beweis: Der Beweis erfolgt durch vollständige Induktion nach n.
Induktionsanfang: Ist n = 1, so ist nur 1 Vergleich erforderlich. Also stimmt die Behauptung wegen
blog 1c + 1 = 0 + 1 = 1.
Induktionsvoraussetzung: Die Behauptung sei richtig für alle Arrays der Länge < n, n ≥ 2.
Schluss auf n: Nach dem ersten Vergleich mit a[k] muss nur in einer der Hälften weitergesucht werden.
Beide Hälften haben eine Länge ≤ bn/2c und erfordern daher nach Induktionsvoraussetzung
C(bn/2c) ≤ blog(bn/2c)c + 1
Vergleiche. Insgesamt sind dann 1 +C(bn/2c) Vergleiche erforderlich. Einsetzen ergibt:
C(n) ≤ 1 +C(bn/2c)
≤ 1 + blog(bn/2c)c + 1
≤ 1 + log(n/2) + 1
da bn/2c ≤ n/2
= 1 + log(n/2) + log 2
= 1 + log((n/2) · 2)
da log(a · b) = log a + log b
= 1 + log n
1
log n bedeutet hier (wie stets in der Informatik) den Logarithmus von n zur Basis 2. dae ist a nach oben gerundet, bac
ist a nach unten gerundet, also d5,2e = 6 und b5,2c = 5.
119
6.2. LINEARE GLEICHUNGSSYSTEME
Also ist C(n) ≤ 1 + log n. Da C(n) ganzzahlig ist, folgt C(n) ≤ b1 + log nc = blog nc + 1.
Es sind also wesentlich weniger Vergleiche nötig als bei der sequentiellen Suche. Bei n = 1.000.000 ≈
220 = 1048576 reichen C(n) = 20 Vergleiche bei der binären Suche aus, während die sequentielle
Suche 1.000.000 Vergleiche braucht.
6.2
Lineare Gleichungssysteme
6.2.1
Vektoren und Matrizen
Ein n-dimensionaler Vektor (genauer: Spaltenvektor) mit Elementen aus einer Menge I ist ein n-Tupel


x1
 x2 


x =  .  mit xi ∈ I, i = 1, . . . , n.
 .. 
xn
Die Menge aller solchen Vektoren wird mit Vn (I) bezeichnet. Meist ist I = R, d. h. Vn (I) = Vn (R).
Dafür schreibt man auch kurz Rn .
Eine m × n-Matrix mit Elementen aus einer Menge I

a11 a12 . . .
 a21 a22 . . .

 ..
 .
A=
 ai1 ai2 . . .

 ..
 .
am1 am2 . . .
ist eine Zusammenfassung

a1 j . . . a1n
a2 j . . . a2n 

..
.. 
.
. 

ai j . . . ain 

.. 
. 
am j . . . amn
von m · n Elementen aus I. Mm,n (I) bezeichnet die Menge aller m × n-Matrizen von Elementen aus I.
Meist ist I = R oder I ⊆ R.
Jede Spalte von A bildet einen Vektor A· j , den sogenannten Spaltenvektor ( j = 1, . . . , n); analog bildet
jede Zeile Ai· einen Zeilenvektor (i = 1, . . . , m).
Offensichtlich lassen sich Vektoren und Matrizen in Java durch 1- bzw. 2-dimensionale Arrays darstellen.
double[] vector = new double[n];
double[][] matrix = new double[m][n];
definieren entsprechende Array-Objekte.
Matrizen und Vektoren können wie folgt miteinander multipliziert werden:
120
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Multiplikation einer m × n-Matrix A mit einem n-Vektor x:


  a x + a x + ··· + a x
11 1
12 2
1n n
a11 . . . a1n
x1
a
x
+
a
x
+
·
·
·
+
a
 ..
..   ..  = 
21
1
22
2
2n xn

 .
.  .   . . . . . . . . . . . . . . . . . . . . . . . . . .
am1 . . . amn
xn
am1 x1 + am2 x2 + · · · + amn xn




Beispiele:
1. Multiplikation einer 2 × 3 Matrix mit einem 3-Vektor. Ergebnis ist ein 2-Vektor.


1
1 2 1
1
·
1
+
2
·
0
+
1
·
(−1)
0
· 0  =
=
2 0 −1
2 · 1 + 0 · 0 + −1 · (−1)
3
−1
2. Multiplikation einer 1 × 3 Matrix mit einem 3-Vektor. Ergebnis ist eine Zahl.


1
1 2 1 ·  0  = 1 · 1 + 2 · 0 + 1 · (−1) = 0
−1
Multiplikation einer m × p-Matrix A mit einer p × n-Matrix B:


a11 . . . a1p
 

 ..
..   b
c11 . . . c1n
 .

11 . . . b1 j . . . b1n
.


.
..
..  =  ..
.. 
 ai1 . . . aip  
.
.   .
. 

  ..
 ..
..  b
cm1 . . . cmn
 .
p1 . . . b p j . . . b pn
. 
am1 . . . amp
mit
ci j = ai1 b1 j + ai2 b2 j + · · · + aip b p j
p
=
∑ aik bk j
k=1
Die Formel zur Berechnung von ci j zeigt, dass ci j aus der i-ten Zeile von A und der j-ten Spalte von
B gebildet wird. Die illustriert Abbildung 6.1.
Beispiele:
1. Multiplikation einer 2 × 3 Matrix mit einer 3 × 3 Matrix. Ergebnis ist eine 2 × 3 Matrix.


2 1 3
1 2 1
 −1 0 0 
2 0 −1
1 1 0
1 · 2 + 2 · (−1) + 1 · 1 ,
1·1+2·0+1·1
, 1·3+0+0
=
2 · 2 + 0 · 0 + (−1) · 1 , 2 · 1 + 0 · 0 + (−1) · 1 , 2 · 3 + 0 + 0
1 2 3
=
3 1 6
mit
cij = ai1 b1j + ai2 b2j + · · · + aip bpj
=
p
'
aik bkj
k=1
Die LINEARE
Formel zurGLEICHUNGSSYSTEME
Berechnung von cij zeigt, daß cij aus der i-ten Zeile von A und der j-ten
6.2.
121
Spalte von B gebildet wird. Die illustriert Abbildung 6.1.
i
·
=
ij
j
Abbildung
Schemader
derMatrixmultiplikation.
Matrixmultiplikation.
Abbildung6.1:
6.1: Schema
Beispiele:
2. Multiplikation einer 1 × 3 Matrix mit einem 3-Vektor. Ergebnis ist eine Zahl.
 einer 3 × 3 Matrix. Ergebnis ist eine 2 × 3 Matrix.
 mit
1. Multiplikation einer 2 × 3 Matrix
2

  = 1 · 2 + 2 · 0 + 1 · 1 = 3
( 1, 2,
2 1 1 30
1 2 1


 −1 0 01
2 0 −1
1 1 0
3. Multiplikation eines 3 Vektors mit einer 1 × 3 Matrix. Ergebnis ist eine 3 × 3 Matrix. (
1 · 2 + 2 · (−1) + 1 · 1 ,
1·1+2·0+1·1
, 1·3+0+0

 

=  
2 2· 2 + 0 · 0 + (−1) · 1 ,2 ·21 · 12+
· 20 ·20· +
1 (−1) · 1 2 , 4 2 ·23 + 0 + 0
 0  1,(2, 1
=  0·1 0·2 0·1  =  0 0 0 
1 2 3
=
1·1 1·2 1·1
1 2 1
1
3 1 6
Diese Beispiele zeigen, dass die Matrixmultiplikation die Multiplikation von Matrizen mit Vektoren
als Spezialfall enthält. Stellt man sich die n Spalten der p × n-Matrix B als p-Spaltenvektoren B· j ,
j = 1, . . . , n, vor, so gilt
A · B = A · B·1 , . . . , B·n
= A · B·1 , . . . , A · B·n .
In Java lässt sich die Matrizenmultiplikation folgendermaßen realisieren:
double[][] a = new double[m][p];
double[][] b = new double[p][n];
double[][] c = new double[m][n];
int i, j, k;
// multiplication
for (i = 0; i < m; i++) {
// Array für Matrix A
// Array für Matrix B
// Array für Ergebnismatrix C
122
KAPITEL 6. ALGORITHMEN AUF ARRAYS
for (j = 0; j < n; j++) {
c[i][j] = 0;
for (k = 0; k < p; k++) {
c[i][j] += a[i][k] * b[k][j];
}//end for k
}//endfor j
}//endfor i
Wir bauen diese Multiplication jetzt in eine Klasse Matrix für Matrizen ein. Jedes Matrix Objekt
hat die drei (privaten) Felder matrix (für die Einträge), numberOfRows und numberOfColumns (für
Anzahl der Zeilen und Spalten). In den Methoden und Konstruktoren der Klasse Matrix verwenden
wir die Variable mit dem reservierten Namen this. Sie bezeichnet eine implizite Referenz auf das
Objekt, das gerade im Konstruktor konstruiert wird bzw. eine Methode für sich aufruft. Sind a, b
Objekte der Klasse Matrix, und ruft a die Methode a.multiply(b) für sich auf, so bezeichnet
also this im Rumpf von multiply() die Referenz auf das Objekt a. Diese implizite Referenz kann
nur innerhalb der Klasse durch die Variable this angesprochen werden. Mehr dazu findet sich in
Kapitel 7.3.
Programm 6.3 Matrix
import java.lang.IllegalArgumentException;
import java.lang.ArrayIndexOutOfBoundsException;
/**
* This is a class for rectangular double matrices
*/
public class Matrix {
/**
* the private representation of a matrix is a
* 2-dimensional double array
*/
private double[][] matrix;
/**
* number of rows
* is 0 for empty matrix
*/
private int numberOfRows;
/**
* number of columns
* is 0 for empty matrix
*/
private int numberOfColumns;
6.2. LINEARE GLEICHUNGSSYSTEME
123
/**
* default constructor, creates empty matrix
*/
public Matrix() {
matrix = null;
numberOfRows = 0;
numberOfColumns = 0;
}
/**
* constructor, constructs a matrix with prescribed number
* of rows and columns and all entries equal to zero
*
* Throws <code>IllegalArgumentException</code> if
* <code>m</code> or <code>n</code> is negative
*
* @param m the number of rows
* @param n thenumber of columns
*/
public Matrix(int m, int n) throws IllegalArgumentException {
if ((m <= 0) || (n <= 0)) {
throw new IllegalArgumentException("Anzahl der "
+ "Zeilen und Spalten muss positiv sein.");
}
// this is a reference to the matrix to be constructed
// we could also say matrix = new double[m][n] etc.,
// but we use this to make the reference clear
this.matrix = new double[m][n];
this.numberOfRows = m;
this.numberOfColumns = n;
}
/**
* @return the number of rows of this matrix
*/
public int getNumberOfRows() {
// here this refers to the object that applies the method
// i.e., in myMatrix.getNumberofRows(), this refers to myMatrix
// we could also say return numberOfRows,
// but we use this to make the reference clear
return this.numberOfRows;
}
/**
124
KAPITEL 6. ALGORITHMEN AUF ARRAYS
* @return the number of coulumns of this matrix
*/
public int getNumberOfColumns() {
return this.numberOfColumns;
}
/**
* returns the entry at a matrix position
*
* Throws <code>IllegalArgumentException</code> if
* <code>i</code> or <code>j</code> is negative
*
* Throws <code>ArrayIndexOutOfBoundsException</code> if
* <code>i</code> or <code>j</code> is too big
*
* @param i the row index
* @param j the colum index
* @return the entry in row i and column j
*/
public double getEntry(int i, int j) throws
IllegalArgumentException,
ArrayIndexOutOfBoundsException {
if ((i < 0) || (j < 0)) {
throw new IllegalArgumentException("Negative Indizes");
}
if (this.matrix == null) {
throw new IllegalArgumentException("Matrix ist leer.");
}
if ((i > this.getNumberOfRows()) ||
(j > this.getNumberOfColumns())) {
throw new ArrayIndexOutOfBoundsException(
"Index zu groß.");
}
return this.matrix[i][j];
}
/**
* sets the entry at position (i,j) of this matrix
*
* Throws <code>IllegalArgumentException</code> if
* <code>i</code> or <code>j</code> is negative or
* if this matrix is empty
*
* Throws <code>ArrayIndexOutOfBoundsException</code> if
6.2. LINEARE GLEICHUNGSSYSTEME
125
* <code>i</code> or <code>j</code> is too big
*
* @param i the row index
* @param j the colum index
* @param x the value of the entry
*/
public void setEntry(int i, int j, double x) throws
IllegalArgumentException,
ArrayIndexOutOfBoundsException {
if ((i < 0) || (j < 0)) {
throw new IllegalArgumentException("Negative Indizes");
}
if (this.matrix == null) {
throw new IllegalArgumentException("Matrix ist leer.");
}
if ((i > this.getNumberOfRows()) ||
(j > this.getNumberOfColumns())) {
throw new ArrayIndexOutOfBoundsException(
"Index zu groß.");
}
this.matrix[i][j] = x;
}
/**
* multiplies this matrix with another matrix
*
* Throws <code>IllegalArgumentException</code> if
* dimensions are not compatible or
* if this matrix is empty
*
* @param m the other matrix
*/
public void multiply(Matrix m) throws IllegalArgumentException {
if (this.getNumberOfColumns() != m.getNumberOfRows()) {
throw new IllegalArgumentException("Falsche "
+ "Dimensionen");
}
if (this.matrix == null) {
throw new IllegalArgumentException("Multiplikation "
+ "mit leerer Matrix.");
}
// array for result
double[][] c = new double[this.getNumberOfRows()]
126
KAPITEL 6. ALGORITHMEN AUF ARRAYS
[m.getNumberOfColumns()];
int i, j, k;
// multiplication
for (i = 0; i < this.getNumberOfRows(); i++) {
for (j = 0; j < m.getNumberOfColumns(); j++) {
c[i][j] = 0;
for (k = 0; k <
this.getNumberOfColumns(); k++){
c[i][j] += this.matrix[i][k]
* m.getEntry(i, j);
}//end for k
}//endfor j
}//endfor i
// store the result in this matrix,
// update this column number
this.matrix = c;
this.numberOfColumns = m.getNumberOfColumns();
}
}
Wie üblich werden jetzt Matrizen mit
Matrix a = new Matrix(10, 15);
erzeugt. Mit Anweisungen der Form
a.setEntry(2, 1, 3.14);
werden Komponenten gesetzt, und zwei Matrizen a und b werden mit
a.multiply(b);
miteinander multipliziert, wobei dann das Ergebnis in der Matrix a steht.
Die algebraische Struktur der Menge der Matrizen (Rechenstruktur!) wird in der Linearen Algebra
ausführlich behandelt.
Wir werden im weiteren die Kurzschreibweisen
A · x bzw. Ax für die Matrix-Vektor Multiplikation, und
A · B bzw. AB für die Matrizenmultiplikation
verwenden (geeignete “Abmessungen” vorausgesetzt).
127
6.2. LINEARE GLEICHUNGSSYSTEME
6.2.2
Ein Produktionsmodell
Ein Betrieb verarbeitet an Produktionsstätte P1 die Rohstoffe R1 . . . , Rn zu Zwischenprodukten Z1 , . . . Z p ,
die dann an der Produktionsstätte P2 zu Endprodukten E1 , . . . , Em weiterverarbeitet werden. Der Bedarf an Rohstoffen für die Zwischenprodukte ist durch Tabelle 6.1 gegeben, d. h. zur Produktion einer
Einheit von Zi werden bi j Einheiten von R j benötigt, j = 1, . . . , n.
Tabelle 6.1: Rohstoffbedarf für Zwischenprodukte.
R1
b11
R2
b12
...
...
Rn
b1n
Zi
..
.
bi1
bi2
...
bin
Zp
b p1
b p2
...
b pn
Z1
..
.
Ebenso ist der Bedarf an Zwischenprodukten für die Endprodukte durch Tabelle 6.2 gegeben.
Tabelle 6.2: Zwischenproduktbedarf für Endprodukte.
Z1
a11
Z2
a12
...
...
Zp
a1p
Ei
..
.
ai1
ai2
...
aip
Em
am1
am2
...
amp
E1
..
.
Diese beiden Tabellen definieren Matrizen A und B gemäß
A := (ai j ) i=1,...,m ,
j=1,...,p
B := (bi j ) i=1,...,p
j=1,...,n
Dann ergibt sich der Bedarf ci j an Rohstoff R j für die Produktion einer Einheit von Endprodukt Ei zu
ci j = ai1 b1 j + ai2 b2 j + · · · + aip b p j .
Bezeichnet man die Matrix der ci j als C, also C := (ci j ) i=1,...,m , so ergibt sich C also durch Multiplikaj=1,...,n
tion der Matrizen A und B, d. h.
C = A·B .
C heißt Rohstoffbedarfsmatrix.
128
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Diese Daten sollen jetzt zum Einkauf der benötigten Rohstoffe für eine vorgegebene Produktion von
yi Einheiten von Endprodukt Ei (i = 1, . . . , m) genutzt werden.
Gegeben ist also der Produktionsvektor



y=

y1
y2
..
.





ym
Die benötigte Menge z j des Zwischenprodukts Z j an Produktionsstätte P1 ergibt sich zu
z j = y1 · a1 j + y2 · a2 j + · · · + ym · am j



a1 j
 a2 j 



= (y1 , . . . , ym )  .  = (a1 j , a2 j , . . . , am j ) 
 .. 
am j

y1
.. 
. 
ym
Bezeichnet


z1


z =  ... 
zp
den Bedarfsvektor an Zwischenprodukten, so gilt also
z = AT y . 2
Hieraus ergibt sich der Bedarf x j an Rohstoff R j wie folgt:
x j = z1 · b1 j + z2 b2 j + · · · + z p · b p j



b1 j



= (z1 , . . . , z p )  ...  = (b1 j , . . . , b p j ) 
bp j

z1
..  = BT · z .
·j
. 
zp
Also gilt für den Bedarfsvektor x


x1


x =  ...  = BT · z = BT AT y .
xn
2 AT ist die zu A transponierte Matrix AT = (aT ) mit aT = a . Sie ergibt sich aus A durch Spiegeln an der Hauptdiagoji
ij
ij
nalen.
129
6.2. LINEARE GLEICHUNGSSYSTEME
Dieser Bedarfsvektor lässt sich natürlich auch direkt über die Rohstoffbedarfsmatrix C = A · B ermitteln:
xj =
Bedarf an R j für alle Ei zusammen bei Produktionsvektor y
= y1 c1 j + y2 c2 j + · · · + ym cm j



c1 j
 c2 j 




= (y1 , y2 , . . . , ym )  .  = (c1 j , . . . , cm j ) 
 .. 

y1
y2
..
.
cm j
ym
=
C·Tj · y





.
Also ist
x = CT · y = BT · AT · y .
3
Jetzt betrachten wir die umgekehrte Fragestellung. Es sind nur bestimmte Mengen von Rohstoffen
verfügbar, die durch einen Rohstoffvektor


r1


r =  ... 
rn
beschrieben werden. Gesucht ist ein Produktionsvektor


y1


y =  ...  ,
ym
der die bezüglich r mögliche Produktion angibt.
Die obige Matrizengleichung liefert CT y = r, d. h. das lineare Gleichungssystem
c11 y1 + c21 y2 + . . . + cm1 ym = r1
c12 y1 + c22 y2 + . . . + cm2 ym = r2
...
c1n y1 + c2n y2 + . . . + cnm ym = rn
mit n Gleichungen und m Unbekannten.4
3
Es gilt allgemein bezüglich der Transponierung (A · B)T = BT AT (Übung). Daher hätte man die obige Gleichung auch
aus dieser Regel direkt ableiten können.
4 Es handelt sich hier um ein spezielles lineares Gleichungssystem in nicht üblicher Schreibweise, die daraus resultiert,
dass die transponierte Matrix CT verwendet wird.
130
6.2.3
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Das Gaußsche Eliminationsverfahren
Wir betrachten jetzt lineare Gleichungssysteme in allgemeiner Form (und Schreibweise). Das System
a11 x1 + a12 x2 + . . . + a1n xn = b1
a21 x1 + a22 x2 + . . . + a2n xn = b2
..............................
am1 x1 + am2 x2 + . . . + amn xn = bm
heißt lineares Gleichungssystem mit m Gleichungen in den n Variablen x1 , . . . , xn . In Matrizenschreibweise schreibt sich dieses System kurz als
Ax = b
mit

a11
. . . a1n

,
A=
am1 . . . amn


x1


x =  ...  ,
xn


b1


b =  ...  .
bm
A wird Koeffizientenmatrix des Gleichungssystems genannt, und b heißt rechte Seite des Gleichungssystems.
Ein Vektor

x1


x =  ... 
xn

mit Ax = b heißt Lösung des linearen Gleichungssystems. Die Menge aller Lösungen wird Lösungsraum
des Gleichungssystem genannt.
Wir werden jetzt einen einfachen Algorithmus zur Lösung linearer Gleichungssysteme entwickeln,
das sogenannte Gaußsche Eliminationsverfahren. Dazu benötigen wir zunächst einige Hilfsaussagen
über Umformungen des Gleichungssystems, die den Lösungsraum unverändert lassen.
Lemma 6.1 Die Addition bzw. Subtraktion des Vielfachen einer Gleichung zu einer anderen ändert
den Lösungsraum nicht.
Beweis: Addiere das c-fache der k-ten Gleichung zur i-ten. Es entsteht das Gleichungssystem Āx = b̄:
a11 x1
+...+
a1n xn
=
b1
.............................................
(ai1 + c · ak1 )x1 + . . . + (ain + c · akn )xn = bi + c · bk
.............................................
a11 x1
+...+
a1n xn
=
bn
Sei x̄ Lösung von Āx = b̄. Dann erfüllt x̄ auch alle Gleichungen von Ax = b (außer evtl. der i-ten), da
diese in Āx = b̄ unverändert sind.
131
6.2. LINEARE GLEICHUNGSSYSTEME
Wir zeigen nun, dass x̄ auch die i-te Gleichung von Ax = b erfüllt und daher auch eine Lösung von
Ax = b ist.
Da x̄ eine Lösung von Āx = b̄ ist, gilt (die i-te Gleichung hingeschrieben):
(ai1 + cak1 )x̄1 + · · · + (ain + c · akn )x̄n = bi + c · bk .
Die linke Seite ist gleich
(ai1 x̄1 + · · · + ain x̄n ) + c(ak1 x̄1 + · · · + akn x̄n ) .
Nun ist (ak1 x̄1 + · · · + akn x̄n ) = bk , da x̄ die k-te Gleichung von Ax = b löst. Also folgt:
bi + c · bk = ai1 x̄1 + · · · + ain x̄n + c · bk
oder
bi = ai1 x̄1 + · · · + ain x̄n ,
d. h. x̄ löst auch die i-te Gleichung von Ax = b. Also ist jede Lösung von Ax̄ = b̄ auch eine Lösung
von Ax = b.
Die Umkehrung folgt entsprechend.
Lemma 6.2 Die Multiplikation einer Gleichung mit einer Konstanten 6= 0 ändert den Lösungsraum
nicht.
Beweis: Der Beweis erfolgt analog zu Lemma 6.1.
Die Operationen “Addition des Vielfachen einer Zeile zu einer anderen” und “Multiplikation einer
Zeile mit einer Konstanten” heißen auch elementare Umformungen.
Lemma 6.3 Das Vertauschen von Zeilen (Umnummerieren der Gleichungen) und Spalten (Umnummerieren der Variablen) ändern den Lösungsraum (bis auf Umnummerieren der Koordinaten) nicht.
Beweis: Klar.
Satz 6.2 Das Gleichungssystem Ax = b kann durch elementare Umformungen und gegebenenfalls
.
Zeilen und Spaltenvertauschungen so umgeformt werden, dass die erweiterte Matrix (A .. b) die in
Abbildung 6.2 dargestellte Dreiecksform hat, d. h.
āii 6= 0 für i = 1, . . . , k,
āi j = 0 für i > 1 und i < j .
132
KAPITEL 6. ALGORITHMEN AUF ARRAYS















0
0
...
0
0
..
. b̄1
..
..
.
.
..
.
..
. b̄k
..
. b̄k+1
0
0
...
0
0
..
.
ā11
0
..
.
..
.
..
.
āi j
..
.
ākk















b̄m
Abbildung 6.2: Dreiecksform der erweiterten Matrix.
Bevor wir Satz 6.2 beweisen, rechnen wir zunächst einige Beispiele durch.
Beispiel 6.1
1. m ≤ n
x1 + x2 − x3 = 2
x1
+ x3 = −1
.
Die erweiterte Matrix (A .. b) hat die Form


..
 1 1 −1 . 2  .
.
1 0 1 .. −1
Die Subtraktion der ersten Zeile von der zweiten ergibt


..
 1 1 −1 . 2  ,
.
0 −1 2 .. −3
wodurch die Dreiecksform erreicht ist.
2. m > n
x1 + x2 = 1
x1 − x2 = 0
x1 + 2x2 = 2
.
Die erweiterte Matrix (A .. b) hat die Form

..
 1 1 . 1

.
 1 −1 .. 0

.
1 2 .. 2



.

6.2. LINEARE GLEICHUNGSSYSTEME
133
Die Subtraktion der ersten Zeile von der zweiten und der dritten ergibt


..
 1 1 . 1 


.
 0 −2 .. −1  ,


..
0 1 . 1
und die Addition des 12 -fachen der zweiten Zeile zu der dritten


..
 1 1 . 1 


.
 0 −2 .. −1  ,


.
0 0 .. 1/2
wodurch die Dreiecksform erreicht ist.
Beweis: (von Satz 6.2) Der Beweis erfolgt durch die Angabe eines Algorithmus (im Struktogramm)
und den Nachweis seiner Korrektheit. Das Struktogramm ist in Abbildung 6.3 angegeben. Die Korrektheit des Algorithmus basiert auf der folgenden Schleifeninvariante:
Die erweiterte Matrix hat bei jedem Eintritt in die Schleife die in Abbildung 6.4 dargestellte
Form.
Dies folgt direkt aus der Tatsache, dass die Addition in den ersten Stellen 1, . . . , i − 1 nur 0 + 0 ergibt
und die i-te Spalte beim i-ten Durchlauf unterhalb von Zeile i zu Null gemacht wird.
Also verlässt man die Schleife mit k = min{n, m} oder k < min{n, m} und à = 0.
Der obige Algorithmus wird auch als Gaußsches Eliminationsverfahren zur Lösung linearer Gleichungssysteme bezeichnet. Seine Nutzung zur Lösung linearer Gleichungssysteme beruht auf dem
folgenden Satz:
.
Satz 6.3 (Lösungskriterien für lineare Gleichungssysteme) Sei Ax = b gegeben, und seien (Ā .. b̄)
und k die durch das Gaußsche Eliminationsverfahren gelieferten Größen. Dann gilt:
a) Ax = b hat genau dann (mindestens) eine Lösung wenn b̄i = 0 für alle i > k (falls k < m).
b) In diesem Falle erhält man alle Lösungen, indem man (für k < n) xk+1 , xk+2 , . . . , xn beliebig
wählt und die anderen Variablen x1 , . . . , xk aus der Dreiecksform
ā11 x1 + ā12 x2 + · · · + ā1k xk = b̄1 − (ā1,k+1 xk+1 + · · · + ā1,n xn )
ā22 x2 + · · · + ā2k xk = b̄2 − (ā2,k+1 xk+1 + · · · + ā2,n xn )
..
.
ākk xk = b̄k − (āk,k+1 xk+1 + · · · + āk,n xn )
durch sukzessives Ausrechnen von xk , xk−1 , . . . , x1 “von unten nach oben” erhält.
127
6.2. LINEARE GLEICHUNGSSYSTEME
134
KAPITEL 6. ALGORITHMEN AUF ARRAYS
.
.
Setze (Ã ... b̃) := (A. .. b) und i := 1
Setze (Ã .. b̃) := (A .. b) und i := 1
à #= 0 (Nullmatrix) and i ≤ min{n, m}
à 6= 0 (Nullmatrix) and i ≤ min{n, m}
Ändere à durch Zeilen- und Spaltenpermutationen bis ãii #= 0
Ändere
à durch
Zeilenund Spaltenpermutationen
bisinãiiZeile
6= 0 gilt.
gilt.
{Das
gewählte
Element
heißt Pivotelement
i.}
{Das gewählte Element heißt Pivotelement in Zeile i.}
Addiere zu allen Zeilen ! mit ã!i #= 0 ein geeignetes Vielfaches,
Addiere
zu+allen
` mit ã`i 6= 0 ein
so
daß ã!i
c · ãZeilen
ist cgeeignetes
=ã − ãã!i
. Vielfaches,
Dies nennt
ii = 0 gilt. {Dann
ii
`i
so
dass
ã
+
c
·
ã
=
0
gilt.
{Dann
ist
c
=
−
.
Dies
nennt man
ii
`i
man Pivotoperation oder Pivotisierung.} ãii
Pivotoperation oder Pivotisierung.}
i := i + 1
i := i + 1
!!
! !!
hh!
hh!
hhh!!!!!
i ≤ min{n, m}
hhhh !!!
i ≤ min{n, m}
hhh !!!!
hhhh !!!
hhh !!!!
true
hhhh !!
true
hhh !!!
hhh
.
Lasse von (Ã.. .. b̃) die Zeile und Spalte mit Index i − 1 weg.
Lasse von (Ã . b̃) die Zeile und Spalte mit Index i − 1 weg.
.. Bezeich..
Bezeichne
die
entstehende
Matrix
wieder
mit
(
Ã
. b̃).
ne die entstehende Matrix wieder mit (Ã . b̃).
Abbildung
6.3:
Gauß-Eliminationsverfahrens.
Abbildung
6.3:Struktogramm
Struktogramm des
des Gauß-Eliminationsverfahrens.
#= 0
#= 0.
..
#= 0
ãii
0
Ã
b̃
Abbildung
6.4:
imBeweis
Beweiszuzu
Satz
Abbildung
6.4:Die
DieSchleifeninvariante
Schleifeninvariante im
Satz
6.2.6.2.
135
6.2. LINEARE GLEICHUNGSSYSTEME
c) Dies sind bereits alle Lösungen von Ax = b.
Man nennt k auch den Rang der Matrix A. Aussage a) bedeutet dann, dass Ax = b genau dann lösbar
.
ist, wenn der Rang von A gleich dem Rang der erweiterten Matrix (A .. b) ist.
Aussage b) und c) bedeuten, dass der Lösungsraum ein (n − k)-dimensionaler affiner Raum ist. Dies
bedeutet grob gesagt folgendes:
– Der Lösungsraum des zugehörigen homogenen Systems Ax = 0 ist ein (n − k)-dimensionaler
Vektorraum L.
– Man erhält alle Lösungen x des inhomogenen Systems Ax = b als Kombination x = x̃ + y, wobei
x̃ irgendeine fest gewählte Lösung von Ax = b ist, und y alle Lösungen von Ax = 0 durchläuft.
Genauer werden diese Zusammenhänge in der Linearen Algebra in einem allgemeineren Rahmen
untersucht.
Beispiel 6.2 (Fortsetzung von Beispiel 6.1)
1. Das Gleichungssystem ist lösbar, da k = 2 = m. Man erhält
.
x1 x2 x3 ..
b
...................
.
.
1
1 −1 ..
2
.
0 −1
2 .. −3
Man kann x3 beliebig wählen (z. B. x3 = c) und erhält
x1 + x2 = 2 + c
−x2 = −3 − 2c
Hieraus ergibt sich x2 = 3 + 2c, und dann aus der ersten Gleichung
x1 = −x2 + 2 + c
= −(3 + 2c) + 2 + c

= −1 − c .

−1 − c
Also ist der Lösungsraum { 3 + 2c  | c ∈ R1 beliebig } ein 1-dimensionaler affiner Raum.
c
2. Das Gleichungssystem ist nicht lösbar, da k = 2 < m und b̄3 =
1
2
6= 0.
136
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Beweis: (von Satz 6.3)
zu a)
1. Die Bedingung b̄i = 0 für i > k ist notwendig für die Lösbarkeit:
Der Gauß-Algorithmus lässt wegen Lemma 6.1–6.3 den Lösungsraum unverändert. Ist b̄i 6= 0
für ein i > k, so lautet die i-te Gleichung von Āx = b̄
0 · x1 + · · · + 0 · xn = bi 6= 0
Dies ist für kein x erfüllt.
2. Die Bedingung b̄i = 0 für alle i > k ist auch hinreichend für die Lösbarkeit:
Man kann aufgrund der Bedingung b) eine Lösung wie folgt ausrechnen: Für beliebig, aber fest
gewählte Werte von xk+1 , . . . , xn ist
d` := ā`,k+1 xk+1 + ā`,k+2 xk+2 · · · + ā`,n xn ,
` = 1, . . . , m, eine Konstante. Dann ergibt sich (wegen ākk 6= 0)
xk =
1
[b̄k − (āk,k+1 xk+1 + · · · + āk,n x̄n )] .
ākk
{z
}
|
Konstante dk
Sind xk , xk−1 , . . . , x`+1 bereits berechnet, so ergibt sich x` (wegen ā`` 6= 0) als
x` =
1
[b̄` − (ā`,`+1 x`+1 + · · · + ā`,k xk ) − d` ] .
ā``
Also folgt a) und auch b).
zu c) Zeige: Jede Lösung lässt sich gemäß b) gewinnen.
Sei


x̄1


x̄ =  ... 
x̄n
eine Lösung. Wähle dann in b) xk+1 := x̄k+1 , . . . , xn := x̄n . Dann sind (wie die Formel oben für xl zeigt)
x1 , x2 , . . . , xk eindeutig bestimmt durch die Wahl von x j = x̄ j ( j = k + 1, . . . , n). Also muss xi = x̄i für
i = 1, . . . , k sein.
6.2.4
Wahl des Pivotelements
Im Gauß-Algorithmus wurde die Auswahl der Pivotelemente offen—d. h. beliebig—gelassen. Aus
numerischen Gründen sind jedoch bestimmte Pivotelemente vorzuziehen.
137
6.2. LINEARE GLEICHUNGSSYSTEME
Beispiel 6.3 (Ungünstige Wahl des Pivotelementes) Das lineare Gleichungssystem
0, 000100x1 + x2 = 1
x1 + x2 = 2
soll mit 3-stelliger Arithmetik5 gelöst werden.
Die Gauß-Elimination ergibt (Zeile 1 mit 10000 multiplizieren und von Zeile 2 abziehen):
x1 (1 − 10000 · 0, 000100) +x2 (1 − 10000) = 2| − 10000
{z }
{z
}
|
{z
}
|
=:a
=:b
=:c
In 3-stelliger Arithmetik ergeben sich folgende Werte für a, b, c (; steht für das Runden):
a
=
1 − 10000 · 0, 000100 = 1 − 1 = 0
b
=
1 − 10000 = −9999 = −0, 9999 · 104
; −0, 100 · 105 = −10000
c
=
2 − 10000 = −9998 = −0, 9998 · 104
; −0, 100 · 105 = −10000
Das transformierte Gleichungssystem in 3-stelliger Arithmetik lautet dann
0, 000100x1 + x2 = 1
−10000x2 = −10000
Hieraus ergibt sich die angenäherte Lösung
x1 = 0, 000
x2 = 1, 000 .
Die exakte Lösung ist jedoch
x1 = 1, 00010
x2 = 0, 99990 ,
was bedeutet, das die angenäherte Lösung für x1 auf Basis der 3-stelligen Arithmetik unvertretbar
schlecht ist.
Die Ursache liegt darin, dass a11 = 0, 000100 ein zu kleines Pivotelement im Verhältnis zu a21 = 1
ist. Will man dies vermeiden, so kann man (z. B. durch Zeilenvertauschung) nach einem größeren
Pivotelement suchen. Im Beispiel ergibt die Zeilenvertauschung
x1 + x2 = 2
0, 000100x1 + x2 = 1
5
Dies bedeutet, dass nur 3 Nachkommastellen in der normalisierten Gleitkommadarstellung (vgl. Kapitel 12) einer Zahl
dargestellt werden können. Die darstellbaren Zahlen haben also die Form 0, x1 x2 x3 · 10e mit xi ∈ {0, 1, . . . , 9}, x1 6= 0 und e
ganzzahlig. Bei mehr Nachkommastellen wird entsprechend gerundet.
138
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Die Gauß-Elimination ergibt
x1 (0, 000100 − 0, 000100) +x2 (1 − 0, 000100) = 1 − 0, 000200
{z
}
|
{z
}
|
{z
} |
=:a
=:c
=:b
mit folgenden Werten für a, b, c in 3-stelliger Arithmetik:
a
=
0, 000100 − 0, 000100 = 0
b
=
1 − 0, 000100 = 0, 9999 = 0, 9999 · 100
; 0, 100 · 101 = 1
=
c
1 − 0, 000200 = 0, 9998 = −0, 9998 · 100
; 0, 100 · 101 = 1
Das transformierte Gleichungssystem in 3-stelliger Arithmetik lautet dann
x1 + x2 = 2
x2 = 1 ,
woraus sich die angenäherte Lösung x1 = x2 = 1 ergibt, die die exakte Lösung für eine 3-stellige
Arithmetik sehr gut approximiert.
Dies Beispiel zeigt, dass man das Gaußsche Eliminationsverfahren nicht einfach “naiv” anwenden
darf, sondern sich Gedanken über die numerische Genauigkeit machen muss.
Dies geschieht durch die Suche nach geeigneten Pivotelementen, die sogenannte Pivotstrategie. Es
gibt zwei Standard-Pivotstrategien, die partielle und die totale Pivotsuche.
Bei der partiellen Pivotsuche sucht man das Pivotelement in der jeweiligen Spalte nach folgender
Regel (vgl. Abbildung 6.5):
Permutiere in Schritt i diejenige Zeile (unter den Zeilen ` = i, . . . , m) mit größtem absolutem
Wert |a`i | an die i-te Stelle.
20
KAPITEL 1. ALGORITHMEN AUF ARRAYS
i
i
!
..
.
Vertauschung
|a!i | = max |aji |
j=i,...,m
Abbildung 1.5: Schema der partiellen Pivotsuche.
Abbildung 6.5: Schema der partiellen Pivotsuche.
Vergleiche, d. h. quadratisch in m viele.
Bei der totalen Pivotsuche sucht man das Pivotelement in der gesamten verbleibenden Restmatrix nach folgender Regel (vgl. Abbildung ??):
Permutiere in Schritt i die verbleibenden Zeilen und Spalten bis an Stelle (i, i) der
Eintrag mit dem größten Absolutbetrag steht.
i
20
6.2. LINEARE GLEICHUNGSSYSTEME
KAPITEL 1. ALGORITHMEN AUF ARRAYS
139
Hierdurch ergibt sich in Schritt i deri Gauß-Elimination ein zusätzlicher Suchaufwand von m − i Vergleichen, also insgesamt über alle Schritte
m
i
!
∑ (m −... i)
= (m − 1) + (m − 2) + · · · + 1
i=1
=
Vertauschung
|a!i | = max |aji |
m(m − 1)
j=i,...,m
2
1.5: Schema der partiellen Pivotsuche.
Vergleiche, d. h.Abbildung
quadratisch
in m viele.
Bei der totalen Pivotsuche sucht man das Pivotelement in der gesamten verbleibenden Restmatrix
nach folgender Regel (vgl. Abbildung 6.6):
Vergleiche, d. h. quadratisch in m viele.
Bei der totalen Pivotsuche sucht man das Pivotelement in der gesamten verbleibenden Restmatrix nach folgender Regel (vgl. Abbildung ??):
Permutiere in Schritt i die verbleibenden Zeilen und Spalten bis an Stelle (i, i) der Eintrag
Permutiere
in Schritt
die verbleibenden
Zeilen und Spalten bis an Stelle (i, i) der
mit dem
größteni Absolutbetrag
steht.
Eintrag mit dem größten Absolutbetrag steht.
i
i
Zeilenpermutation
größter Absolutbetrag
Spaltenpermutation
Abbildung 1.6: Schema der totalen Pivotsuche.
Abbildung 6.6: Schema der totalen Pivotsuche.
Hierdurch ergibt sich in Schritt i der Gauß Elimination ein zusätzlicher Suchaufwand von
(m − i)(n − i) Vergleichen, also insgesamt über alle Schritte
Hierdurch ergibt sich in Schritt i der Gauß Elimination ein zusätzlicher Suchaufwand von (m − i)(n −
m
i) Vergleichen,
also insgesamt über alle Schritte
!
(m − i)(n − i) = (m − 1)(n − 1) + (m − 2)(n − 2) + · · · + 1
i=1
m
n−1
1
2 = (m − 1)(n
1)1+=(m
(n −−
1)i)
+ (n − 2)2 + · −
·· +
n − 2)(n
(n − 2)) + · · · + 1
∑ (m≤− i)(n
3
2
i=1
Vergleiche, d. h. kubisch in m viele.
Vergleiche. Wenn wir der Einfachheit halber m = n annehmen (quadratische Matrizen), so ergeben
In der Numerischen Mathematik werden weitere Methoden zum Erreichen numerischer Gesich und zum Abschätzen des maximalen Fehlers untersucht.
nauigkeit
(m − 1)2 + (m − 2)2 + · · · + 1 = m
m−1
1
(m − )
3
2
viele Vergleiche, also kubisch in m viele.
In der Numerischen Mathematik werden weitere Methoden zum Erreichen numerischer Genauigkeit
und zum Abschätzen des maximalen Fehlers untersucht.
140
6.2.5
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Eine Java Klasse zur Lösung linearer Gleichungssysteme
Wir geben nun ein Java Programm für die Lösung linearer Gleichungssysteme mittels Gauß-Elimination
an. Dazu implementieren wir eine Klasse LinearEquation, deren Objekte lineare Gleichungssysteme sind. Zu den public Methoden dieser Klasse gehören
solveByTotalPivotSearch() und
solveWithoutPivotSearch(),
die die entsprechende Variante der Gauß-Elimination des letzten Abschnitts zur Lösung verwenden.
Ein Object linEq dieser Klasse kann sich also durch die Anweisung
linEq.solveByTotalPivotSearch();
selbst lösen. Die Lösbarkeit wird in dem private Feld solvable abgespeichert, auf das mit der
public Methode isSolvable() zugegriffen werden kann. Ist das System lösbar (also linEq.isSolvable()
== true), so wird eine Lösung in dem private Feld solution abgespeichert, auf die mit getSolution()
zugegriffen werden kann.
Eine javadoc Übersicht aller public Konstruktoren und Methoden (es gibt keine public Felder)
der Klasse LinearEquation ist in Abbildung 6.7 angegeben, genauere Informationen enthalten Abbildung 6.8 und Abbildung 6.9.
Bei der Pivotsuche werden Vertauschungen von Zeilen und Spalten nicht tatsächlich durchgeführt
(dies würde zu viel Rechenzeit erfordern), sondern man merkt sich die Indexvertauschungen in zwei
Hilfsarrays, nämlich
int[] row;
int[] col;
// Zeilenindex
// Spaltenindex
Ist durch
double[][] a;
die Koeffizientenmatrix definiert, so ist a[row[i]][col[j]] also das “aktuelle” Element an der
Stelle (i, j); entsprechend sind x[col[j]] und b[row[i]] die aktuellen Elemente für
double[] x;
double[] b;
// Variable
// rechte Seite
Die Arrays row und col werden beim Aufruf des Konstruktors LinearEquation(double[][],
double[]) entsprechend zu row[i] = i und col[j] = j initialisiert.
Neben der Klasse LinearEquation werden noch die Hilfsklassen
MatrixPosition und
DimensionException
benötigt. Sie sind am Ende von Programm 6.4 aufgeführt.
6.2. LINEARE GLEICHUNGSSYSTEME
Abbildung 6.7: javadoc Summary der Klasse LinearEquation.
Programm 6.4 Java Klassen zum Lösen linearer Gleichungssysteme
/**
* class for systems of linear equations
*/
public class LinearEquation {
/**
* will be true if system is solvable
141
142
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Abbildung 6.8: Konstruktoren der Klasse LinearEquation.
*/
private boolean solvable;
/**
* will be true if solvability has been checked
*/
private boolean solved;
/**
* the rank of the coefficient matrix
*/
private int rank;
/**
* the matrix of coefficients
*/
private double[][] coeff;
/**
* the right hand side
*/
private double[] rhs;
6.2. LINEARE GLEICHUNGSSYSTEME
Abbildung 6.9: Die wichtigsten Methoden der Klasse LinearEquation.
/**
* encodes row permutations, row i is at position row[i]
*/
private int[] row;
/**
* encodes column permutations, column j is at position col[j]
*/
private int[] col;
/**
143
144
KAPITEL 6. ALGORITHMEN AUF ARRAYS
* holds the solution vector
*/
private double[] solution;
/**
* is true if system is in triangular form
*/
private boolean triangular;
/**
* default constructor
*/
public LinearEquation() {
solvable = false;
solved = false;
triangular = false;
rank = 0;
solution = null;
coeff = null;
rhs = null;
row = null;
col = null;
}
/**
* constructs object with given coeff matrix and rhs
* and initializes row and col
*
* @param a the matrix to which the coeff matrix is set
* @param b the rhs to which the rhs is set
*/
public LinearEquation(double[][] a, double[] b)
throws NullPointerException, DimensionException {
if (a == null || b == null) {
throw new NullPointerException("zugewiesener Array "
+ "ist null");
}
if (a.length != b.length) {
throw new DimensionException("unverträgliche "
+ "Dimension");
}
coeff = a;
rhs = b;
6.2. LINEARE GLEICHUNGSSYSTEME
145
row = new int[coeff.length];
for (int i = 0; i < coeff.length; i++) row[i] = i;
col = new int[coeff[0].length];
for (int j = 0; j < coeff[0].length; j++) col[j] = j;
rank = 0;
solution = null;
solved = false;
solvable = false;
triangular = false;
}
/**
* tests if system has been tested for solvability
*
* @return true if a solutioan has already been computed
*/
public boolean isSolved() {
return solved;
}
/**
* brings system into triangular form with choice of pivot method
* @exception NullPointerException
* @exception DimensionException
*/
private void triangularForm(int method) throws NullPointerException {
if (coeff == null || rhs == null) {
throw new NullPointerException();
}
int m = coeff.length;
int n = coeff[0].length;
int i, j, k;
// counters
int pivotRow = 0; // row index of pivot element
int pivotCol = 0; // column index of pivot element
double pivot; // value of pivot element
// main loop, transformation to triangle form
k = -1; // denotes current position on diagonal
boolean exitLoop = false;
146
KAPITEL 6. ALGORITHMEN AUF ARRAYS
while (! exitLoop) {
k++;
// pivot search for entry in remaining matrix
// (depends on chosen method in switch)
// store position in pivotRow, pivotCol
MatrixPosition pivotPos = new MatrixPosition(0, 0);
MatrixPosition currPos = new MatrixPosition(k, k);
switch (method) {
case 0 :
pivotPos = nonZeroPivotSearch(k);
break;
case 1 :
pivotPos = totalPivotSearch(k);
break;
}
pivotRow = pivotPos.rowPos;
pivotCol = pivotPos.colPos;
pivot = coeff[row[pivotRow]][col[pivotCol]];
// permute rows and colums to get this entry onto
// the diagonal
permutePivot(pivotPos, currPos);
//
//
//
//
//
if
test conditions for exiting loop
after this iteration
reasons are: Math.abs(pivot) == 0
k == m - 1 : no more rows
k == n - 1 : no more colums
((Math.abs(pivot) == 0) ||( k == m - 1)
|| (k == n - 1)) {
exitLoop = true;
}
// update rank
if (Math.abs(pivot) > 0) {
rank++;
}
// pivoting only if Math.abs(pivot) > 0
//
and k < m - 1
if ((Math.abs(pivot) > 0) && (k < m - 1)) {
6.2. LINEARE GLEICHUNGSSYSTEME
147
pivotOperation(k);
}
}//end while
triangular = true;
}
/**
* method for total pivot search
*
* @param k search starts at entry (k,k)
* @return the position of the found pivot element
*/
private MatrixPosition totalPivotSearch(int k){
double max = 0;
int i, j, pivotRow = k, pivotCol = k;
double absValue;
for (i = k; i < coeff.length; i++) {
for (j = k; j < coeff[0].length; j++) {
// compute absolute value of
// current entry in absValue
absValue = Math.abs(coeff[row[i]][col[j]]);
// compare absValue with value max
// found so far
if (max < absValue) {
// remember new value and position
max = absValue;
pivotRow = i;
pivotCol = j;
}//end if
}//end for j
}//end for k
return new MatrixPosition(pivotRow, pivotCol);
}
/**
* method for trivial pivot search, searches for non-zero entry
*
* @param k search starts at entry (k,k)
* @return the position of the found pivot element
*/
private MatrixPosition nonZeroPivotSearch(int k){
148
KAPITEL 6. ALGORITHMEN AUF ARRAYS
int i, j;
double absValue;
for (i = k; i < coeff.length; i++) {
for (j = k; j < coeff[0].length; j++) {
// compute absolute value of
// current entry in absValue
absValue = Math.abs(coeff[row[i]][col[j]]);
// check if absValue is non-zero
if (absValue > 0) { // found a pivot element
return new MatrixPosition(i, j);
}//end if
}//end for j
}//end for k
return new MatrixPosition(k, k);
}
/**
* permutes two matrix rows and two matrix columns
*
* @param pos1 the fist position for the permutation
* @param pos2 the second position for the permutation
*/
private void permutePivot(MatrixPosition pos1, MatrixPosition pos2) {
int r1 = pos1.rowPos; int c1 = pos1.colPos;
int r2 = pos2.rowPos; int c2 = pos2.colPos;
int index;
index = row[r2]; row[r2] = row[r1]; row[r1] = index;
index = col[c2]; col[c2] = col[c1]; col[c1] = index;
}
/**
* performs a pivot operation
*
* @param k pivoting takes place below (k,k)
*/
private void pivotOperation(int k) {
double pivot = coeff[row[k]][col[k]];
for (int i = k + 1; i < coeff.length; i++) {
// compute factor
double q = coeff[row[i]][col[k]] / (double) pivot;
// modify entry a[i,k], i > k
6.2. LINEARE GLEICHUNGSSYSTEME
149
coeff[row[i]][col[k]] = 0;
// modify entries a[i,j], i > k fixed, j = k+1...n-1
for (int j = k + 1; j < coeff[0].length; j++) {
coeff[row[i]][col[j]] = coeff[row[i]][col[j]]
- coeff[row[k]][col[j]] * q;
}//end for j
// modify right-hand-side
rhs[row[i]] = rhs[row[i]] - rhs[row[k]] * q;
}//end for k
}
/**
* solves linear system with the chosen method
* @param method the pivot search method
*/
private void solve(int method) throws NullPointerException{
if (solved) {
return; // solution exists
}
if (! triangular) {
triangularForm(method);
}
if (! isSolvable(method)) {
return;
}
int n = coeff[0].length;
double[] x = new double[n];
// set x[rank] = ... = x[n-1] = 0
if (rank < n) {
for (int j = rank; j < n ; j++) {
x[col[j]] = 0;
}
}//end if
// compute x[rank-1]
x[col[rank-1]] = rhs[row[rank-1]]
/ (double) coeff[row[rank-1]][col[rank-1]];
// compute remaining x[i] backwards
150
KAPITEL 6. ALGORITHMEN AUF ARRAYS
for (int i = rank - 2; i >= 0; i--) {
x[col[i]] = rhs[row[i]];
for (int j = i + 1; j <= rank-1; j++) {
x[col[i]] = x[col[i]]
- coeff[row[i]][col[j]] * x[col[j]];
}//end for j
x[col[i]] = x[col[i]]
/ (double) coeff[row[i]][col[i]];
}//end for i
solution = x;
solved = true;
}
/**
* solves linar system by total pivot search
*/
public void solveByTotalPivotSearch() throws NullPointerException {
solve(1);
}
/**
* solves linar system without pivot search
*/
public void solveWithoutPivotSearch() throws NullPointerException {
solve(0);
}
/**
* checks solvability of linar system with the chosen method
* @param method the pivot search method
* @return true if linear system in solvable
*/
private boolean isSolvable(int method) throws NullPointerException {
if (solved) {
return solvable;
}
if (! triangular) {
triangularForm(method);
}
for (int i = rank; i < rhs.length; i++) {
if (Math.abs(rhs[row[i]]) > 0) {
solvable = false;
return false; // not solvable
6.2. LINEARE GLEICHUNGSSYSTEME
}// end if
}// end for
solvable = true;
return true;
}
/**
* checks if a solved system is solvable
* @return true if linear system is solved and solvable
*/
public boolean isSolvable() {
return solvable && solved;
}
/**
* returns the solution
* @return <code>double</code> array representing a solution
*/
public double[] getSolution() {
return solution;
}
/**
* returns current matrix (A|b) as String
* @return String representing current matrix
*/
public String equationsToString() throws NullPointerException{
if ((coeff == null) || (rhs == null)
|| (row == null) || (col == null)) {
throw new NullPointerException();
}
StringBuffer strBuf = new StringBuffer();
String str = "
";
for (int j = 0; j < coeff[0].length; j++) {
str = str + col[j] + "
";
}
strBuf.append(str + "\n");
for (int i = 0; i < coeff.length; i++) {
str = "" + row[i] + ":";
for (int j = 0; j < coeff[0].length; j++) {
str = str + " " + coeff[row[i]][col[j]];
}
str = str + "
" + rhs[row[i]];
151
152
KAPITEL 6. ALGORITHMEN AUF ARRAYS
strBuf.append(str + "\n");
}
return strBuf.toString();
}
/**
* returns solution as String
* @return string representing solution vector
*/
public String solutionToString() throws NullPointerException{
if (solution == null) throw new NullPointerException();
StringBuffer strBuf = new StringBuffer();
for (int j = 0; j < solution.length; j++) {
strBuf.append("x_" + j + " = " + solution[j] + "\n");
}
return strBuf.toString();
}
}
/**
* class for matrix positions
*/
public class MatrixPosition {
int rowPos;
int colPos;
/**
* Constructor
* @param i the row position
* @param j the column position
MatrixPosition(int i, int j) {
rowPos = i;
colPos = j;
}
}
/**
* class for dimension exceptions
*/
import java.lang.*; // for class Exception
6.2. LINEARE GLEICHUNGSSYSTEME
153
public class DimensionException extends Exception {
/**
* Constructs a <code>DimensionException</code>
* with no detail message.
*/
public DimensionException() {
super();
}
/**
* Constructs a <code>DimensionException</code> with the
* specified detail message.
*
* @param s the detail message.
*/
public DimensionException(String s) {
super(s);
}
}
Das Applet Gauss.java verwendet diese Klassen zur Implementation eines interaktiven Lösers für
lineare Gleichungssysteme. Das Layout ist in Abbildung 6.10 dargestellt.
Programm 6.5 Gauss.java
/**
* Gauss.java
*
* demonstrates the use of Gauss’ algorithms for solving linear equations
*
* Assumptions:
* Input is per row, row i contains coefficients a_ij and
* the righthandside b_j as last number,
* missing a_ij are interpreted as 0
* numbers in a row are separated by white space
*/
import java.awt.*;
import java.applet.Applet;
import java.util.*;
// for class StringTokenizer
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
154
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Abbildung 6.10: Applet zur Lösung linearer Gleichungssysteme.
public class Gauss extends Applet {
private
private
private
private
private
TextArea input, output;
Button startBtn, helpBtn;
Choice choiceBtn;
Panel p1, p2, p3;
String helpStr =
"Bitte oben Kooeffizienten a_ij und rechte Seite b_i \n"
+ "zeilenweise eingeben.\n"
155
6.2. LINEARE GLEICHUNGSSYSTEME
+
+
+
+
+
+
"Die letze Zahl der Zeile i ist die rechte Seite b_i,\n"
"die Zahlen davor als a_i1, a_i2, ...\n"
"Fehlende a_ij in Zeile i werden als 0 interpretiert.\n"
"Das eingegebene Gleichungssystem wird mit \n"
"der gewählten Pivotsuche gelöst. \nDabei wird die Zeit "
"gemessen und ausgegeben.\n";
public LinearEquation linEq = new LinearEquation();
// setup the graphical user interface components
public void init() {
setLayout(new FlowLayout(FlowLayout.LEFT));
setFont(new Font("Times", Font.PLAIN, 12));
Font courier = new Font("Courier", Font.PLAIN, 12);
p1 = new Panel();
p2 = new Panel();
p3 = new Panel();
p1.setLayout(new FlowLayout(FlowLayout.LEFT));
input = new TextArea(" 1 0 1 0
+ " 1 1 0 1
+ " 1 1 2 0
+ " 1 0 0 1
input.setFont(courier);
p1.add(input);
// put input
4
7
6
5
\n"
\n"
\n"
");
on panel
p2.setLayout(new FlowLayout(FlowLayout.LEFT));
helpBtn = new Button("
Hilfe
");
helpBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
showHelp();
}
});
p2.add(helpBtn);
choiceBtn = new Choice();
choiceBtn.addItem("ohne Pivotsuche");
choiceBtn.addItem("mit totaler Pivotsuche");
p2.add(choiceBtn);
startBtn = new Button("
Start
");
156
KAPITEL 6. ALGORITHMEN AUF ARRAYS
startBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
runGauss();
}
});
p2.add(startBtn);
p3.setLayout(new FlowLayout(FlowLayout.LEFT));
output = new TextArea(helpStr, 15, 60);
output.setFont(courier);
p3.add(output);
add(p1);
add(p2);
add(p3);
}
/**
* reads numbers from input to arrays a and b and to precision
*/
private void initialize() throws NumberFormatException,
NoSuchElementException, DimensionException,
NullPointerException {
String inputStr = input.getText();
// divide input into lines
StringTokenizer inputLines = new StringTokenizer(inputStr,
"\n\r");
// get the number of rows
if (! inputLines.hasMoreTokens()) {
throw new NoSuchElementException();
}
int m = inputLines.countTokens();
StringBuffer strBuf = new StringBuffer();
strBuf.append(m + " Gleichungen");
// define array of m lines of tokens
StringTokenizer[] line = new StringTokenizer[m];
for (int i = 0; i < m; i++) {
line[i] = new StringTokenizer(inputLines.nextToken());
}
6.2. LINEARE GLEICHUNGSSYSTEME
// get the number of columns including b
int n = 0;
for (int i = 0; i < m; i++) {
if (line[i].countTokens() < 2) {
throw new NoSuchElementException();
}
if (line[i].countTokens() - 1 > n) {
n = line[i].countTokens() - 1;
}
}
strBuf.append(" und " + n + " Variable\n");
// initialize linEq
double[][] a = new double[m][n];
double[] b = new double[m];
for (int i = 0; i < m; i++) {
int j;
int k = line[i].countTokens() - 1;
// that many coeffs on line i
for (j = 0; j < k; j++) {
a[i][j] = Double.parseDouble(
line[i].nextToken());
}
for (j = k; j < n; j++) {
a[i][j] = 0;
}
b[i] = Double.parseDouble(line[i].nextToken());
}
linEq = new LinearEquation(a, b);
// write info in output field
output.setText(strBuf.toString());
}
/**
* displays help text
*/
public void showHelp() {
output.setText(helpStr);
}
157
158
KAPITEL 6. ALGORITHMEN AUF ARRAYS
/**
* solves system of linear equations by Gaussian elimination
*
* @see <code>LinearEquationSolver</code>
*/
public void runGauss() {
try {
output.setText("Rechne ...\n");
initialize();
int choice = choiceBtn.getSelectedIndex();
String choiceStr = choiceBtn.getSelectedItem();
long startTime = System.currentTimeMillis();
switch (choice) {
case 0 :
linEq.solveWithoutPivotSearch();
break;
case 1 :
linEq.solveByTotalPivotSearch();
break;
}
long endTime = System.currentTimeMillis();
long runTime = endTime - startTime;
// Construct the output string in a
// StringBuffer object
StringBuffer outputBuf = new StringBuffer();
outputBuf.append("Benötigte Zeit "
+ choiceStr + " in Millisekunden: "
+ Long.toString(runTime) + "\n");
outputBuf.append("solvable = " + linEq.isSolvable()
+ "\n");
outputBuf.append(linEq.equationsToString() + "\n");
if (linEq.isSolvable()) {
outputBuf.append(linEq.solutionToString());
}
output.append("\n" + outputBuf.toString());
} catch (NoSuchElementException exp) {
output.append("\nJede Zeile muss mindestens "
+ "2 Zahlen enthalten.");
} catch (NumberFormatException exp) {
6.2. LINEARE GLEICHUNGSSYSTEME
output.append("\nNur double Zahlen eingeben.");
} catch (DimensionException exp) {
output.append(exp.toString());
} catch (Exception exp) { // other exceptions
output.append(exp.toString());
}
}
}
159
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
151
6.3160 Kürzeste Wege in gerichteten
Graphen
KAPITEL
6. ALGORITHMEN AUF ARRAYS
6.3.1
Wege
6.3 Graphen
Kürzesteund
Wege
in gerichteten Graphen
Ein gerichteter
Graph
oder
Digraph (directed graph) ist ein Paar G = (V, E) bestehend aus
6.3.1 Graphen
und
Wege
gerichteter
Digraph
E) bestehend
aus
–Ein
Der
Menge Graph
V der oder
Knoten.
Bei(directed
uns istgraph)
meististVein=Paar
{1, G
2, =
. . (V,
. , n}
oder (in Implementationen) V = {0, 1, . . . , n − 1}.
– Der Menge V der Knoten. Bei uns ist meist V = {1, 2, . . . , n} oder (in Implementationen) V =
1, . . . , nE
− 1}.
– Der{0,
Menge
der (gerichteten) Kanten oder Bögen zwischen Knoten. Bei uns ist
E ⊆ V × V \ {(i, i)|i ∈ V }; e = (i, j) bedeutet, daß die Kante e vom Knoten i zum
– Der Menge E der (gerichteten) Kanten oder Bögen zwischen Knoten. Bei uns ist E ⊆ V ×V \
Knoten
j∈
gerichtet
ist.
{(i, i)|i
V }; e = (i,
j) bedeutet, dass die Kante e vom Knoten i zum Knoten j gerichtet ist.
Beispiel
6.4
E)mit
mit
V {1,
= 2,
{1,
3, 4,
5} und
Beispiel
6.4 GG=
= (V,
(V, E)
V=
3,2,
4, 5}
und
= {(1,
3),(2,
(2,3),
3),(2,
(2, 4),
1),1),
(4,(4,
3), (5,
E =E{(1,
2),2),
(1,(1,
3),
4),(3,
(3,5),
5),(4,(4,
3),4)}
(5, 4)}
ist ein
Graphmit
mit55 Knoten
Knoten und
8 gerichteten
Kanten.
Er ist inEr
Abbildung
6.11 dargestellt.
ist ein
Graph
und
8 gerichteten
Kanten.
ist in Abbildung
6.11 dargestellt.
2
4
3
5
1
Abbildung
6.11:
Zeichnung
Graphen
Beispiel
Abbildung
6.11:
Zeichnung des
des Graphen
ausaus
Beispiel
6.11.6.11.
Graphen
habenviele
vieleAnwendungen.
Anwendungen. SieSie
eignen
sich sich
zur Beschreibung
von
Graphen
haben
eignen
zur Beschreibung
von
– Verbindungen zwischen Orten in einem Netzwerk (Straßennetz, U-Bahnnetz,. . .),
– Verbindungen zwischen Orten in einem Netzwerk (Straßennetz, U-Bahnnetz,. . .),
– Hierarchien,
– Hierarchien,
– Syntax- und Flussdiagrammen,
– Syntax- und Flußdiagrammen,
– Arbeitsabläufen,
– Arbeitsabläufen,
und vielem anderen mehr.
solchen
Anwendungen
und In
vielem
anderen
mehr.haben die Kanten (i, j) des Graphen meist eine Bewertung, die je nach Anwendung als Länge oder Entfernung von i nach j (Straßennetz), Kosten (Arbeitsablauf) oder ähnlich
In solchen Anwendungen haben die Kanten (i, j) des Graphen meist eine Bewertung, die je
interpretiert wird.
nach Anwendung als Länge oder Entfernung von i nach j (Straßennetz), Kosten (Arbeitsablauf) oder ähnlich interpretiert wird.
161
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
152
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Ein solcher bewerteter Graph ist beschreibbar durch eine Matrix A = (ai j )i, j=1,...,n mit
Ein solcher bewerteter Graph
ist beschreibbar
eine (i,
Matrix
A=
mit
(Länge) durch
von Kante
j) falls
(i,(a
j)ij∈)i,j=1,...,n
E,
 Bewertung


ai j =
aij =
0 falls i = j,
∞ sonst.

Bewertung (Länge) von Kante (i, j) falls (i, j) ∈ E,

0 falls i = j,

 ∞ sonst.
Wir nennen A die Bewertungsmatrix des Graphen G. Zur Abspeicherung von A im Rechner wird statt
6
∞Wir
einenennen
sehr große
Zahl
M (z. B. M := n des
· max
1) verwendet.
i j| +
A die
Bewertungsmatrix
Graphen
Zur
Abspeicherung von A im Rechner
(i, j)∈E |aG.
wird statt ∞ eine sehr große Zahl M (z. B. M := n · max(i,j)∈E |aij | + 1) verwendet.6
Beispiel 6.5 (Fortsetzung von Beispiel 6.4) Für den Graphen aus Beispiel 6.4 legen wir die in AbBeispiel
(Fortsetzung
von Beispiel
6.4)Kantenbewertungen
Für den Graphen aus
Beispiel
6.4 legen wir
bildung
6.126.5
links
neben den Kanten
angegebenen
fest.
Die zugehörige
Matrix A
die
in
Abbildung
6.12
links
neben
den
Kanten
angegebenen
Kantenbewertungen
fest. Die
ist rechts daneben angegeben.
zugehörige Matrix A ist rechts daneben angegeben.
−1
2
−1
2
1
2
3
4
0
3
1
1
5
⇒



A=


0 −1
∞
0
∞ ∞
−1 ∞
∞ ∞

2 ∞ ∞
2 3 ∞ 

0 ∞ 1 

0 0 ∞ 
∞ 1 0
Abbildung 6.12: Bewerteter Graph aus Beispiel 6.12.
Abbildung 6.12: Bewerteter Graph aus Beispiel 6.12.
Falls nur der Graph G beschrieben werden soll (d. h. ohne Kantenbewertungen), so reicht
auch die Matrix
+
1 ohne
(i, j) Kantenbewertungen),
∈ E,
Falls nur der Graph G beschrieben werden soll (d. h.
so reicht auch die
A = (aij ) mit aij =
0
sonst.
Matrix
1 (i, j) ∈ E,
A = (ades
ai j = G. In ihr ist durch Zugriff auf aij in koni j ) mit
Diese Matrix heißt Adjazenzmatrix
Graphen
0 sonst.
stanter Zeit (d. h. unabhängig von der Größe des Graphen) feststellbar, ob i und j durch
eine Matrix
Kante verbunden
sind. Dafürdes
benötigt
man
quadratischen
(n × n Zeit
Diese
heißt Adjazenzmatrix
Graphen
G. jedoch
In ihr ist
durch ZugriffSpeicherplatz
auf ai j in konstanter
Matrix).
(d. h. unabhängig von der Größe des Graphen) feststellbar, ob i und j durch eine Kante verbunden
Es sind
auch
andere
Datenstrukturen
zur Speicherung
von (n
Graphen
möglich, z. B. für jeden
sind.
Dafür
benötigt
man
jedoch quadratischen
Speicherplatz
× n Matrix).
Knoten i eine Liste der Knoten j, die mit i durch eine Kante (i, j) verbunden sind. Diese
Es sind auch andere Datenstrukturen zur Speicherung von Graphen möglich, z. B. für jeden Knoten
benötigen weniger Platz, erfordern jedoch mehr Zeit, um festzustellen, ob (i, j) ∈ E ist oder
i eine
Liste der Knoten j, die mit i durch eine Kante (i, j) verbunden sind. Diese benötigen weninicht, da Listen nur sequentiellen Zugriff erlauben.
ger Platz, erfordern jedoch mehr Zeit, um festzustellen, ob (i, j) ∈ E ist oder nicht, da Listen nur
Bei manchenZugriff
Anwendungen
sequentiellen
erlauben.spielen die Richtungen der Kanten keine Rolle. In diesem Fall
ist mit einer Kante (i, j) auch die Kante (j, i) im Graphen, und beide haben dieselbe Bewer-
Bei
manchen
spielen
die Richtungen
der Kanten
keinegerichteten
Rolle. In diesem
tung.
In derAnwendungen
Darstellung des
Graphen
wird dann statt
der beiden
KantenFall
eineist mit
einer6 Wir
Kante
(i,
j)
auch
die
Kante
(
j,
i)
im
Graphen,
und
beide
haben
dieselbe
Bewertung.
In der
werden hier nur diese einfache, aber viel Speicherplatz verbrauchende Datenstruktur verwenden. Eine
Darstellung
Graphen
wirdindann
dervon
beiden
Kantendieeine
ungerichtete
gezeichnet,
oft benutzte des
Alternative
besteht
einemstatt
Array
Listen.gerichteten
Dabei entsprechen
Indices
der Komponenten
den Knoten des Graphen, und die Komponente i enthält eine Liste aller Knoten j, zu denen von i aus eine
6 Wir werden
Kante
(i, j) existiert.
hier nur diese einfache, aber viel Speicherplatz verbrauchende Datenstruktur verwenden. Eine oft benutzte
Alternative besteht in einem Array von Listen. Dabei entsprechen die Indices der Komponenten den Knoten des Graphen,
und die Komponente i enthält eine Liste aller Knoten j, zu denen von i aus eine Kante (i, j) existiert.
162
KAPITEL 6. ALGORITHMEN AUF ARRAYS
s. Man spricht dann von ungerichteten Kanten. Ein Graph mit nur ungerichteten
s R
s statt also s
I
Kanten heißt ungerichteter Graph.
Im folgenden werden wir die Kantenbewertung als Entfernung interpretieren, bzgl. der wir kürzeste
Wege zwischen je zwei Knoten berechnen wollen.
Ein Weg von i nach j ist eine endliche Folge von Knoten und Kanten
i = i1 , (i1 , i2 ), i2 , (i2 , i3 ), . . . , ik−1 , (ik−1 , ik ), ik = j .
i3 - p p p
i1 - i2 - p
p
p
p
p
i` p p p - ik−1 - ik
p
p
p
p
p p p p p
Dabei sind Knotenwiederholungen zugelassen, und auch der triviale Weg, der nur aus einem Knoten
besteht (man “geht” dann von i nach i, indem man in i bleibt).
Ein Weg heißt elementar, wenn keine Knotenwiederholungen auftreten. Ein Weg von i nach j heißt
Zykel, falls i = j gilt. Die Länge eines Weges ist die Summe der Entfernungen der Kanten auf dem
Weg. Die Länge des trivialen Weges ist 0. Ein Weg von i nach j heißt kürzester Weg, falls alle anderen
Wege von i nach j eine mindestens ebenso große Länge haben.
Unser Ziel ist nun die Berechnung der kürzesten Weglängen ui j zwischen je zwei Knoten i, j (mit
ui j = ∞ falls kein Weg von i nach j existiert) sowie zugehöriger kürzester Wege.
6.3.2
Zwei konkrete Anwendungen
Als erste Anwendung betrachten wir den Ausschnitt aus dem BVG-Netz von Berlin in Abbildung
6.13. Dieses Netz wird in Abbildung 6.14 als bewerteter ungerichteter Graph wiedergegeben, da jede
Kante in beiden Richtungen “bereist” werden kann, und die Reisezeit für beide Richtungen gleich ist.
Die Bewertung einer Kante ist für beide Richtungen gleich und entspricht der mittleren Reisezeit in
Minuten (ohne Umsteigezeiten und Wartezeiten).
Die Bewertungsmatrix A dieses Graphen ist in Tabelle 6.3 angegeben. Als kürzester Weg von der
Yorkstraße zum Mathematikgebäude ergibt sich
Yo −→ Be −→ Zoo −→ ER −→ MA
mit der Länge 5 + 5 + 2 + 5 = 17 Minuten.
Umsteige- und Wartezeiten können berücksichtigt werden, indem man die Stationen “aufbläht” zu
mehreren Knoten, die den verschiedenen Linien entsprechen, und den Kanten dazwischen die Umsteigeund Wartezeiten zuordnet bzw. die Kantenbewertungen um diese Werte vergrößert. Dies ist in Abbildung 6.15 für die Station Zoologischer Garten ausgeführt.
Bei Entfernungen als Kantenbewertungen treten nur nicht-negative Zahlen auf. Oft sind jedoch auch
negative Kantenbewertungen sinnvoll, wie die folgende Anwendung des Devisentausches zeigt.
163
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
154
KAPITEL 6. ALGORITHMEN AUF ARRAYS
S-Friedrichstr.
MA-Gebäude
Bismarckstr.
S1
S-Tiergarten
Fußwege
ErnstReuter-Pl.
Zoologischer
Garten
Wittenbergpl.
U2
U1
Möckernbrücke
Bus 119
U9
Yorckstr.
U7
Berliner Str.
Abbildung 6.13: Ausschnitt aus dem BVG-Netz.
Abbildung 6.13: Ausschnitt aus dem BVG-Netz.
Tabelle 6.3: Bewertungsmatrix des Graphen zum BVG-Netz.
Bi ER
0
2
2
0
Bi
ER
∞
5
0∞ ∞
2
2∞ ∞
0
∞
∞
52
7
∞
∞ ∞
∞ ∞
∞ ∞
∞ ∞
∞∞ ∞
2
MA
∞
5
MA
0
∞
10
∞
5
11
0
∞
10
∞
∞
∞
11
∞
Ti
∞
∞
Ti
10
0∞
7∞
310
∞0
∞
7
∞
∞3
Fr
∞
∞
Fr
∞
7∞
0∞
∞∞
∞7
10
0
∞
∞∞
Zoo Be Yo
∞
7
∞
2
∞ ∞
Zoo∞ Be∞
11
3 ∞ ∞ 7∞
∞ 2 ∞ ∞10
0 11 5 ∞∞
5 3 0 ∞5
∞
5
0
∞
∞
∞ ∞ 2
2 0 ∞ 5 15
Mö
∞
∞
Yo∞
∞∞
∞∞
∞∞
∞∞
2
10
0
∞7
Wi
∞
∞
Mö
Wi
∞
∞∞ ∞
∞∞ ∞
∞2 ∞
∞∞ ∞
15
∞
∞
7
∞0
2
Tabelle 6.3: Bewertungsmatrix des Graphen zum BVG-Netz.
Bi
ER
MA
BiTi
ERFr
Zoo
MA
TiBe
Yo
Fr
Mö
Zoo
Wi
Be
Yo
Mö
Wi
7
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
∞
10
∞
∞
5
∞
∞
2
0
5
∞
∞
5
0
2
15
∞
2
0
7
∞
15
7
0
164
KAPITEL 6. ALGORITHMEN AUF ARRAYS
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
155
Fr 155
7
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
MA
5
Bi
2
Ti
ER
11
MA
2
5
Bi
10
Fr
7
3
10
10
Zoo
2
Ti
ER
2
11
3
2
Wi
7
10
Mö
Zoo
7
2
15
Wi
7
2
7
5
Mö
Yo
15
5
2
5
Be
Yo
Abbildung 6.14: Der Graph
5 für das BVG-Netz.
Abbildung 6.14: Der Graph für das BVG-Netz.
Ti
Be
ER
3 für das BVG-Netz.
Abbildung 6.14:
2 Der Graph
ER
10
Zoo
Ti
10
5
2
2
3
Wi
Zoo
10
5
10
5
2
Wi
Be
5
Abbildung 6.15: Zoologischer Garten mit Umsteige- und Wartezeiten.
Be
Abbildung 6.15: Zoologischer Garten mit Umsteige- und Wartezeiten.
Abbildung 6.15: Zoologischer Garten mit Umsteige- und Wartezeiten.
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
165
Gegeben ist ein gerichteter Graph, bei dem die Knoten Devisenbörsen darstellen und eine Kante (i, j)
dem Tausch von Währung i in die “Zielwährung” j entspricht. Die Bewertung der Kante (i, j) gibt
den Preis (Kurs) für eine Einheit der Zielwährung j bzgl. der Währung i an. Daher haben beide
Kantenrichtungen unterschiedliche Werte. Ein Beispiel ist in Abbildung 6.16 angegeben (Kurse vom
November 1998).
Abbildung 6.16: Ein Graph für den Devisentausch (Kurse November 2003).
In dieser Anwendung möchte man für gegebene “Heimatwährung” i und Zielwährung j eine Umtauschfolge (also einen Weg) bestimmen, so dass das Produkt der Bewertungen entlang des Weges
(also der Preis für eine Einheit der Zielwährung) möglichst klein wird.
Die Problem lässt sich folgendermaßen auf ein Kürzeste-Wege Problem transformieren. Ersetze die
Kantenbewertung ai j der Kante (i, j) durch log ai j . Dann ist (nach den Logarithmengesetzen) das
Produkt entlang eines Weges gleich der Summe der Logarithmen der Kanten. Man erhält also ein
Kürzestes Wege Problem mit āi j := log ai j als Bewertung der Kanten. Dabei können negative Bewertungen āi j auftreten, nämlich genau dann, wenn ai j < 1 ist.
Als Konsequenz negativer Kantenbewertungen können auch Zykel negativer Länge auftreten. Diese
machen, wie wir sehen werden, bei der Berechnung kürzester Wege Schwierigkeiten. Beim Devisentausch haben sie jedoch eine besondere Bedeutung: Sie entsprechen einem Gewinn bringenden Zykel,
auf dem man durch Umtausch seinen Einsatz vermehren kann. Im Graphen aus Abbildung 6.16 existiert ein solcher Zykel, vgl. Abbildung 6.17. Der Tausch entlang des Zykels ermöglicht den Kauf einer
DM für 2,79·0,51·0,61 = 0,9960 DM, also eine Vermehrung des eingesetzten Kapitals um den Faktor
1/0,9960 ' 1,004161.
Solche Zykel können tatsächlich bei Devisengeschäften vorkommen, allerdings nur kurzfristig, da
sich Kurse dauernd aufgrund von Angebot und Nachfrage ändern. Die Rechner der Devisenhändler
bemerken solche Zykel sofort und ordern entsprechend bis die steigende Nachfrage durch das Steigen
der Preise den Faktor wieder größer als 1 werden lässt.
166
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Abbildung 6.17: Ein gewinnbringender Zykel beim Devisentausch.
6.3.3
Die Bellman Gleichungen
Wir werden jetzt eine Methode kennen lernen, mit der man die kürzesten Weglängen (und auch die
kürzesten Wege selbst) zwischen je 2 Knoten iterativ berechnen kann (sofern sie existieren). Die
Grundidee basiert darauf, die Anzahl der Kanten auf den betrachteten Wegen in jeder Iteration um 1
zu vergrößern. Dazu definieren wir für i, j ∈ V und m ∈ N die Größe

 Länge eines kürzesten Weges von i nach j
(m)
mit höchstens m Kanten, falls dieser existiert,
ui j :=

∞ falls kein solcher Weg existiert.
Diese Größe ist wohldefiniert, da es für festes m nur endlich viele Wege mit höchstens m Kanten
zwischen i und j gibt, und somit unter diesen endlich vielen auch einen kürzesten.
In Beispiel ?? ergeben sich für i = 1 und j = 5 folgende Werte:
(1)
(2)
(3)
(4)
(5)
u15 = ∞, u15 = 3, u15 = 2, u15 = 2, u15 = 2 .
Allgemein gilt:
Lemma 6.4 Für festes i und j und m ≥ 2 ist
(1)
(2)
(m)
(m+1)
ui j ≥ ui j ≥ . . . ≥ ui j ≥ ui j
.
Beweis: Beim Übergang von m auf m + 1 vergrößert sich die Menge der Wege unter denen man einen
(m)
(m+1)
kürzesten Weg ermittelt. Daher gilt ui j ≥ ui j
.
(m)
Die ui j lassen sich durch Rekursionsgleichungen berechnen, die im folgenden Satz 6.4 angegeben
sind. Sie besagen anschaulich, dass sich die kürzeste Länge eines Weges von i nach j mit höchstens
167
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
m + 1 Kanten aufspalten lässt in die kürzeste Länge eines Weges mit höchstens m Kanten von i bis zu
einem Knoten k und die Länge ak j der Kante (k, j). Die Minimumsbildung in der Rekursionsgleichung
dient dem Finden des richtigen Knoten k.
Im Beweis dieses Satzes spielt das Prinzip der optimalen Substruktur eine wichtige Rolle. Es besagt
in diesem Fall, das ein kürzester Weg zwischen zwei Knoten sich aus kürzesten Teilwegen zusammen
setzt. Kurz: jeder Teilweg eines kürzesten Weges ist selber ein kürzester Weg zwischen den Endpunkten des Teilweges. Abbildung 6.18 veranschaulicht dieses Prinzip. Es gilt in verschiedenen Bereichen
der Mathematik und gibt immer Anlass zu Algorithmen, die große“ optimale Strukturen aus optima”
len Teilstrukturen zusammen setzen.
i
r
s
j
kürzester Weg von i nach j
!
r
s
kürzester Weg von r nach s
Abbildung 6.18: Das Prinzip der optimalen Substruktur bei kürzesten Wegen.
(m)
Satz 6.4 (Bellman Gleichungen) Die ui j
Bellman Gleichungen).
(1)
ui j = ai j ,
(m+1)
ui j
erfüllen die folgenden Rekursionsgleichungen (die sog.
(m)
= min [uik + ak j ] für m ≥ 1 .
k=1,...,n
Beweis: Für m = 1 kommen nur Wege mit höchstens einer Kante in Frage. Für i 6= j also gerade die
Länge der Kante (i, j), falls diese existiert, bzw. ∞ andernfalls. Für i = j kommt nur der triviale Weg
(1)
mit 0 Kanten in Frage. Also gilt in allen Fällen ui j = ai j .
(m+1)
Betrachte nun ui j
. Wir zeigen zunächst
(m+1)
ui j
(m)
≥ min [uik + ak j ] .
k=1,...,n
(m+1)
Diese Ungleichung ist trivialerweise erfüllt, falls ui j
höchstens m + 1 Kanten existiert.
(6.1)
= ∞ ist, also kein Weg von i nach j mit
Also nehmen wir an, dass ein solcher Weg existiert. Sei dann W ein kürzester Weg von i nach j mit
höchstens m + 1 Kanten (dieser existiert, da wegen der Beschränkung auf maximal m + 1 Kanten nur
endlich viele Wege in Frage kommen). Wir unterscheiden den Fall, dass W keine bzw. mindestens
eine Kante enthält.
(1) W habe mindestens eine Kante, etwa
168
KAPITEL 6. ALGORITHMEN AUF ARRAYS
- i1 - i2 - p p p p p p - ` - j
ip i
p p p?
W :=
(m+1)
Dann hat W die Länge ui j
. (Dabei kann W einen Zykel enthalten oder auch i = j sein, so dass der
ganze Weg einen Zykel bildet.)
Das Anfangsstück
i1 - i2 - p p p W 0 := ip p p p - `
i - p p p?
von W ist ein kürzester Weg von i nach ` mit höchstens m Kanten; denn gäbe es einen kürzeren, so
würde man durch Anhängen der Kante (`, j) an W 0 einen kürzeren Weg mit höchstens m + 1 Kanten
von i nach j erhalten, im Widerspruch zur Annahme, dass W ein kürzester Weg von i nach j ist.
(m)
Also ist die Länge von W 0 gleich ui` und somit die Länge von W gleich
(m+1)
(m)
(m)
= ui` + a` j ≥ min [uik + ak j ] ,
ui j
k=1,...,n
da ` bei der Minimumsbildung vorkommt. Also ist Ungleichung (6.1) erfüllt.
(2) Hat W keine Kanten, so ist i = j und W besteht nur aus dem einzigen Knoten i = j. Dann ist seine
(m+1)
(m+1)
(m)
(1)
(m)
Länge uii
= 0. Aus Lemma 6.4 folgt uii
≤ uii ≤ uii = aii = 0 und somit uii = 0. Also gilt
(m+1)
uii
(m)
(m)
= uii + aii ≥ min [uik + ak j ] ,
k=1,...,n
da i bei der Minimumsbildung vorkommt. Folglich gilt Ungleichung (6.1) auch in diesem Fall.
Jetzt zeigen wir umgekehrt die Ungleichung
(m+1)
ui j
(m)
≤ min [uik + ak j ] .
k=1,...,n
(6.2)
Diese Gleichung ist wieder trivialerweise erfüllt, falls die rechte Seite ∞ ist. Ist sie endlich, so sei r
der Index, für den das Minimum angenommen werde, d. h.
(m)
(m)
min [uik + ak j ] = uir + ar j .
k=1,...,n
Dabei unterscheiden wir die Fälle r 6= j bzw. r = j.
(m)
(m)
(1) Sei r 6= j. Da uir + ar j < ∞, sind uir < ∞ und ar j < ∞. Also existiert ein kürzester Weg
W=
- j1 - j2 - p p p jq p p p - r
i
p p p?
(m)
von i nach r mit höchstens m Kanten und Länge uir und es existiert wegen r 6= j und ar j < ∞ im
Graphen die Kante (r, j). Der Weg W kann Zykel enthalten und es kann auch i = r sein (dann hat W
entweder 0 Kanten oder der gesamte Weg W bildet einen Zykel). Das folgende Argument gilt für all
diese Fälle.
Da die Kante (r, j) existiert, ist
169
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
j1 - j2 - p p p W 0 := p p p - r - j
jq i - p p p?
(m)
ein Weg von i nach j mit höchstens m + 1 Kanten und seine Länge ist uir + ar j . Diese kann natürlich
nicht kleiner als die kürzeste Länge eines Weges von i nach j mit höchstens m + 1 Kanten sein, d. h.
(m)
(m+1)
uir + ar j ≥ ui j
.
Also gilt Ungleichung (6.2).
(2) r = j. Dann ist
(m)
(m)
(m)
(m+1)
min [uik + ak j ] = ui j + a j j = ui j ≥ ui j
k=1,...,n
wegen a j j = 0 und Lemma 6.4.
Aus den Ungleichungen (6.1) und (6.2) folgt die Behauptung.
6.3.4
Der Einfluss negativer Zykel
Es stellt sich nun die Frage, wie groß man die Anzahl m der Kanten maximal wählen muss, um
kürzeste Weglängen zu berechnen. Hier gibt es Schwierigkeiten falls der Graph Zykel negativer Länge
(kurz: negative Zykel) enthält. In diesem Fall können die Wege mit höchstens m Kanten einen solchen
(m)
Zykel (sogar mehrfach) enthalten, so dass das Ergebnis ui j keinem elementaren Weg (also einem
ohne Knotenwiederholungen) mehr entspricht, wie das folgende Beispiel zeigt.
Beispiel 6.6 (Der Einfluss negativer Zykel) Betrachte den Graph aus Abbildung 6.19. Um die kürzeste
Weglänge von 1 nach n zu berechnen, müssen m := n − 1 Kanten berücksichtigt werden. Dann gibt
(m)
(m)
zwar u1n die richtige kürzeste Weglänge an, aber die Werte u1 j sind für j = 2, . . . , n − 2 ungleich der
kürzesten Länge eines elementaren Weges von 1 nach j (die 0 beträgt). Zum Beispiel ergibt sich für
j=4
n−3
(3)
(4)
(5)
(6)
(7)
(8)
(n−1)
u14 = u14 = 0, u14 = u14 = −1, u14 = u14 = −2, . . . , u14 = −
.
2
Die Ursache ist natürlich der negative Zykel 2 −→ 3 −→ 2 der Länge −1.
−1
?
1
- 2
0
0
- 3
0
- 4
0
- p p p
0
Abbildung 6.19: Ein Graph mit einem Zykel negativer Länge.
- n
170
KAPITEL 6. ALGORITHMEN AUF ARRAYS
Wir untersuchen daher zunächst den Fall, dass der gegebene Graph G keine negativen Zykel hat. Dies
lässt immer noch negative Kantenbewertungen zu, nur dürfen sich diese nicht entlang eines Zykels zu
einer negativen Zahl summieren.
Lemma 6.5 Hat G keinen negativen Zykel und gibt es einen Weg von i nach j, so existiert ein kürzester
Weg von i nach j, der elementar ist.
Beweis: Da G keine negativen Zykel enthält, kann die Wegnahme von Zykel aus einem Weg die
Weglänge höchstens verkürzen. Also kann man sich bei der Suche nach kürzesten Wegen auf elementare Wege beschränken. Da es nur endlich viele elementare Wege von i nach j gibt, existiert hierunter
auch ein kürzester.
Wir definieren nun

 Länge eines kürzesten elementaren Weges von i nach j
falls kein Weg von i nach j existiert,
ui j :=

∞ falls ein Weg von i nach j existiert.
Dann gilt (vgl. Lemma 6.4):
Satz 6.5 Hat G keine negativen Zykel, so ist für festes i und j
(1)
(2)
(n−1)
ui j ≥ ui j ≥ . . . ≥ ui j
= ui j .
Die Bellman Gleichungen berechnen also in n − 2 Iterationen die kürzesten Weglängen ui j .
(m)
Beweis: Da G keine negativen Zykel hat, folgt aus Lemma 6.5, dass ui j nie kleiner als ui j werden
kann. Elementare Wege können bei n Knoten höchstens n − 1 Kanten haben. Die Monotonieeigen(m)
schaft der ui j aus Lemma 6.4 ergibt dann die Behauptung.
6.3.5
Der Bellman-Ford Algorithmus
Satz 6.5 lässt sich direkt in den folgenden Algorithmus umsetzen, der nach seinen Entdeckern BellmanFord Algorithmus genannt wird. Wir geben ihn direkt als Java Methode an, die aus der Entfernungsmatrix A des Graphen die Matrix U der kürzesten Weglängen ermittelt und zurück gibt. Dabei speichert
(m)
(m+1)
u die ui j ab und dient temp zur Berechnung der ui j
für festes i.
Programm 6.6 bellman
public double[][] bellman(double[][] a){
171
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
int n = a.length; // number of nodes
double[][] u = new double[n][n];
// initialize u
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
u[i][j] = a[i][j];
}
}
// main loop
for (int m = 2; m < n; m++){
for (int i = 0; i < n; i++) {
double[] tmp = new double[n]; // tmp array
// for calculating min{...} in Bellman equation
for (int j = 0; j < n; j++) {
// save current distance from i to j
tmp[j] = u[i][j];
for (int k = 0; k < n; k++) {
if (tmp[j] > u[i][k] + a[k][j]) {
tmp[j] = u[i][k] + a[k][j];
}
}// end for k
}// end for j
// store tmp in u[i]
for ( int j = 0; j < n; j++ ) {
u[i][j] = tmp[j];
}
}// endfor i
}// endfor m
return u;
}
Die Korrektheit des Bellman-Ford Algorithmus folgt direkt aus Satz 6.5 und den Bellman Gleichungen (Satz 6.4). Für den Aufwand überlegt man sich aus der Schachtelung der Schleifen:
Satz 6.6 Der Bellman-Ford Algorithmus in der Version von Programm 6.6 benötigt (n − 2) · n · n · n
Vergleiche und höchstens (n − 2) · n · (n(1 + n) + 1) Zuweisungen, d. h. seine Laufzeit liegt in der
Größenordnung von n4 Operationen.
(m)
Ist man nur an den ui j interessiert, nicht jedoch an den ui j
(d. h. den kürzesten Weglängen mit
(m)
höchstens m Kanten), so kann man wegen der Monotonieeigenschaft der ui j in u[i,j] direkt jedesmal den Wert von temp[j] abspeichern. Dann gilt nach der m-ten Iteration der äußeren Schleife
(m)
(m+`)
ui j ≥ u[i,j] = ui j
≥ ui j
172
KAPITEL 6. ALGORITHMEN AUF ARRAYS
für ein ` mit 0 ≤ ` ≤ n − 1 − m. Dies liegt daran, dass kürzere Weglängen mit einer Kante mehr direkt
in u[i,j] abgespeichert werden und für weitere Rechnungen schon benutzt werden.
Dies hat im Programm den Vorteil, dass die Zwischenspeicherung in temp überflüssig wird und da(m)
durch weniger Zuweisungen nötig sind. Dafür wird in der m-ten Iteration jedoch nicht ui j berechnet,
(m)
(n−1)
sondern ein Wert zwischen ui j und ui j
= ui j .
Der Algorithmus vereinfacht sich dann zu
Programm 6.7 bellmanShort
public double[][] bellmanShort(double[][] a){
int n = a.length; // number of nodes
double[][] u = new double[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
u[i][j] = a[i][j];
}
for ( int m = 2; m < n; m++ ) {
for ( int i = 0; i < n; i++ ) {
for ( int j = 0; j < n; j++ ) {
for ( int k = 0; k < n; k++ ) {
if ( u[i][j] > u[i][k] + a[k][j] ) {
u[i][j] = u[i][k] + a[k][j];
}
}
}
}
}
return u;
}
Dieser Algorithmus hat allerdings immer noch eine Laufzeit von der Größenordnung n4 . Tatsächlich
gibt es, wenn man nur an den ui j interessiert ist, andere einfache Algorithmen, deren Laufzeit von der
Größenordnung n3 ist, vgl. die Literaturhinweise am Ende des Kapitels.
Beispiel 6.7 (Fortsetzung von Beispiel ??) Für den Graphen aus Beispiel ?? sollen die kürzesten
Weglängen mit dem Bellman-Ford Algorithmus berechnet werden (in der Version von Programm
6.6).
(m)
Dabei bezeichnet U (m) die Matrix (ui j )i, j=1,...,n und U

0 −1
 ∞
0

∞
∞
U (1) = A = 

 −1 ∞
∞ ∞
die Matrix (ui j )i, j=1,...,n . Gemäß Satz 6.4 ist

2 ∞ ∞
2 3 ∞ 

0 ∞ 1 

0 0 ∞ 
∞ 1 0
173
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
(2)
Dann ergibt sich u11 aus den Bellman Gleichungen gemäß
(2)
u11
=
(1)
min [u1k + ak1 ]
k=1,...,n
= min[0 + 0, −1 + ∞, 2 + ∞, ∞ − 1, ∞ + ∞] = 0 .
Dies entspricht einer Verknüpfung der ersten Zeile von U (1) mit der ersten Spalte von A. Diese Verknüpfung, wir wollen sie mit “⊗” bezeichnen, ist gerade die rechte Seite der Bellman Gleichungen,
also


0
 ∞ 


(1)
(1)

min [uk + ak1 ] .
U1· ⊗ A·1 = (0, −1, 2, ∞, ∞) ⊗ 
 ∞  = k=1,...,n
 −1 
∞
Entsprechend ist

(2)
u12
=
=
(2)
u13
=
=

−1
 0 


(1)

∞
U1· ⊗ A·2 = (0, −1, 2, ∞, ∞) ⊗ 


 ∞ 
∞
min[0 − 1, −1 + 0, 2 + ∞, ∞ + ∞, ∞ + ∞] = −1 ,


2
 2 


(1)

U1· ⊗ A·3 = (0, −1, 2, ∞, ∞) ⊗ 
 0 
 0 
∞
min[0 + 2, −1 + 2, 2 + 0, ∞ + 0, ∞ + ∞] = 1 .
(1)
Hier wurde gegenüber u13 = 2 eine kürzere Weglänge über 2 Kanten ermittelt. Weiterhin ist

∞
 3 


(1)

U1· ⊗ A·4 = (0, −1, 2, ∞, ∞) ⊗ 
 ∞ 
 0 
1
min[0 + ∞, −1 + 3, 2 + ∞, ∞ + 0, ∞ + 1] = 2 ,


∞
 ∞ 


(1)

U1· ⊗ A·5 = (0, −1, 2, ∞, ∞) ⊗ 
 1 
 ∞ 
0
min[0 + ∞, −1 + ∞, 2 + 1, ∞ + ∞, ∞ + 0] = 3 .

(2)
u14
=
=
(2)
u15
=
=
174
KAPITEL 6. ALGORITHMEN AUF ARRAYS
(2)
Also ist die erste Zeile von U (2) gleich (0, −1, 1, 2, 3). Die anderen ui j berechnen sich entsprechend,
z. B.


0
 ∞ 


(2)
(1)

∞
u21 = U2· ⊗ A·1 = (∞, 0, 2, 3, ∞) ⊗ 


 −1 
∞
= min[∞ + 0, 0 + ∞, 2 + ∞, 3 − 1, ∞ + ∞] = 2 .
Insgesamt erhält man



U (2) = 


0 −1 1 2 3
2
0 2 3 3
∞ ∞ 0 2 1
−1 −2 0 0 1
0 ∞ 1 1 0






und schließlich

U =U
(4)


=


0 −1 1 2 2
2
0 2 3 3
1
0 0 2 1
−1 −2 0 0 1
0 −1 1 1 0



.


Das Beispiel zeigt, dass die Bellman Gleichungen eine starke Analogie zur Matrixmultiplikation aufweisen. Bei der Matrixmultiplikation C := A · B ist der Eintrag ci j der Ergebnismatrix C gegeben als
n
ci j =
∑ [aik · bk j ] .
k=1
Hier ist “·” die innere und “+” die äußere Operation.
In den Bellman Gleichungen C := A ⊗ B ist der Eintrag ci j der Ergebnismatrix C gegeben als
ci j = min [aik + bk j ] ,
k=1,...,n
also mit “+” als innerer und “min” als äußerer Operation.
Speziell ist
U (1)
U (2)
U (3)
= A
= U (1) ⊗ A
= U (2) ⊗ A
..
.
= A⊗A
= A⊗A⊗A
U (n−1) = U (n−2) ⊗ A = A
· · ⊗ A} =: An−1 .
| ⊗ ·{z
n−1 mal
175
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
Die Analogie zwischen Matrixmultiplikation und Bellman Gleichungen wird besonders deutlich,
wenn man sich nur dafür interessiert, ob i und j mit einem Weg mit höchstens m Kanten verbun(m)
den sind oder nicht. Dann kann ui j als Boolesche Variable definiert werden, d. h.
true es gibt einen Weg von i nach j mit höchstens m Kanten,
(m)
ui j =
false sonst.
Ausgehend von A = (ai j ) mit
ai j =
erhält man
(m+1)
ui j
true falls (i, j) ∈ E
false sonst
(m)
(m)
(m)
= [ui1 ∧ a1 j ] ∨ [ui2 ∧ a2 j ] ∨ . . . ∨ [uin ∧ an j ]
was genau der Matrixmultiplikation entspricht (∧ entspricht ·, ∨ entspricht +).
6.3.6
Die Ermittlung negativer Zykel
Für die Berechnung der kürzesten Weglängen ui j hat es sich als notwendig erwiesen, dass der Graph
G keine negativen Zykel enthält. Es stellt sich daher die Frage, wie man die Gültigkeit dieser Voraussetzung effizient überprüfen kann.
Satz 6.7 (Test auf negative Zykel) Die folgenden Aussagen sind äquivalent:
1. Der Graph G enthält einen negativen Zykel.
2. Der Graph G enthält einen elementaren negativen Zykel.
(n)
3. Es gibt einen Knoten i mit uii < 0
Insbesondere kann die Existenz negativer Zykel durch Überprüfen der Diagonalelemente in U (n) festgestellt bzw. ausgeschlossen werden.
Beweis: (1) ⇒ (2): Sei Z ein negativer Zykel in G. Ist Z nicht bereits elementar, so durchlaufe man
von einem beliebigen Startknoten aus Z solange, bis der erste Knoten zum zweiten Mal erreicht wird.
Sei i dieser Knoten. Dann zerfällt Z in den elementaren Zykel Z1 von i nach i und den Rest Z2 ,
der ebenfalls ein Zykel ist, aber nicht notwendigerweise elementar. Die Länge von Z ist gleich der
Summe der Längen von Z1 und Z2 . Also muss Z1 oder Z2 negativ sein. Ist es Z1 , so ist ein elementarer
negativer Zykel gefunden. Ist es Z2 , so muss dasselbe Argument ggf. wiederholt werden. Da der
Graph G endlich ist, endet man nach einer endlichen Anzahl von Anwendungen dieses Argumentes
bei einem negativen elementaren Zykel.
(2) ⇒ (3): Sei Z ein elementarer negativer Zykel in G und i ein Knoten in Z. Da Z elementar ist,
enthält Z höchstens n Kanten (sonst würde ein Knoten doppelt vorkommen). Also gilt
(n)
uii ≤ Länge(Z) < 0,
176
KAPITEL 6. ALGORITHMEN AUF ARRAYS
da Z einen Weg von i nach i mit höchstens n Kanten bildet.
(n)
(n)
(3) ⇒ (1): Sei uii < 0. Nach Definition von uii existiert dann ein Weg von i nach i (also ein Zykel
(n)
Z) mit höchstens n Kanten, dessen Länge gleich uii ist. Also ist Z ein negativer Zykel (der trotz der
Beschränkung auf n Kanten nicht elementar zu sein braucht).
(m)
Es existieren also genau dann negative Zykel, sobald ein uii < 0 wird für 1 ≤ m ≤ n. Da dies bei
(m)
der Berechnung von uii festgestellt werden kann, kann man abbrechen, sobald dies zum erstenmal eintritt, und eine entsprechende Fehlermeldung ausgeben. Der einzige Mehraufwand (neben der
Überprüfung) besteht in einer zusätzlichen Iteration. Diese wird notwendig, da bei n Knoten ein elementarer Zykel maximal n Kanten haben kann (vgl. die zweite Richtung des Beweises).
6.3.7
Die Ermittlung kürzester Wege
Bisher haben wir nur die kürzesten Weglängen ui j berechnet. Natürlich möchte man auch einen zugehörigen kürzesten Weg ermitteln.
Dazu nutzt man die folgende, im Beweis von Satz 6.4 durchgeführte Überlegung aus: Bei der Berech(m+1)
(m)
(m+1)
nung der Bellman Gleichungen ui j
= mink [uik + ak j ] entspricht (im Fall ui j
< ∞) der Index
k0 , für den das Minimum angenommen wird, einem Knoten k0 , so dass (k0 , j) die letzte Kante auf
einem kürzesten Weg von i nach j ist. Merkt man sich also im Bellman-Ford Algorithmus jeweils
diesen Index, so lässt sich aus dieser Information ein kürzester Weg rekonstruieren.
Die Realisierung im Programm erfolgt durch ein n × n Array
int[][] tree;
mit der Initialisierung
tree[i][j] :=
i
-1
falls (i, j) ∈ E
.
sonst
In jedem Durchlauf der äußeren Schleife des Algorithmus wird dann tree[i][j] der Wert k zugewiesen, wenn bei u[i][j] eine Änderung auftritt und das zugehörige Minimum bei k angenommen
wird.
Der Algorithmus (in der Version von Programm 6.7) wird dann zu7 :
Programm 6.8 bellmanTree
public double[][] bellmanTree( double[][] a ){
int n = a.length; // number of nodes
7 In
dieser Variante wird nur die tree-Matrix zurückgegeben. Natürlich würde man entweder eine geeignete ErgebnisKlasse definieren, die die Rückgabe von u und tree gleichzeitig erlaubt, oder analog zur Lösung linearer Gleichungssysteme in Abschnitt 6.2.5 Graphen als Objekte ansehen, die entsprechende Felder u und tree aktualisieren.
6.3. KÜRZESTE WEGE IN GERICHTETEN GRAPHEN
177
double[][] u = new double[n][n];
for ( int i = 0; i < n; i++ )
for ( int j = 0; j < n; j++ ) u[i][j] = a[i][j];
for ( int m = 2; m < n; m++ )
for ( int i = 0; i < n; i++ )
for ( int j = 0; j < n; j++ )
for ( int k = 0; k < n; k++ )
if ( u[i][j] > u[i][k] + a[k][j] ) {
u[i][j] = u[i][k] + a[k][j];
tree[i][j] = k;
}
return tree;
}
Das Array tree enthält nach Ausführung der Methode die Information über die kürzesten Wege (falls
keine negativen Zykel gefunden wurden). Um dies genauer zu erläutern, brauchen wir den Begriff des
gerichteten Baums.
Ein gerichteter Baum ist ein Digraph T = (V, E) mit folgenden Eigenschaften:
– Es gibt genau einen Knoten r, in dem keine Kante endet (die Wurzel von T ).
– Zu jedem Knoten i 6= r gibt es genau einen Weg von der Wurzel r zu i.
Dies bedeutet, dass keine zwei Wege in den gleichen Knoten einmünden. Der Graph kann sich ausgehend von der Wurzel also nur verzweigen. Daher kommt auch der Name Baum.
Satz 6.8 Hat G keine negativen Zykel, so ist bei Termination von Programm 6.8 für jeden Knoten i
der Graph Ti := (V, Ei ) mit der Knotenmenge V = {0, . . . , n − 1} und der Kantenmenge
Ei = {(tree[i][j],j) | j = 0, 1, . . . , n − 1; tree[i][j] 6= −1}
ein gerichteter Baum mit i als Wurzel.
Ein Weg von i nach j in Ti ist ein kürzester Weg von i nach j in G. Ti heißt daher auch KürzesterWege-Baum zum Knoten i.
Beweis: Jeder Knoten j 6= i hat in Ti (wenn er von i aus in G erreichbar ist) genau einen Vorgängerknoten,
nämlich tree[i][j]. Also können in Ti keine zwei Kanten in einem Knoten zusammentreffen. Daher existiert zu jedem Knoten j, der von i aus in G erreichbar ist, ein eindeutiger Weg von i nach j
in Ti . Da G keinen negativen Zykel hat, hat i keinen Vorgängerknoten. Also bildet Ti einen Baum mit
Wurzel i.
Sei i = i0 , i1 , . . . , i`+1 = j die Folge der Knoten auf dem Weg von i nach j in Ti . Dann ist
i` = tree[i][ j], i`−1 = tree[i][i` ], . . . , i = tree[i][i2 ] .
178
KAPITEL 6. ALGORITHMEN AUF ARRAYS
- i1
- i2
- p p p
i
ai,i1 ai1 ,i2 ai2 ,i3
- i`
- j
ai`−1 ,i` ai` , j Da die Werte im Array tree gerade bei der Minimumsbildung aktualisiert werden, folgt
ui j = ui,i` + ai` , j
ui,i`
= ui,i`−1 + ai`−1 ,i`
..
.
ui,i2
= ui,i1 + ai1 ,i2
ui,i1
= ai,i1 .
Daher ist ui j = ai,i1 +ai1 ,i2 +. . .+ai`−1 , j und somit der Weg in Ti ein kürzester Weg von i nach j in G.
Beispiel 6.8 (Fortsetzung von Beispiel ??) Für den Graphen aus Beispiel ?? erhält man


−1 1
2
2
3
 4 −1 2
2
3 


1 −1 5
3 
tree = 
 4
.
 4
1
4 −1 3 
4
1
4
5 −1
Als E1 ergibt sich aus der ersten Zeile von tree die Kantenmenge
E1 = {(1, 2), (2, 3), (2, 4), (3, 5)} ,
und somit der in Abbildung 6.20 dargestellte Kürzeste-Wege Baum T1 . Die Kanten aus E1 entsprechen
folgenden Bellman Gleichungen:
(4)
u11 = 0
(4)
(3)
u12 = u11 + a12
(4)
(3)
u13 = u12 + a23
(4)
(3)
u14 = u12 + a24
(4)
(3)
u15 = u13 + a35
6.4
k=1
k=2
k=2
k=3
j=2
j=3
j=4
j=5
Literaturhinweise
Suchverfahren in Arrays werden in nahezu allen Büchern über Entwurf und Analyse von Algorithmen behandelt. Besonders ausführlich gehen [Knu98b, Meh88, OW02] hierauf ein.
Die Lösung linearer Gleichungssysteme ist Gegenstand aller Bücher über lineare Algebra. Ein empfehlenswertes Buch, das lineare Algebra und Numerik verbindet, ist [GMW91].
Die Berechnung kürzester Wege findet sich sowohl in Büchern über Entwurf und Analyse von Algorithmen
(z. B. in [CLRS01, OW02]), als auch in Büchern über Graphenalgorithmen und/oder kombinatorische Optimierung (etwa [Jun94]). Eine sehr gute Darstellung verschiedener kürzeste Wegealgorithmen gibt [Tar83].
179
6.4. LITERATURHINWEISE
−1 2
>
1
3
- 4
2
?
3
1
- 5
Abbildung 6.20: Der Kürzeste-Wege Baum zum Knoten 1 in Beispiel 6.8.