Forelæsning 15/10-2002
Henning Christiansen
Hvad
mener vi med en graf?
NEJ!
Graf:
En matematisk abstraktion over ting som er ³logisk² forbundet med hinanden
Dagens
program:
·
Terminologi og anvendelser
·
Repræsentationer af
grafer
·
Algoritme til korteste
vej
·
Algoritme til korteste
vej ‹ med vægte (=omkostninger)
·
Kort om
netværksplanlægning
o
Princippet
o
Skitser af algoritmer,
topologisk sortering, kritisk vej m.v.
Udspringer
af matematisk disciplin Grafteori
Leonard
Euler 1736: ³De 7 broer i Königsberg² (nu Kaliningrad)
³Er
det muligt at gå en tur, så man passerer alle broer, men ikke mere end én
gang²?
Svaret
er nej... beviset baseret på abstraktion = grafer:
(Interesserede
kan finde bevis på nettet eller i en bog på biblioteket)
Grafteori:
Stort og spændende område, mange spændende sætninger ‹ og anvendelser!
Terminologi:
En graf består af
·
knuder (vertices); en eller anden (abstrakt mængde)
{1,2,3,4}
·
kanter (edges), en binær relation over knuder.
{(1,2),
(2,3), (2,2), ...}
Forskellige
typer grafer:
Ikke-orienteret
graf:
Kanter
uden retning, dvs. (1,2) ‰ (2,1)
Orienteret
graf:
Kanter
har retning, dvs. (1,2) ‚ (2,1), dvs. Pile
Vi
taler om ind-kanter og ud-kanter for given knude
Vægtet
graf (typisk orienteret, men også
ikke-orienteret):
Hver
kant har en vægt (‰pris‰omkostning‰...)
Tætte
og tynde grafer:
Tæt: Alle kan se alle,
dvs. antal kanter ‰ O(antal-knuder2)
Tynd: Hver knude har lille antal kanter til/fra
dvs.
antal kanter ‰ O(antal-knuder)
Eksempel
2D-strukturer uden krydsende kanter
Fænomener
i grafer:
En
vej (path) fra K1 til Kn:
En sekvens af kanter (K1,K2),(K2,K3),...,(Kn-1,Kn).
En
kreds/cyklus (cycle): En vej fra K
til K.
Klassifikation
af grafer:
·
Acyklisk: ingen kredse
·
Sammenhængende: For vilkårlige knuder K1, K2, altid vej fra K1 til K2
eller omvendt
·
Skov: Aldrig mere end én vej mellem to knuder
·
Træ: en sammenhængende skov :)
o
NB: et træ har altid én ³top-knude²
(burde hedde dets rod, men nu er det altså etableret)
Knuder
= Byer/Forbrugere-Producenter
/Computere-Servere-Printere
Kanter
= Veje/Lastbilsruter/Togstrækninger/Vandrør
/el-ledninger/datanetforbindelser
Interessante
spørgsmål:
·
Er grafen sammenhængende
(ellers øer)
·
Billigste vej mellem to
knuder
·
Billigste vej som
involverer givet punktmængde
Sammenhængsgrad
‰ et mål for forsyningssikkerhed:
·
hvor mange forskellige
veje mellem to vilkårlige punkter
Rejseplanlægning:
Knuder
= stationer/stoppesteder/adresser
Kanter
= trafikforbindelser; pris = rejsetid
Ekstra
komplikation: Køreplan => ventetid
Planlægning
af projekter:
Knuder
= Produkter (=aktivitet afsluttet)
Kanter
= Hvilke ting forudsætter hvilke andre
Hvor
lang tid tager aktivitet X?
Programstrukturer:
Kaldegrafer:
Hvilke metoder kalder hvilke?
·
Fejlsøgning, optimering
på compiletime
UML: Hvilke klasser benytter hvilke og hvordan (f.eks. nedarver fra, indeholder objekter fra, ...)
·
Vedligeholdelse af
programmer...
Matrice:
bool [ 5][ 5] graf
00d 0 |
1ss1 1 |
0 0 |
1
1 |
0 0 |
0
0 |
0
0 |
1 1 |
0
0 |
0 0 |
0
0 |
0
0 |
0 0 |
0
0 |
0 0 |
0 0 |
0 0 |
1 1 |
0
0 |
0 0 |
0
0 |
0 0 |
0 0 |
1
1 |
0 0 |
0
1
0
2
3
4
Lister:
For
hver knude, en liste over dens ud-kanter
0: [1, [3, null]]
1: [2, null]
2: null
3: [2, null]
4: [4, null]
Eksempel
på listerepræsentation af vægtede grafer:
LinkedList()
Constructs
an empty list.
boolean add(Object o)
Appends
the specified element to the end of this list.
Object getFirst()
Returns
the first element in this list.
Object getLast()
Returns
the last element in this list.
int indexOf(Object o)
Returns
the index in this list of the first occurrence
of the specified element, or -1 if
the List does not
contain this element.
int lastIndexOf(Object o)
Returns
the index in this list of the last occurrence of the
specified element, or -1 if the list
does not contain this element.
ListIterator listIterator(int index)
Returns
a list-iterator of the elements in this list
(in proper sequence), starting at
the specified position in the list.
Object remove(int index)
Removes
the element at the specified position in this list.
boolean remove(Object o)
Removes
the first occurrence of the specified element
in this list.
Object removeFirst()
Removes
and returns the first element from this list.
Object removeLast()
Removes
and returns the last element from this list.
Osv.
osv.
(her
renset for diverse ekstrainformation og fejlcheck ved indlæsning)
€
Attributter har her tekstlige navne som benyttes ved indlæs og udskriv
class
Edge // start vertex given by position in list (below)
{public
Vertex
dest; // Second
vertex in Edge
public double cost; // Edge cost
public Edge( Vertex d, double c ) {dest
= d; cost = c;}}
class
Vertex
{
public String
name; // Vertex name
public List adj; // Adjacent vertices //liste
over ud-kanter
public Vertex(String
nm){name=nm;adj=new LinkedList( );}}
public
class Graph
{private
Map vertexMap = new HashMap( ); // String to Vertex
private Vertex getVertex( String
vertexName )
{Vertex v = (Vertex)
vertexMap.get( vertexName );
if(v==null)
{v=new
Vertex(vertexName); vertexMap.put(vertexName,v);}
return
v;}
public void addEdge( String sourceName,
String destName, double cost)
{Vertex
v=getVertex(sourceName); Vertex w=getVertex(destName);
v.adj.add( new
Edge( w, cost ) );}
public
static void main( String [ ] args )
{Graph g = new Graph( );
FileReader fin
= new FileReader( args[0] ); //file name
BufferedReader
graphFile = new BufferedReader( fin );
// Read the
edges and insert
String line;
while( ( line =
graphFile.readLine( ) ) != null )
{StringTokenizer st = new StringTokenizer( line );
String
source = st.nextToken( ); String
dest = st.nextToken(
);
int
cost =
Integer.parseInt( st.nextToken( ) );
g.addEdge(
source, dest, cost );};
System.out.println( "File read..." );
System.out.println(
g.vertexMap.size( ) + " vertices" );
.....
}
Korteste vej mellem to
knuder
‹
beregner korteste veje fra startknude til alle andre knuder
a
la princippet i dynamisk programmering;
vi
har måske brug for at gå via andre knuder
Version
1: Orienterede grafer uden vægte; vejlængde = antal kanter.
Datastruktur:
hver
knude tilføjes hjælpevariable:
double dist; //hidtil bedst fundne afstand fra start;
init=INFINITY
Vertex previous; // Previous vertex on shortest path;
init=null
Metode
clearAll, som initialiserer alle hjælpevariable i graf
global
kø ³q² over knuder som skal besøges.
Princip
i algoritme:
Givet
knude kalde start.
Først
besøges start;
Så
besøges alle knuder i afstand 1 fra start
‹
dvs. alle som kan nås via én kant fra start
Dernæst
alle knuder i afstand 2 fra start;
‹
dvs. alle som kan nås via én kant fra dem i forrige skridt,
dog
ikke dem som allerede har været besøgt!
osv.
Implementation i Java
public
void unweighted( String startName )
{ clearAll( );
Vertex
start = (Vertex) vertexMap.get( startName );
LinkedList q = new LinkedList( );
q.addLast( start ); start.dist = 0;
while( !q.isEmpty( ) )
{
Vertex v = (Vertex) q.removeFirst( );
for(
Iterator itr = v.adj.iterator( ); itr.hasNext( ); )
{ Edge e = (Edge) itr.next(
);
Vertex w = e.dest;
if( w.dist == INFINITY )
{ w.dist = v.dist + 1;
w.prev = v;
q.addLast( w );}}}}
Sluttilstand:
Alle
knuder med påskrevet afstand fra start.
Incl.
dem som ikke kan nås fra start: ³INFINITY²
Korteste vej med vægte,
³Dijkstras algoritme²
Princip,
som før men ekstra komplikation i og med
·
ikke nok med besøgt/ikke
besøgt
·
vægt for knude kan
tælles ned flere gange
10 5
15 14
12 2
Vi
skelner mellem
·
færdigbehandlede knuder
(³visited²)
·
ufærdige naboer til
færdigbehandlede knuder (³unseen²)
For at generalisere algoritmen fra før bruger vi en prioritetskø, som indeholder den sidste slags.
Alle knuder tildeles fra start afstand fra startknude = uendelig.
Abstrakt
algoritme:
indsæt start-knude med afstand=0 i kø;
while( der-er-flere-kø ) {
lad v = knuden i køen med mindst afstand... v fjernes fra køen;
for alle v¹s naboer w, som ikke allerede er færdigbehandlede,
{
hvis v.afstand + længde(v--> w) < w.afstand så
{ w.afstand= v.afstand + længde(v--> w);
sæt w i kø (hvis
den ikke allerede er der; }
notér v som færdigbehandlet }}
Lad
os formulere et matematisk induktionsbevis over antal gennemløb af
while-løkken:
Hypotesen:
·
Afstand for alle
færdigbehandlede er korrekt, og
·
Afstand for ³ufærdige
nabo² w = lgd. af korteste vej i
delgraf som udover w kun indeholder færdigbehandlede.
Induktionsstart,
lad os vælge når startknuden er færdigbehandlet:
c1 c1
start 0
c2 c2
c4 c3 c3
c4
OK!
Antag nu at algoritmen har kørt korrekt op til skridt k:
v
Skridt
k+1: Lad v være ³hvid² knude med mindst vægt:
v¹s
naboers vægte justeres (måske)
v
forfremmes til færdigbehandlet
1.
Redegøre for at v¹s
afstand D er den mindst mulige. Men
kan der være en anden vej fra start til v, som er billigere? Per
induktionshypotese del 2, kan der ikke være en anden vej
grå->grå->...->grå->v. Ergo må en sådan vej P være af formen ....
-> hvid -> grå. Kald denne hvide v1 og dens noterede afstand D1. Udfra
valget af v må vi have D1 „ D. Kan længden af P = D1 + noget-positivt så være
mindre end D?? Nej vel? Modstrid!
2.
Redegøre for at de
nye afstande for v¹s naboer er korteste vej i delgrafen bestående af de grå ‹
nu udvidet med v. Hvis en sådan nabo
w får talt sin vægt ned, så er det netop fordi ny vej via v er kortere end
tidligere
grå->grå->...->grå-> w.
Q.E.D.
Bogens
algoritme i Java:
Prioritetskøen
implementeres lidt snusket med BinaryHeap... (NB: ikke i Javas std. pakker)
BinaryHeap
er en datastruktur,
·
med underligt navn,
·
hvis indmad vi ikke
kender
men
som implementerer metoderne
·
insert(Comparable x)
·
Comparable deleteMin( )
hvor
³Min² er bestemt ved objekternes compareTo.
Problem
ved denne BinaryHeap: Objekternes rækkefølge bestemmes på
indsættelsestidspunktet.
Dvs.
hvis objekt ændres undervejs, så det påvirker resultat af compareTo, opfører
deleteMin sig ikke korrekt!!!
Teknik
(læs: hæsligt bøjet søm):
·
I algoritmen ændres
knudernes ³afstand² altid nedad
·
Vi vælger altid knude
med mindst ³afstand²
·
Når knude er valgt, sættes
den ³scratch² felt = 1 (init=0).
·
Hvis en knude med
³scratch =1² fremkommer ved deleteMin, ignoreres den.
Følgende
³Path² objekter indsættes i BinaryHeap¹en_
class
Path implements Comparable
{
public Vertex
dest; // w
public double cost; // d(w)
public Path( Vertex d, double c )
{dest = d; cost = c;}
public int compareTo( Object rhs
)
{ double otherCost =
((Path)rhs).cost;
return cost < otherCost ? -1 : cost > otherCost ? 1 : 0;}}
Dijkstras
algoritme for korteste vej i vægtede grafer ved brug af ³BinaryHeap²:
public
void dijkstra( String startName )
{ PriorityQueue pq = new
BinaryHeap( );
Vertex
start = (Vertex) vertexMap.get( startName );
clearAll(
);
pq.insert( new Path( start, 0 ) ); start.dist = 0;
int
nodesSeen = 0;
while(
!pq.isEmpty( ) && nodesSeen < vertexMap.size( ) )
{Path vrec = (Path) pq.deleteMin( );
Vertex v =
vrec.dest;
if(
v.scratch != 0 ) continue; // already processed v
v.scratch =
1; nodesSeen++;
for( Iterator itr = v.adj.iterator( ); itr.hasNext( ); )
{ Edge e = (Edge) itr.next( );
Vertex w = e.dest;
double cvw = e.cost;
if( w.dist > v.dist + cvw )
{ w.dist = v.dist +cvw;
w.prev = v;
pq.insert( new Path( w, w.dist ) );}}}}
Acykliske grafer
Typisk
anvendelse: Planlægning
0
1
2
3
0:
tagspær
1:
lægter
2:
tagsten
3:
rejsegilde
A
--> B: A forudsætter B
Acykliske
grafer nemmere at programmere om: man kommer aldrig tilbage til samme sted!
at
ordne knuderne i en graf i rækkefølge, så afhængigheder er overholdt.
³tagspærene
skal på før lægterne²
³hvorvidt
du sætter vinduer i før eller efter tagstenene er ligegyldigt²
G
= vor graf;
while(flere-knuder-tilbage)
{
find
knude V med nul indgående kanter; //findes fordi G acyklisk
udskriv
V;
fjern
v og alle dens udgående kanter fra G; }
Bogens
algoritme i Java (udsnit af fig. 14.32)
(fig.
14.32 blander top.sort. med en kortestevej algoritme)
Princip: hver knudes
³scratch² benyttes som tæller for antal ind-kanter
der
bruges en kø til at holde de knuder som på givet tidspunkt
har
0 ind-kanter
LinkedList
q = new LinkedList( );
//
Compute the indegrees
Collection
vertexSet = vertexMap.values( );
for(
Iterator vsitr = vertexSet.iterator( ); vsitr.hasNext( ); {
Vertex v
= (Vertex) vsitr.next( );
for(
Iterator witr = v.adj.iterator( ); witr.hasNext( ); )
(
(Edge) witr.next( ) ).dest.scratch++;}
//
Enqueue vertices INITIALLY of indegree zero
for(
Iterator vsitr = vertexSet.iterator( ); vsitr.hasNext( ); ) {
Vertex v = (Vertex) vsitr.next( );
if( v.scratch == 0 ) {q.addLast( v ); UDSKRIV
v; } ;
//
Loop: Remove 0-nodes from queue and count down in-degrees
while(!q.isEmpty(
)) {
Vertex v = (Vertex)
q.removeFirst( );
for( Iterator itr =
v.adj.iterator( ); itr.hasNext( ); ) {
Edge e =
(Edge) itr.next( );
Vertex w
= e.dest;
if(
--w.scratch == 0 ) {q.addLast( w );; UDSKRIV v; }}}
Korteste vej for acykliske grafer
Tricket:
Knuderne besøges i rækkefølge svarende til topologisk
sortering
Dvs. når en knude besøges, er alle dens forgængere
besøgt
(og
med garanti ikke ændrer sin korteste vej)
Algoritmen for topologisk sortering med 4 ekstra linjer i stedet for udskrift:
LinkedList
q = new LinkedList( );
//
Compute the indegrees
Collection
vertexSet = vertexMap.values( );
for(
Iterator vsitr = vertexSet.iterator( ); vsitr.hasNext( ); {
Vertex v
= (Vertex) vsitr.next( );
for(
Iterator witr = v.adj.iterator( ); witr.hasNext( ); )
(
(Edge) witr.next( ) ).dest.scratch++;}
//
Enqueue vertices INITIALLY of indegree zero
for(
Iterator vsitr = vertexSet.iterator( ); vsitr.hasNext( ); ) {
Vertex v = (Vertex) vsitr.next( );
if( v.scratch == 0 ) q.addLast( v )
;
//
Loop: Remove 0-nodes from queue and count down in-degrees
while(!q.isEmpty(
)) {
Vertex v = (Vertex)
q.removeFirst( );
for( Iterator itr =
v.adj.iterator( ); itr.hasNext( ); ) {
Edge e =
(Edge) itr.next( );
Vertex w
= e.dest;
if(
--w.scratch == 0 ) q.addLast( w );
if(
v.dist == INFINITY ) continue; //
on ³island²; avoid overflow
if(
w.dist > v.dist + cvw )
{ w.dist = v.dist + cvw;
w.prev = v;}}}
Skitse af netværksplanlægning
Udgangspunkt: En aktivitetsgraf
Knuder:
Aktivitet m. forventet tidsforbrug
Kanter:
Forudsætningsforhold
Obs:
Omkostninger på knuder passer ikke ind i vores hidtidige model.
I
stedet: En begivenhedsgraf:
Knuder:
aktivitet-start, aktivitet-slut
Kanter.
Forudsætningsforhold, Aktivitet m. tidsforbrug
Hvor
lang tid tager projektet hvis arbejdet planlægges optimalt?
Kritiske
vej = længste vej fra start til slut
Algoritmer a la korteste vej kan tilpasses ³længste vej² ‰
for
hver aktivitet, tidligst mulige slutpunkt
Yderligere
algoritmer til:
Senest mulige starttidspunkt
Tidligts mulige sluttidspunkt
Hvor meget der kan slækkes
Yderligere
forfining af modellen med ressourcer:
Aktivitet
A kræver 1 gravko m. fører, 1 rendegraver m. fører
Aktivitet
E kræver 1 rendegraver m. fører, 1 bobcat, 5 gartnere,
3
river, 2 skovle
Vi har ialt følgende
ressourcer:
1
gravko, 2 rendegraver, 1 byggekran, ...
små forbedringer = store penge
heuristikker til at behandle usikkerheder,
robusthed for forudset og uforudsete ændringer