Algoritmer og datastrukturer Kapittel 11 - Delkapittel 11.2 11.2 Korteste vei i en graf 11.2.1 Dijkstras metode En graf er et system med noder og kanter mellom noder. Grafen kalles rettet Notasjon Verdien til en node X skrives som v(x) og lengden på kanten fra node X til node Y (avstanden fra X til Y) skrives k(x,y). Algoritmens start Velg en node som startnode, farg noden gul og gi den verdien 0. Farg de nodene som er direkte etterfølgere til startnoden røde og gi dem som verdier den avstanden de har fra startnoden. Resten av nodene skal være grå og skal ikke ha noen verdier foreløpig. 1. Hvis det ikke er noen røde noder igjen, så er vi ferdige. Hvis det er røde noder igjen, velg den blant de røde som har minst verdi. Hvis det er flere med samme minste verdi, så spiller det ingen rolle hvem av dem en velger. Kall den valgte noden X. 2. De direkte etterfølgerne til X er enten farget grå, røde eller gule. De som er grå skal farges røde og får som verdier summen av verdien til X og avstandene fra X til nodene (vektene/lengdene på kantene fra X til nodene). F.eks. hvis en grå direkte etterfølger til X heter Y, så skal verdien til Y (v(y)) bli lik v(x) + k(x,y). Hvis en direkte etterfølger til X er rød, så skal den beholde sin farge. Den har, siden den er rød, allerede fått en verdi. La Z være en rød direkte etterfølger til X. Hvis v(x) + k(x,z) er mindre enn v(z), så skal Z få v(x) + k(x,z) som ny verdi! Hvis v(x) + k(x,z) ikke er mindre enn v(z), så skal Z beholde sin gamle verdi. Hvis en direkte etterfølger til X er gul, så skal den ikke endres verken i farge eller verdi. 3. Farg noden X gul og gå så tilbake til 1. Du kan nå sjekke forståelsen din ved å titte på en applett med Dijkstras algoritme. Den bruker fargene omtrent som beskrevet over. Den eneste forskjellen er at startnoden blir farget blå. 11.2.2 Implementasjon av Dijkstras metode I implementasjonen av Dijkstras metode bruker vi en prioritetskø. Nodene farges røde i det øyeblikket de legges inn i prioriteskøen. Kantene og nodene er som definert i underkapittel 11.1.2. Grafens noder ligger i en nodetabell og alle kantene ut fra en node er kjedet sammen. Se underkapittel 11.1.2. import java.util.*; import java.io.*; /////////////////////////// /// /// /// CLASS GRAF /// /// /// /////////////////////////// Side 1 av 14
Side 2 av 14 public class Graf // private konstanter private final int TABDIM = 64; private final int NULLNODE = -1; private final int UENDELIG = Integer.MAX_VALUE/3; //////////////////// // class Kant ////// //////////////////// private static class Kant // Edge private int til; // noden kanten går til private int lengde; // kantens lengde private Kant neste; // peker til neste kant // konstruktør public Kant(int til, int lengde, Kant neste) this.til = til; this.lengde = lengde; this.neste = neste; //////////////////// // class Node ////// //////////////////// private static class Node // Vertex private String nodenavn; // nodens navn private Kant førstekant; // peker til første kant i kantlisten private int avstand; // avstand hit private int forrige; // foregående node i korteste vei hit private int teller; // hjelpevariabel // konstruktør public Node (String nodenavn) this.nodenavn = nodenavn; førstekant = null; // en tabell av noder private Node[] node; // En avbildning (map) holder orden på hvilke noder (nodenavn) // grafen består og hvilke nummer nodene er tildelt private Map h; private int antallnoder; // antall noder i gafen
Side 3 av 14 /////////////////////////////////////// // PRIVATE HJELPEMETODER /////////////////////////////////////// // privat hjelpemetode private void utvidtabell() Node[] temp = node; node = new Node[2*antallNoder]; // dobler System.arraycopy(temp,0,node,0,antallNoder); // privat hjelpemetode private void nullstill() Node p = null; for (int i = 0; i < antallnoder; i++) p = node[i]; p.avstand = UENDELIG; p.forrige = NULLNODE; p.teller = 0; // en rekursiv hjelpemetode som bruker teknikken "dybde først" // den kalles fra en off. metode med samme navn - se nedenfor private void kortestvei1(int fra, int lengdehit) Kant k = node[fra].førstekant; if (lengdehit + k.lengde < node[k.til].avstand) node[k.til].avstand = lengdehit + k.lengde; node[k.til].forrige = fra; kortestvei1(k.til,lengdehit + k.lengde); ////////////////////////////////////////////// /// OFFENTLIGE METODER ////////////////////////////////////////////// public Graf() // konstruktør node = new Node[TABDIM]; // nodetabellen h = new HashMap(); // holder orden på nodene antallnoder = 0; // i utgangspunktet 0 noder
Side 4 av 14 /// nynode ////////////////////////////// public boolean nynode(string nodenavn) Object o = h.get(nodenavn); // Er navn registrert tidligere? if (o!= null) return false; // duplikater ikke tillatt h.put(nodenavn,new Integer(antallNoder)); // Før vi legger noden i nodetabellen må vi sjekke om den // er full! Hvis ja, så utvider vi tabellen! if (antallnoder == node.length) utvidtabell(); // utvider node[antallnoder++] = new Node(nodenavn); // ny node return true; // ny node lagt inn ///// nykant ////////////////////////////////// // Offentlig metode som legger inn nye kanter i grafen public boolean nykant(string fra, String til, int lengde) Object o = h.get(fra); if (o == null) return false; // ingen node med dette navnet int franode = ((Integer)o).intValue(); o = h.get(til); if (o == null) return false; // ingen node med dette navnet int tilnode = ((Integer)o).intValue(); Kant k = node[franode].førstekant; if (k.til == tilnode) // vi oppaterer lengden på en eksistrende kant k.lengde = lengde; return true; // dette er ingen kant mellom disse nodene fra før node[franode].førstekant = new Kant(tilnode,lengde,node[franode].førsteKant); return true; /// skrivgraf ///////////////////////////////////7
Side 5 av 14 // en metode som skriver ut for hver node hvilke andre noder // det går en kant til. public void skrivgraf(string fil) throws IOException PrintWriter ut = new PrintWriter(new FileWriter(fil)); Kant k = null; for (int i = 0; i < antallnoder; i++) ut.print("kanter fra " + node[i].nodenavn + " til: "); k = node[i].førstekant; if (k == null) ut.print(" ingen kanter ut fra denne noden"); else ut.print(node[k.til].nodenavn + " (len. " + k.lengde + ") "); ut.println(); ut.close(); /// skrivvei /////////////////////////////////////// // Skriver korteste vei fra en startnode til tilnode. // Utskriften går til ut public void skrivvei(string til, PrintWriter ut) Object o = h.get(til); if (o == null) return; // ukjent navn Stakk s = new TabellStakk(); int nodenr = ((Integer)o).intValue(); int avstand = node[nodenr].avstand; if (avstand == UENDELIG) ut.println("det er ingen vei til " + node[nodenr].nodenavn); while (nodenr!= NULLNODE) s.legginn(new Integer(nodenr)); nodenr = node[nodenr].forrige; while (!s.tom()) ut.print(((integer)(s.taut())).intvalue() + " - ");
Side 6 av 14 ut.println(" lengde = " + avstand); ////////////////////////////////////////////// /// OFFENTLIGE METODER ////////////////////////////////////////////// public Graf() // konstruktør node = new Node[TABDIM]; // nodetabellen h = new HashMap(); // holder orden på nodene antallnoder = 0; // i utgangspunktet 0 noder /// nynode ////////////////////////////// public boolean nynode(string nodenavn) Object o = h.get(nodenavn); // Er navn registrert tidligere? if (o!= null) return false; // duplikater ikke tillatt h.put(nodenavn,new Integer(antallNoder)); // Før vi legger noden i nodetabellen må vi sjekke om den // er full! Hvis ja, så utvider vi tabellen! if (antallnoder == node.length) utvidtabell(); // utvider node[antallnoder++] = new Node(nodenavn); // ny node return true; // ny node lagt inn ///// nykant ////////////////////////////////// // Offentlig metode som legger inn nye kanter i grafen public boolean nykant(string fra, String til, int lengde) Object o = h.get(fra); if (o == null) return false; // ingen node med dette navnet int franode = ((Integer)o).intValue(); o = h.get(til); if (o == null) return false; // ingen node med dette navnet int tilnode = ((Integer)o).intValue(); Kant k = node[franode].førstekant; if (k.til == tilnode) // vi oppaterer lengden på en eksistrende kant k.lengde = lengde; return true;
Side 7 av 14 // dette er ingen kant mellom disse nodene fra før node[franode].førstekant = new Kant(tilnode,lengde,node[franode].førsteKant); return true; /// skrivgraf ///////////////////////////////////7 // en metode som skriver ut for hver node hvilke andre noder // det går en kant til. public void skrivgraf(string fil) throws IOException PrintWriter ut = new PrintWriter(new FileWriter(fil)); Kant k = null; for (int i = 0; i < antallnoder; i++) ut.print("kanter fra " + node[i].nodenavn + " til: "); k = node[i].førstekant; if (k == null) ut.print(" ingen kanter ut fra denne noden"); else ut.print(node[k.til].nodenavn + " (len. " + k.lengde + ") "); ut.println(); ut.close(); /// skrivvei /////////////////////////////////////// // Skriver korteste vei fra en startnode til tilnode. // Utskriften går til ut public void skrivvei(string til, PrintWriter ut) Object o = h.get(til); if (o == null) return; // ukjent navn Stakk s = new TabellStakk(); int nodenr = ((Integer)o).intValue(); int avstand = node[nodenr].avstand; if (avstand == UENDELIG)
Side 8 av 14 ut.println("det er ingen vei til " + node[nodenr].nodenavn); while (nodenr!= NULLNODE) s.legginn(new Integer(nodenr)); nodenr = node[nodenr].forrige; while (!s.tom()) ut.print(((integer)(s.taut())).intvalue() + " - "); ut.println(" lengde = " + avstand); /// graffrafil /////////////////////////////////////// // en metode som leser en fil med grafdata og bygger opp // grafen. Filen må være organisert på en bestemt måte void graffrafil(string filnavn) throws IOException BufferedReader inn = new BufferedReader(new FileReader(filnavn)); StringTokenizer str = null; String tekst = inn.readline(); int antall = Integer.parseInt(tekst); int antkanter = 0; String fra = null; String til = null; for (int i = 0; i < antall; i++) str = new StringTokenizer(inn.readLine()); fra = str.nexttoken(); nynode(fra); antkanter = Integer.parseInt(str.nextToken()); for (int j = 0; j < antkanter; j++) til = str.nexttoken(); nynode(til); nykant(fra,til,integer.parseint(str.nexttoken())); /// Dijkstra ////////////////////////////////////////// // en metode som bruker Dijkstras algoritme for korteste vei public void Dijkstra(String franode) // en lokal hjelpeklasse class Avstand
Side 9 av 14 int nodenr; int avstand; Avstand(int nodenr, int avstand) this.nodenr = nodenr; this.avstand = avstand; // en lokal komparator for class Avstand class AvstandKomparator implements Comparator public int compare(object a, Object b) return ((Avstand)b).avstand - ((Avstand)a).avstand; Object o = h.get(franode); // sjekker om noden eksisterer if (o == null) return; // ukjent navn nullstill(); PrioritetsKø q = new HeapPrioritetsKø(new AvstandKomparator()); Kant k = null; // hjelpevariabel Node denne = null; // hjelpevariabel Node etterfølger = null; final int GRÅ = 0; final int GUL = 1; final int RØD = 2; int nodenr = ((Integer)o).intValue(); denne = node[nodenr]; // startnoden denne.avstand = 0; denne.teller = RØD; q.legginn(new Avstand(nodenr,0)); while (!q.tom()) nodenr = ((Avstand)q.taUt()).nodenr; denne = node[nodenr]; if (denne.teller!= RØD) continue; // hopper over denne k = denne.førstekant; int tempavstand = 0; // hjelpevariabel
Side 10 av 14 tempavstand = denne.avstand + k.lengde; etterfølger = node[k.til]; if (tempavstand < etterfølger.avstand) etterfølger.avstand = tempavstand; etterfølger.forrige = nodenr; etterfølger.teller = RØD; q.legginn(new Avstand(k.til,tempavstand)); // denne.teller = GUL; // ferdig med denne // while (!q.tom()) // Dijkstra /// kortestevei1 - rekursiv dybde først /////////// public void kortestvei1(string franode) // denne metoden kaller opp en rekursiv hjelpemetode // - se ovenfor Object o = h.get(franode); if (o == null) return; // ukjent nodenavn nullstill(); int nodenr = ((Integer)o).intValue(); node[nodenr].avstand = 0; kortestvei1(nodenr,0); /// kortestevei2 /////////////////////////////////// // en metode som ved hjelp av en stakk bruker "dybde først" public void kortestvei2(string franode) Object o = h.get(franode); if (o == null) return; // ukjent nodenavn nullstill(); Stakk s = new TabellStakk(); int nodex = ((Integer)o).intValue(); int nodey; // hjelpevariabel node[nodex].avstand = 0; node[nodex].teller = 1; // 1 betyr på stakk s.legginn(o);
Side 11 av 14 Kant k; try while (!s.tom()) nodex = ((Integer)(s.taUt())).intValue(); node[nodex].teller = 0; // 0 betyr ikke på stakk k = node[nodex].førstekant; nodey = k.til; if (node[nodex].avstand + k.lengde < node[nodey].avstand) node[nodey].avstand = node[nodex].avstand + k.lengde; node[nodey].forrige = nodex; if (node[nodey].teller == 0) // 0 betyr ikke på stakk s.legginn(new Integer(nodeY)); node[nodey].teller = 1; // 1 betyr på stakk catch (TomStakk t) /// kortestevei3 ///////////////////////////////////// // en metode som ved hjelp av en kø bruker "bredde først" public void kortestvei3(string franode) Object o = h.get(franode); if (o == null) return; // ukjent navn nullstill(); Kø q = new TabellKø(); int nodex = ((Integer)o).intValue(); int nodey; node[nodex].avstand = 0; node[nodex].teller = 1; // 1 betyr på kø q.legginn(o); Kant k;
Side 12 av 14 while (!q.tom()) nodex = ((Integer)(q.taUt())).intValue(); node[nodex].teller = 0; // 0 betyr ikke på kø k = node[nodex].førstekant; nodey = k.til; if (node[nodex].avstand + k.lengde < node[nodey].avstand) node[nodey].avstand = node[nodex].avstand + k.lengde; node[nodey].forrige = nodex; if (node[nodey].teller == 0) // 0 betyr ikke på kø q.legginn(new Integer(nodeY)); node[nodey].teller = 1; // 1 betyr på kø /// kortestuvektetvei ///////////////////////////////// // En metode som bruker "bredde først" for å finne korteste // vei når vi antar at alle kantene har samme lengde public void kortestuvektetvei(string fra) Object o = h.get(fra); if (o == null) return; // ukjent navn nullstill(); Kø q = new TabellKø(); int nodenr = ((Integer)o).intValue(); Node franode = node[nodenr]; franode.avstand = 0; q.legginn(new Integer(nodenr)); Kant k; Node tilnode; while (!q.tom()) nodenr = ((Integer)(q.taUt())).intValue();
Side 13 av 14 franode = node[nodenr]; k = franode.førstekant; tilnode = node[k.til]; if (tilnode.avstand == UENDELIG) tilnode.avstand = franode.avstand + 1; tilnode.forrige = nodenr; // nodenr er nr. til franode q.legginn(new Integer(k.til)); /// asykliskkortestvei /////////////////////////// // En metode som bruker "toplogisk sortering" for å finne // korteste vei i en graf som ikke har sykler public boolean asykliskkortestvei(string fra) Object o = h.get(fra); if (o == null) return false; // ukjent navn nullstill(); Kø q = new TabellKø(); Kant k; // hjelpevariabel int nodenr = ((Integer)o).intValue(); node[nodenr].avstand = 0; // avstand i startnode settes til 0 // variablen teller i hver node skal settes til antallet // inngående kanter til noden for (nodenr = 0; nodenr < antallnoder; nodenr++) k = node[nodenr].førstekant; while(k!= null) node[k.til].teller++; // noder som ikke har inngående kanter legges i køen - det må // finnes minst en slik node siden grafen ikke har sykler
Side 14 av 14 for (nodenr = 0; nodenr < antallnoder; nodenr++) if (node[nodenr].teller == 0) q.legginn(new Integer(nodenr)); // hovedløkken starter int i; for (i = 0;!q.tom(); i++) nodenr = ((Integer)(q.taUt())).intValue(); k = node[nodenr].førstekant; if (--node[k.til].teller == 0) q.legginn(new Integer(k.til)); // vi behandler kun noder som har vært "besøkt" - i // begynnelsen er det bare startnoden som har vært "besøkt" if (node[nodenr].avstand < UENDELIG) if (node[k.til].avstand > node[nodenr].avstand + k.lengde) node[k.til].avstand = node[nodenr].avstand + k.lengde; node[k.til].forrige = nodenr; return i == antallnoder; // slutt på class Graf