710 likes | 897 Views
Grafer. Anvendelser III. Plan. Grafer Terminologi Repræsentation Traversering Korteste vej Topologisk sortering Problemkompleksitet og afgørlighed. A. I. H. G. B. C. D. E. F. Grafer. En graf er et nyttigt, abstrakt begreb.
E N D
Grafer Anvendelser III
Plan • Grafer • Terminologi • Repræsentation • Traversering • Korteste vej • Topologisk sortering • Problemkompleksitet og afgørlighed
A I H G B C D E F Grafer • En graf er et nyttigt, abstrakt begreb. • Intuitiv definition: En graf er en mængde af objekter og forbindelser imellem disse. • Matematisk definition: En graf G = (V,E) består af en endelig mængde af knuder, V, og en endelig mængde af kanter, E, hvor hver kant forbinder to af knuderne (E VxV). V = {A, B, C, D, E, F, G, H, I} E = {(A,B), (A,C), (A,F), (A,G), (D,E), (D,F), (E,F), (E,G), (H,I)}
Anvendelsesområder • Alt hvad der involverer relationer imellem • objekter kan modelleres ved hjælp af en graf Trafiknetværk: Knuder: byer, vejkryds Kanter: veje Elektriske kredsløb: Knuder: komponenter Kanter: ledninger Organiske molekyler: Knuder: atomer Kanter: bindinger
Anvendelsesområder(fortsat) Programsystemer: Knuder: metoder Kanter: metode A kan kalde metode B Objektorienteret design (UML-diagrammering): Knuder: klasser/objekter Kanter: nedarvning, aggregering eller associering Projektplanlægning: Knuder: delopgaver Kanter: præcedenser (delopgave A skal udføres før delopgave B)
En kants to knuder kaldes endeknuder for kanten. I H I H Terminologi Hvis rækkefølgen af en kants endeknuder har betydning, kaldes kanten for orienteret. Dette angives på den grafiske repræsentation ved at kanten forsynes med en pil. Endeknuderne kaldes da for henholdsvis begyndelsesknuden og slutknuden. En orienteret graf er en graf, hvor alle kanter er orienterede. En ikke-orienteret graf er en graf, hvor ingen kanter er orienterede.
A I H G B C D E F Terminologi (fortsat) • En vej er en liste af knuder, hvor successive knuder er forbundet med en kant. • En vej kaldes simpel, hvis ingen knude gentages. • En cykel er en vej, der er simpel, bortset fra at den første og sidste knude er den samme. Cykler: FDEF, AFEGA og AFDEGA.
A I H G B C D E F Terminologi (fortsat) • En graf G’ = (V’,E’) er en delgraf af G = (V,E), hvis V’Vog E’E. • En graf kaldes sammenhængende, hvis der for enhver knude findes en vej til enhver anden knude. • En graf, der ikke er sammenhængende, består af sammenhængende delgrafer, også kaldet komponenter. 2 komponenter
Udspændende træ for G Graf G Terminologi (fortsat) • Et træ er en sammenhængende graf uden cykler. • En mængde af disjunkte træer kaldes en skov. • Et udspændende træ for en graf G er en delgraf af G, der indeholder alle grafens knuder, og som udgør et træ.
Terminologi (fortsat) • En graf, hvor enhver knude er forbundet med enhver anden knude, kaldes for komplet. • [ for en ikke-orienteret graf: |E| = |V|*(|V|-1)/2) ] • En tyndgraf er en graf med relativt få kanter (i forhold til |V|) • En tætgraf er en graf med relativt mange kanter (i forhold til |V|) • En vægtet graf er en graf, hvor kanterne er forsynet med talværdier, kaldet vægte. [ vægtene repræsenterer normalt omkostninger ]
Basale grafproblemer • Veje: Er der en vej fra knude A til knude B? • Cykler: Indeholder grafen en cykel? • Sammenhæng (udspændende træ): Er der for hver knude en vej til enhver anden knude? • 2-sammenhæng: Vil grafen blive usammenhængende, hvis en af knuderne og de tilstødende kanter fjernes? • Planaritet: Kan grafen tegnes, uden at to kanter krydser hinanden?
Basale grafproblemer(fortsat) • Korteste vej: Hvilken vej er den korteste fra knude A til knude B? • Længste vej: Hvilken vej er den længste fra knude A til knude B? • Minimalt udspændende træ: Hvad er den billigste måde at forbinde alle knuder? • Hamilton-cykel: Er der en cykel, som indeholder samtlige knuder? • Den rejsende sælgers problem: Hvilken Hamilton-cykel er den billigste?
Repræsentation af grafer • Grafer er abstrakte matematiske objekter. Algoritmer må arbejde med konkrete repræsentationer. • Mange mulige repræsentationer. Valget er bestemt af algoritmer og graftyper (tynde/tætte, vægtede/uvægtede, orienterede/ikke-orienterede). • I det følgende gennemgås 3 repræsentationer: (1) kantmængde (2) nabomatrix (3) nabolister
class Graph { Set edges; } • class Edge { • Vertex source, dest; • double cost; • } class Vertex { String name; } (1) Kantmængde-repræsentation
A I H G B C D E F class Graph { // unweighted boolean[][] adjMatrix; } class Graph { // weighted double[][] adjMatrix; } (2) Nabomatrix-repræsentation • A B C D E F G H I • A 0 1 1 0 0 1 1 0 0 • B 1 0 0 0 0 0 0 0 0 • C 1 0 0 0 0 0 0 0 0 • D 0 0 0 0 1 1 0 0 0 • E 0 0 0 1 0 1 1 0 0 • F 1 0 0 1 1 0 0 0 0 • G 1 0 0 0 1 0 0 0 0 • H 0 0 0 0 0 0 0 0 1 • I 0 0 0 0 0 0 0 1 0
A I H • A: • B: • C: • D: • E: • F: • G: • H: • I: B G C D E F G F C B A A F E G F D A E D A E I H (3) Naboliste-repræsentation
class Vertex { String name; // Vertex name List adj; // List of edges } • class Edge { • Vertex dest; // Second vertex of edge • double cost; // Edge cost • } Naboliste-repræsentation class Graph { Map vertexMap; // Map String to Vertex }
Sammenligning af repræsentationer • Pladskrav: Kantmængde: O(|E|) • Nabomatrix: O(|V|2) • Nabolister: O(|V| + |E|)
Repræsentation har betydning for algoritmers effektivitet Værste tilfælde: Er der en kant fra knude A til knude B? Kantmængde: O(|E|) Nabomatrix: O(1) Nabolister: O(|V|) Er der en kant fra knude A? Kantmængde: O(|E|) Nabomatrix O(|V|) Nabolister: O(1)
Traversering af grafer • Mål: at besøge enhver knude i en graf. • Dybde-først-traversering: • Rekursiv algoritme: • * Mærk alle knuder “ubesøgt”. • * Besøg startknuden. • * Ved besøg af en knude, v: Mærk knuden “besøgt” Besøg (rekursivt) alle ubesøgte knuder, der er forbundet med v. Løser nogle simple grafproblemer: sammenhæng, cykler Basis for løsning af nogle vanskelige grafproblemer: 2-sammenhæng
class Vertex { String name; List adj; boolean visited; voidvisit() { visited = true; Iterator itr = adj.iterator(); while (itr.hasNext()) { Vertex w = ((Edge) itr.next()).dest; if (!w.visited) w.visit(); } } } Implementering af dybde-først-traversering Kompleksitet: O(|E|)
A A A B G B G B G C C C D D D E E E F F F A: F C B G B: A C: A D: F E E: G F D F: A E D G: E A A A A A B B G B G G C C C B G C D D D E E E D E F F F F Dybde-først-traversering af en komponent
1 A 2 6 7 F B C 3 E 4 5 G D Dybde-først-traversering af en sammenhængende graf repræsenteret ved nabolister kræver tid proportional med |E| Dybde-først-traversering af en komponent udgør et dybde-først-træ
void traverse(Vertex startVertex) { Stackstack = new ArrayStack(); stack.push(startVertex); startVertex.visited = true; while (!stack.isEmpty()) { Vertex v = (Vertex) stack.topAndPop(); Iterator itr = v.adj.iterator(); while (itr.hasNext()) { Vertex w = ((Edge) itr.hasNext()).dest; if (!w.visited) { stack.push(w); w.visited = true; } } } } Ikke-rekursiv dybde-først-traversering • Benyt en eksplicitstak af knuder.
void traverse(Vertex startVertex) { Queue queue= newListQueue(); queue.enqueue(startVertex); startVertex.visited = true; while (!queue.isEmpty()) { Vertex v = (Vertex)queue.dequeue(); Iterator itr = v.adj.iterator(); while (itr.hasNext()) { Vertex w = ((Edge) itr.next()).dest; if (!w.visited) { queue.enqueue(w); w.visited = true; } } } } Bredde-først-traversering • Hvis stakken erstattes med en kø, foretages bredde-først-traversering.
A A A B G C B G C B G C D E A: F C B G B: A C: A D: F E E: G F D F: A E D G: E A D E D E F F F F C B G C B G E D B G E D A A A A B G C B G C B G B G C C D E D E D D E E F F F D F E D G E D Bredde-først-traversering af en komponent
1 A 2 3 4 5 C B G F 6 7 E D Bredde-først-traversering af en sammenhængende graf repræsenteret ved nabolister kræver tid proportional med |E| Bredde-først- traversering af en komponent udgør et bredde-først-træ
aktuel aktuel start start Dybde-først Bredde-først Dybde-først versus bredde-først
void traverse(Vertex startVertex) { PriorityQueuepq = new SpecialPriorityQueue(); pq.update(startVertex, priority); while (!pq.isEmpty()) { Vertex v = (Vertex)pq.deleteMin(); v.visited = true; Iterator itr = v.adj.iterator(); while (itr.hasNext()) { Vertex w = ((Edge) itr.next()).dest; if (!w.visited) pq.update(w, priority); } } } Bedste-først-traversering • Hvis køen erstattes med en prioritetskø, foretages bedste-først-traversering.
SpecialPriorityQueue • pq.update(Object obj, int prio) Hvis obj ikke findes i pq, så indsæt det med prioritet prio. Ellers, opdater dets prioritet til prio. • pq.deleteMin() Fjern fra pq det objekt, der har mindst prioritet. Returner dette objekt. Denne abstrakte datatype findes ikke i Java. Men vi kan benytte klassen PairingHeap fra lærebogen.
class PairingHeap { • Positioninsert(Comparable x); • Comparable deleteMin(); • void decreaseKey(Position pos, Comparable newVal); • boolean isEmpty(); • } Pairing Heap
class Vertex implements Comparable { String name; boolean visited; double priority; Position heapPosition; Vertex prev; public int compareTo(Object rhs) { Vertex v = (Vertex) rhs; return priority < v.priority ? -1 : priority == v.priority ? 0 : 1; } public void reset() { visited = false; priority = Graph.INFINITY; heapPosition = null; prev = null; } } Ændringer i class Vertex
void traverse(Vertex startVertex) { PriorityQueue pq = new PairingHeap(); startVertex.priority = priority; startVertex.heapPosition = pq.insert(startVertex); while (!pq.isEmpty()) { Vertex v = (Vertex)pq.deleteMin(); v.visited = true; Iterator itr = v.adj.iterator(); while (itr.hasNext()) { Vertex w = ((Edge) itr.next()).dest; if (!w.visited && priority < w.priority) { w.priority = priority; w.prev = v; if (w.heapPosition == null) w.heapPosition = pq.insert(w); else pq.decreaseKey(w.heapPosition, w); } } } }
Kompleksitet af bedste-først-søgning • Tidskompleksiteten for bedste-først-søgning i en graf er O((|V|+|E|)log|V|). • |V| kald af deletemin • |E| kald af enten insertellerdecreaseKey • Hvis pq er en hob, så er tiden for et kald af såvel deletemin, insertsom decreaseKey O(log|V|).
Korteste-vej-problemet Find den korteste vej fra knude A til knude B. Uvægtede korteste vej (minimer antallet af kanter fra A til B): Anvend bredde-først-traversering. Traverser grafen startende i A ved hjælp af en kø. Vægtede korteste vej (bestem den billigste vej fra A til B): Anvend bedste-først-traversering: Dijkstras algoritme. Traverser grafen startende i A ved hjælp af en prioritetskø, idet prioriteten for hver ubesøgt knude er omkostningen af den hidtil billigste vej fra A til knuden. Virker dog kun i en graf med ikke-negative vægte.
A A I I H H 2 2 6 6 6 6 1 1 3 3 G G 1 1 1 1 B B C C 1 2 1 1 2 2 2 2 4 4 1 4 4 1 K K 5 5 J J 3 1 1 E E D D 2 2 5 2 2 4 4 3 3 1 2 1 2 2 2 F F M M 2 2 L L 1 1 A A I I H H 2 2 6 6 6 6 1 1 3 3 G G 1 1 1 1 B B C C 1 1 4 2 2 2 2 4 1 4 4 1 K K 5 5 J J 3 3 1 1 E E D D 2 2 5 4 2 2 4 4 3 3 1 2 1 2 2 F F M M 2 2 L L 1 1 4 Eksempel på bestemmelseaf korteste-vej-træ
A A I I H H 2 2 6 6 6 6 1 1 3 3 G G 1 1 1 1 B B C C 1 1 2 2 2 2 4 4 1 4 4 1 7 K K 5 5 J J 1 1 E E D D 2 2 4 4 2 2 4 4 3 3 1 2 1 2 F F M M 2 2 L L 1 1 5 4 A A I I H H 2 2 6 6 5 5 1 1 3 3 G G 1 1 1 1 B B C C 1 1 2 2 1 2 2 4 4 1 4 4 7 7 J K K 5 5 J 1 1 E E D D 2 2 2 2 4 4 3 3 1 2 1 2 F F M M 2 2 L L 1 1 5
8 8 A A I I H H 2 2 6 6 1 1 3 3 G G 1 1 1 1 B B C C 1 1 1 1 2 2 2 2 4 4 4 4 6 7 K K J J 1 5 5 1 E E D D 2 2 2 2 4 1 4 3 3 1 2 2 F F M M 2 2 L L 1 1 8 8 8 A A I I H H 2 2 6 6 1 1 3 3 G G 1 1 1 1 B B C C 1 1 1 1 2 2 2 2 4 4 4 4 K K J J 5 5 1 1 E E D D 2 2 1 2 2 1 4 4 3 3 2 2 F F M M 2 2 L L 1 1 A I H 2 6 1 3 G 1 1 B C 1 1 2 2 4 4 K J 5 1 E D 2 1 2 4 3 2 F M 2 L 1
private boolean dijkstra(Vertex startVertex) { clearAll(); PriorityQueue pq = new PairHeap(); startVertex.priority = 0; startVertex.heapPosition = pq.insert(startVertex); while (!pq.isEmpty()) { Vertex v = (Vertex)pq.deleteMin(); v.visited = true; Iterator itr = v.adj.iterator(); while (itr.hasNext()) { Edge e = (Edge) itr.next(); Vertex w = e.dest; double cvw = e.cost; if (cvw < 0) return false; if (!w.visited &&v.priority + cvw< w.priority) { w.priority =v.priority + cvw; w.prev = v; if (w.heapPosition == null) w.heapPosition = pq.addItem(w); else pq.decreaseKey(w.heapPosition, w); } } } return true; }
Minimalt udspændende træ Hvis udtrykket v.priority + cvwoveralt erstattes med cvw, vil det minimalt udspændende træ blive bestemt.
clearAll private void clearAll() { Iterator itr = vertexMap.values().iterator(); while (itr.hasNext()) ((Vertex) itr.next()).reset(); }
private void printPath(Vertex destVertex) { if (!destVertex.visited) System.out.println(destVertex.name + " is unreachable"); else { destVertex.printPath(); System.out.println(" cost: " + destVertex.priority); } System.out.println(); } public void printPath() { if (prev != null) { prev.printPath(); System.out.print(" to "); } System.out.print(name); } Udskrivning af korteste vej
class Graph public class Graph { public Vertex addVertex(String vertexName) {...} public void addEdge(String source, String dest, double cost) {...} public boolean processRequest(BufferedReader in) {...} public static void main(String[] args) {...} private static final double INFINITY = DOUBLE.MAX_VALUE; private HashMap vertexMap = new HashMap(); private void printPath(Vertex destVertex) {...} private void clearAll() {...} private boolean dijkstra(Vertex startVertex) {...} }
main public static void main(String[] args) { BufferedReader graphFile; try { graphFile = new BufferedReader( new FileReader(args[0])); // ... fortsættes } catch(IOException e) { System.err.println(e); } }
main(forsat) Graph g = new Graph(); String line; while ((line = graphFile.readLine()) != null) { StringTokenizer st = new StringTokenizer(line); try { // read source, destination and cost if (st.countTokens() != 3) { System.err.println("Skipping bad line " + line); continue; } String source = st.nextToken(); String dest = st.nextToken(); int cost = Integer.parseInt(st.nextToken()); g.addEdge(source, dest, cost); } catch (NumberFormatException e) { System.err.println("Skipping bad line " + line); } } BufferedReader in = new BufferedReader( new FileReader(System.in)); while (g.processRequest(in)) ;
public void addEdge(String source, String dest, double cost) { getVertex(source).adj.add(new Edge(getVertex(dest), cost)); } public Vertex getVertex(String vertexName) { Vertex v = (Vertex) vertexMap.get(vertexName); if (v == null) { v = new Vertex(vertexName); vertexMap.put(vertexName, v); } return v; } addEdge
processRequest public boolean processRequest(BufferedReader in) { String sourceName, destName; try { System.out.println("Enter start vertex:"); if ((sourceName = in.readLine()) == null) return false; System.out.println("Enter destination vertex:"); if ((destName = in.readLine()) == null) return false; } catch (IOException e) { System.out.println("Error: " + e); return false; } Vertex source, dest; if ((source = (Vertex) vertexMap.get(sourceName)) == null) { System.out.println("Start vertex not in graph"); return true; } if ((dest = (Vertex) vertexMap.get(destName)) == null) { System.out.println("Destination vertex not in graph"); return true; } if (dijkstra(source)) printPath(dest); else System.out.println("Dijkstra fails - negative edge"); return true; }
Kompleksitet Dijkstras algoritme kan implementeres, så den har tidskompleksitet O((|E| + |V|)log(|V|). Hvis alle vægte er ens, f.eks. 1, eksisterer en algoritme med tidskompleksitet O(|E|+|V|). Hvis vægte må være negative, eksisterer en algoritme med tidskompleksitet O(|E||V|). Hvis grafen ikke indeholder cykler, eksisterer en algoritme med tidskompleksitet O(|E|).
DAGs • En orienteret graf uden cykler kaldes for en DAG (Directed Acyclic Graph). • En DAG kan f.eks. benyttes til at modellere et aktivitetsnetværk, hvor pilene angiver, at en aktivitets færdiggørelse er en forudsætning for en andens påbegyndelse. • Eksempel: kursusforudsætninger.
aktivitetsnetværk D C H A B I F G E Netværksplanlægning • aktivitet forgænger(e) varighed (uger) • A konstruer lagerstyringsmodel - 4 • B implementer lagerstyringsprogram A 13 • C konstruer prognosemodel - 4 • D implementer prognoseprogram C 15 • E indsaml data - 12 • F design database A 4 • G implementer database E, F 2 • H oplær personale B, D 2 • I afprøv system G, H 2