Algoritmer og Datastrukturer/Datalogi C

Forelæsning 15/10-2002

Henning Christiansen

 

 

Grafer og grafalgoritmer

 

 

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.


Grafer, terminologi og baggrund

 

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)

 


Anvendelser

 

Infrastruktur/forsyningslinjer

 

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...

Repræsentation af grafer

 

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:

 

 

 

 

 

 

 

 

 

 



Hvordan var det nu med java.util.LinkedList

 

 

 

Constructors

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.


Repræsentation med lister benytte i lærebogen

(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 }}


Hvorfor virker den?

 

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!

 

Eksempel: Topologisk sortering

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²

 

Algoritme som udskriver knuderne i topologisk sorteret rækkefølge

 

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; }}}

 

 


Eksempel fra bogen

 

 



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, ...

 

Alt dette kan behandles med algoritmer

små forbedringer = store penge

heuristikker til at behandle usikkerheder,

robusthed for forudset og uforudsete ændringer