Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Letný semester, prednáška č. 8: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
(Vytvorená stránka „== Oznamy == == Triedy pre grafy z minulej prednášky == == Pokračovanie úvodu do grafov == === Vytvorenie grafu === Metóda <tt>readGraph</tt> triedy <tt>Trieda<…“)
 
 
(74 medziľahlých úprav od rovnakého používateľa nie je zobrazených.)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
 +
 +
* Na začiatku zajtrajších cvičení bude okrem niekoľkých bežných úloh k cvičeniam zverejnená aj druhá bonusová domáca úloha, za ktorú bude možné získať 2 body navyše. Odovzdať ju bude možné ''do 16. apríla 2024, 9:50'' &ndash; čiže do začiatku deviatych cvičení.
 +
* Budúci týždeň počas cvičení &ndash; čiže ''v utorok 16. apríla od 9:50 do 11:20'' &ndash; bude prebiehať tretí test zameraný na látku z prvých ôsmich týždňov. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6.
  
 
== Triedy pre grafy z minulej prednášky ==
 
== Triedy pre grafy z minulej prednášky ==
  
== Pokračovanie úvodu do grafov ==
+
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
/**
 +
*  Rozhranie implementovane reprezentaciami orientovanych grafov o vrcholoch 0, 1, ..., n-1 pre nejake prirodzene n.
 +
*  Aj neorientovane grafy budu povazovane za specialny pripad orientovanych grafov; toto rozhranie tak bude
 +
*  implementovane vsetkymi triedami reprezentujucimi grafy.
 +
*/
 +
public interface DirectedGraph {
 +
    /**
 +
    * Metoda, ktora vrati pocet vrcholov reprezentovaneho grafu.
 +
    *
 +
    * @return Pocet vrcholov grafu.
 +
    */
 +
    int getVertexCount();
 +
 
 +
    /**
 +
    * Metoda, ktora zisti, ci graf obsahuje vrchol s cislom vertex.
 +
    * @param vertex Vrchol, ktoreho existencia sa ma zistit.
 +
    * @return      Vrati true prave vtedy, ked graf obsahuje vrchol s cislom vertex.
 +
    */
 +
    boolean hasVertex(int vertex);
 +
 
 +
    /**
 +
    * Metoda, ktora vrati pocet orientovanych hran reprezentovaneho grafu.
 +
    *
 +
    * @return Pocet orientovanych hran grafu.
 +
    */
 +
    int getDirectedEdgeCount();
 +
 
 +
    /**
 +
    * Metoda, ktora zisti, ci v grafe existuje hrana medzi danou dvojicou vrcholov.
 +
    *
 +
    * @param from Pociatocny vrchol.
 +
    * @param to  Koncovy vrchol.
 +
    * @return    Vrati true prave vtedy, ked v grafe existuje hrana z vrcholu from do vrcholu to.
 +
    */
 +
    boolean hasEdge(int from, int to);
 +
 
 +
    /**
 +
    * Metoda, ktora vrati vsetkych naslednikov daneho vrcholu -- cize vsetky vrcholy, do ktorych vedie z daneho vrcholu
 +
    * orientovana hrana. Pre neorientovane grafy tak tato metoda vzdy vrati vsetkych susedov daneho vrcholu.
 +
    *
 +
    * @param vertex Lubovolny vrchol grafu.
 +
    * @return      Naslednici vrcholu vertex ako instancia typu Iterable&lt;Integer&gt;.
 +
    */
 +
    Iterable<Integer> outgoingEdgesDestinations(int vertex);
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
public class DirectedEdge {
 +
    private int from, to;
 +
 
 +
    public DirectedEdge(int from, int to) {
 +
        this.from = from;
 +
        this.to = to;
 +
    }
 +
 
 +
    public int getFrom() {
 +
        return from;
 +
    }
 +
 
 +
    public int getTo() {
 +
        return to;
 +
    }
 +
 
 +
    @Override
 +
    public boolean equals(Object o) {
 +
        if (o == null) {
 +
            return false;
 +
        }
 +
        return this.getClass() == o.getClass() && from == ((DirectedEdge) o).from && to == ((DirectedEdge) o).to;
 +
    }
 +
 
 +
    @Override
 +
    public int hashCode() {
 +
        return Integer.valueOf(from).hashCode() + 31 * Integer.valueOf(to).hashCode();
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
import java.util.*;
 +
 
 +
/**
 +
* Trieda reprezentujuca orientovany graf pomocou zoznamov naslednikov jednotlivych jeho vrcholov.
 +
*/
 +
public class SuccessorListsDirectedGraph implements DirectedGraph {
 +
    /**
 +
    * Pre kazdy vrchol si pamatame zoznam jeho naslednikov.
 +
    */
 +
    private ArrayList<ArrayList<Integer>> successorLists;
 +
 
 +
    /**
 +
    * Pocet orientovanych hran v grafe (velkost grafu).
 +
    */
 +
    private int directedEdgeCount;
 +
 
 +
    /**
 +
    * Konstruktor, ktory dostane ako argumenty pocet vrcholov grafu (t. j. jeho rad), ako aj vsetky hrany grafu.
 +
    *
 +
    * @param vertexCount  Rad grafu, cize pocet jeho vrcholov.
 +
    * @param directedEdges Zoskupenie pozostavajuce zo vsetkych orientovanych hran grafu.
 +
    */
 +
    public SuccessorListsDirectedGraph(int vertexCount, Collection<? extends DirectedEdge> directedEdges) {
 +
        if (vertexCount <= 0) {
 +
            throw new IllegalArgumentException("Nonpositive vertex count.");
 +
        }
 +
        successorLists = new ArrayList<>();
 +
        for (int i = 0; i <= vertexCount - 1; i++) {
 +
            successorLists.add(new ArrayList<>());
 +
        }
 +
        directedEdgeCount = 0;
 +
        for (DirectedEdge e : directedEdges) {
 +
            if (!hasVertex(e.getFrom()) || !hasVertex(e.getTo())) {
 +
                throw new IllegalArgumentException("Edge incident to a nonexistent vertex.");
 +
            }
 +
            if (!hasEdge(e.getFrom(), e.getTo())) {
 +
                successorLists.get(e.getFrom()).add(e.getTo());
 +
                directedEdgeCount++;
 +
            } else {
 +
                throw new IllegalArgumentException("Multiple edges connecting the same pair of vertices.");
 +
            }
 +
        }
 +
    }
 +
 
 +
    @Override
 +
    public int getVertexCount() {
 +
        return successorLists.size();
 +
    }
 +
 
 +
    @Override
 +
    public boolean hasVertex(int v) {
 +
        return v >= 0 && v <= getVertexCount() - 1;
 +
    }
 +
 
 +
    @Override
 +
    public int getDirectedEdgeCount() {
 +
        return directedEdgeCount;
 +
    }
 +
 
 +
    @Override
 +
    public boolean hasEdge(int from, int to) {
 +
        if (!hasVertex(from) || !hasVertex(to)) {
 +
            throw new IllegalArgumentException("Nonexistent vertex.");
 +
        }
 +
        return successorLists.get(from).contains(to);
 +
    }
 +
 
 +
    @Override
 +
    public Iterable<Integer> outgoingEdgesDestinations(int vertex) {
 +
        if (!hasVertex(vertex)) {
 +
            throw new IllegalArgumentException("Nonexistent vertex.");
 +
        }
 +
        // Nasledujucim prikazom vratime nemodifikovatelny pohlad na zoznam naslednikov vrcholu vertex
 +
        return Collections.unmodifiableList(successorLists.get(vertex));
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
import java.util.*;
 +
 
 +
/**
 +
* Trieda reprezentujuca orientovany graf pomocou matice susednosti.
 +
*/
 +
public class AdjacencyMatrixDirectedGraph implements DirectedGraph {
 +
    /**
 +
    * Matica susednosti.
 +
    */
 +
    private boolean[][] adjacencyMatrix;
 +
 
 +
    /**
 +
    * Pocet orientovanych hran v grafe (velkost grafu).
 +
    */
 +
    private int directedEdgeCount;
 +
 
 +
    /**
 +
    * Konstruktor, ktory dostane ako argumenty pocet vrcholov grafu (t. j. jeho rad), ako aj vsetky hrany grafu.
 +
    *
 +
    * @param vertexCount  Rad grafu, cize pocet jeho vrcholov.
 +
    * @param directedEdges Zoskupenie pozostavajuce zo vsetkych orientovanych hran grafu.
 +
    */
 +
    public AdjacencyMatrixDirectedGraph(int vertexCount, Collection<? extends DirectedEdge> directedEdges) {
 +
        if (vertexCount <= 0) {
 +
            throw new IllegalArgumentException("Nonpositive vertex count.");
 +
        }
 +
        adjacencyMatrix = new boolean[vertexCount][vertexCount];
 +
        directedEdgeCount = 0;
 +
        for (DirectedEdge e : directedEdges) {
 +
            if (!hasVertex(e.getFrom()) || !hasVertex(e.getTo())) {
 +
                throw new IllegalArgumentException("Edge incident to a nonexistent vertex.");
 +
            }
 +
            if (!hasEdge(e.getFrom(), e.getTo())) {
 +
                adjacencyMatrix[e.getFrom()][e.getTo()] = true;
 +
                directedEdgeCount++;
 +
            } else {
 +
                throw new IllegalArgumentException("Multiple edges connecting the same pair of vertices.");
 +
            }
 +
        }
 +
    }
 +
 
 +
    @Override
 +
    public int getVertexCount() {
 +
        return adjacencyMatrix.length;
 +
    }
 +
 
 +
    @Override
 +
    public boolean hasVertex(int v) {
 +
        return v >= 0 && v <= getVertexCount() - 1;
 +
    }
 +
 
 +
    @Override
 +
    public int getDirectedEdgeCount() {
 +
        return directedEdgeCount;
 +
    }
 +
 
 +
    @Override
 +
    public boolean hasEdge(int from, int to) {
 +
        if (!hasVertex(from) || !hasVertex(to)) {
 +
            throw new IllegalArgumentException("Nonexistent vertex.");
 +
        }
 +
        return adjacencyMatrix[from][to];
 +
    }
 +
 
 +
    @Override
 +
    public Iterable<Integer> outgoingEdgesDestinations(int vertex) {
 +
        if (!hasVertex(vertex)) {
 +
            throw new IllegalArgumentException("Nonexistent vertex.");
 +
        }
 +
        List<Integer> a = new ArrayList<>();
 +
        for (int i = 0; i <= getVertexCount() - 1; i++) {
 +
            if (adjacencyMatrix[vertex][i]) {
 +
                a.add(i);
 +
            }
 +
        }
 +
        return Collections.unmodifiableList(a);
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
/**
 +
*  Rozhranie implementovane reprezentaciami neorientovanych grafov o vrcholoch 0, 1, ..., n-1 pre nejake prirodzene n.
 +
*/
 +
public interface UndirectedGraph extends DirectedGraph {
 +
    /**
 +
    * Metoda, ktora vrati pocet neorientovanych hran reprezentovaneho grafu.
 +
    *
 +
    * @return Pocet neorientovanych hran grafu.
 +
    */
 +
    int getUndirectedEdgeCount();
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
import java.util.*;
 +
 
 +
public class UndirectedEdge {
 +
    private Set<Integer> incidentVertices;
 +
 
 +
    public UndirectedEdge(int u, int v) {
 +
        incidentVertices = new HashSet<>();
 +
        incidentVertices.add(u);
 +
        incidentVertices.add(v);
 +
    }
 +
 
 +
    public Set<Integer> getIncidentVertices() {
 +
        return Collections.unmodifiableSet(incidentVertices);
 +
    }
 +
 
 +
    @Override
 +
    public boolean equals(Object o) {
 +
        if (o == null) {
 +
            return false;
 +
        }
 +
        return this.getClass() == o.getClass() && incidentVertices.equals(((UndirectedEdge) o).getIncidentVertices());
 +
    }
 +
 
 +
    @Override
 +
    public int hashCode() {
 +
        return incidentVertices.hashCode();
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 
 +
import java.util.*;
  
=== Vytvorenie grafu ===
+
public class Edges {
  
Metóda <tt>readGraph</tt> triedy <tt>Trieda</tt> uvedenej nižšie prečíta pomocou danej inštancie triedy <tt>Scanner</tt> reprezentáciu grafu a vytvorí z nej graf typu určeného nasledujúcim parametrom. Argument pre typ grafu je pritom ''vymenovaného typu'' <tt>GraphType</tt> (o vymenovaných typoch sa možno dočítať viac [https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html tu]).
+
    /**
 +
    * Metoda, ktora prerobi zoskupenie neorientovanych hran na prislusny zoznam orientovanych hran.
 +
    * @param undirectedEdges Zoznam neorientovanych hran.
 +
    * @return                Prislusny zoznam orientovanych hran.
 +
    */
 +
    public static List<DirectedEdge> undirectedToDirectedEdges(Collection<? extends UndirectedEdge> undirectedEdges) {
 +
        ArrayList<DirectedEdge> result = new ArrayList<>();
 +
        for (UndirectedEdge e : undirectedEdges) {
 +
            ArrayList<Integer> vertices = new ArrayList<>(e.getIncidentVertices());
 +
            if (vertices.size() == 1) {
 +
                result.add(new DirectedEdge(vertices.get(0), vertices.get(0)));
 +
            } else {
 +
                result.add(new DirectedEdge(vertices.get(0), vertices.get(1)));
 +
                result.add(new DirectedEdge(vertices.get(1), vertices.get(0)));
 +
            }
 +
        }
 +
        return Collections.unmodifiableList(result);
 +
    }
 +
}
 +
</syntaxhighlight>
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
package graphs;
 
package graphs;
  
public enum GraphType {
+
import java.util.*;
     DIRECTED_SUCCESSOR_LISTS, DIRECTED_ADJACENCY_MATRIX, UNDIRECTED_ADJACENCY_LISTS, UNDIRECTED_ADJACENCY_MATRIX
+
 
 +
/**
 +
* Trieda reprezentujuca neorientovany graf pomocou zoznamov susedov jednotlivych jeho vrcholov.
 +
*/
 +
public class AdjacencyListsUndirectedGraph extends SuccessorListsDirectedGraph implements UndirectedGraph {
 +
     private int undirectedEdgeCount;
 +
 
 +
    public AdjacencyListsUndirectedGraph(int vertexCount, Collection<? extends UndirectedEdge> undirectedEdges) {
 +
        super(vertexCount, Edges.undirectedToDirectedEdges(undirectedEdges));
 +
        undirectedEdgeCount = undirectedEdges.size();
 +
    }
 +
 
 +
    @Override
 +
    public int getUndirectedEdgeCount() {
 +
        return undirectedEdgeCount;
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 20: Riadok 357:
 
package graphs;
 
package graphs;
  
 +
import java.util.*;
 +
 +
/**
 +
* Trieda reprezentujuca neorientovany graf pomocou matice susednosti.
 +
*/
 +
public class AdjacencyMatrixUndirectedGraph extends AdjacencyMatrixDirectedGraph implements UndirectedGraph {
 +
    private int undirectedEdgeCount;
 +
 +
    public AdjacencyMatrixUndirectedGraph(int vertexCount, Collection<? extends UndirectedEdge> undirectedEdges) {
 +
        super(vertexCount, Edges.undirectedToDirectedEdges(undirectedEdges));
 +
        undirectedEdgeCount = undirectedEdges.size();
 +
    }
 +
 +
    @Override
 +
    public int getUndirectedEdgeCount() {
 +
        return undirectedEdgeCount;
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="java">
 +
package graphs;
 +
 +
import java.io.*;
 
import java.util.*;
 
import java.util.*;
  
 
public class Trieda {
 
public class Trieda {
 
     /**
 
     /**
     * Metoda, ktora precita textovu reprezentaciu grafu pozostavajucu z poctu vrcholov n, poctu hran m a z m dvojic
+
    * Metoda vypise do daneho vystupneho prudu pocet vrcholov a hran orientovaneho grafu, ako aj vsetky dvojice
    * vrcholov udavajucich jednotlive hrany a vytvori z nej graf urceneho typu.
+
    * vrcholov tvoriace orientovane hrany grafu.
 +
    *
 +
    * @param g  Graf, pre ktory sa vypis realizuje.
 +
    * @param out Vystupny prud, do ktoreho sa vypis realizuje.
 +
    */
 +
    public static void printDirectedGraph(DirectedGraph g, PrintStream out) {
 +
        int n = g.getVertexCount();
 +
        out.println(n + " " + g.getDirectedEdgeCount());
 +
        for (int u = 0; u <= n - 1; u++) {
 +
            for (int v : g.outgoingEdgesDestinations(u)) {
 +
                out.println(u + " " + v);
 +
            }
 +
        }
 +
    }
 +
 
 +
    /**
 +
     * Metoda, ktora precita textovu reprezentaciu orientovaneho grafu pozostavajucu z poctu vrcholov n, poctu hran m
 +
    * a z m dvojic vrcholov udavajucich jednotlive orientovane hrany a vytvori podla nej graf urcenej implementacie.
 
     *
 
     *
     * @param scanner   Scanner, z ktoreho sa reprezentacia grafu cita.
+
     * @param scanner       Skener, z ktoreho sa reprezentacia grafu cita.
     * @param graphType Typ vytvaraneho grafu.
+
     * @param implementation Implementacia vytvaraneho grafu (zoznamy naslednikov, alebo matica susednosti).
     * @return         Vytvoreny graf.
+
     * @return               Vytvoreny graf.
 
     */
 
     */
     public static Graph readGraph(Scanner scanner, GraphType graphType) {
+
     public static DirectedGraph readDirectedGraph(Scanner scanner, GraphImplementation implementation) {
 
         int n = scanner.nextInt();
 
         int n = scanner.nextInt();
 
         int m = scanner.nextInt();
 
         int m = scanner.nextInt();
         List<Edge> edges = new ArrayList<>();
+
         List<DirectedEdge> edges = new ArrayList<>();
 
         for (int i = 1; i <= m; i++) {
 
         for (int i = 1; i <= m; i++) {
             edges.add(new Edge(scanner.nextInt(), scanner.nextInt()));
+
             edges.add(new DirectedEdge(scanner.nextInt(), scanner.nextInt()));
 
         }
 
         }
         Graph g = null;
+
         DirectedGraph g = null;
         switch (graphType) {
+
         switch (implementation) {
             case DIRECTED_SUCCESSOR_LISTS:
+
             case LISTS:
                 g = new SuccessorListsGraph(n, edges);
+
                 g = new SuccessorListsDirectedGraph(n, edges);
 
                 break;
 
                 break;
             case DIRECTED_ADJACENCY_MATRIX:
+
             case MATRIX:
                 g = new AdjacencyMatrixGraph(n, edges);
+
                 g = new AdjacencyMatrixDirectedGraph(n, edges);
 
                 break;
 
                 break;
             case UNDIRECTED_ADJACENCY_LISTS:
+
        }
 +
        return g;
 +
    }
 +
 
 +
    /**
 +
    * Metoda, ktora precita textovu reprezentaciu neorientovaneho grafu pozostavajucu z poctu vrcholov n, poctu hran m
 +
    * a z m dvojic vrcholov udavajucich jednotlive neorientovane hrany a vytvori podla nej graf urcenej implementacie.
 +
    *
 +
    * @param scanner        Skener, z ktoreho sa reprezentacia grafu cita.
 +
    * @param implementation Implementacia vytvaraneho grafu (zoznamy naslednikov, alebo matica susednosti).
 +
    * @return              Vytvoreny graf.
 +
    */
 +
    public static UndirectedGraph readUndirectedGraph(Scanner scanner, GraphImplementation implementation) {
 +
        int n = scanner.nextInt();
 +
        int m = scanner.nextInt();
 +
        List<UndirectedEdge> edges = new ArrayList<>();
 +
        for (int i = 1; i <= m; i++) {
 +
            edges.add(new UndirectedEdge(scanner.nextInt(), scanner.nextInt()));
 +
        }
 +
        UndirectedGraph g = null;
 +
        switch (implementation) {
 +
             case LISTS:
 
                 g = new AdjacencyListsUndirectedGraph(n, edges);
 
                 g = new AdjacencyListsUndirectedGraph(n, edges);
 
                 break;
 
                 break;
             case UNDIRECTED_ADJACENCY_MATRIX:
+
             case MATRIX:
 
                 g = new AdjacencyMatrixUndirectedGraph(n, edges);
 
                 g = new AdjacencyMatrixUndirectedGraph(n, edges);
 
                 break;
 
                 break;
Riadok 58: Riadok 457:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Volanie metódy <tt>readGraph</tt> potom môže vyzerať napríklad nasledovne:
+
== Prehľadávanie (orientovaného alebo neorientovaného) grafu do hĺbky ==
<syntaxhighlight lang="java">
 
Graph g = readGraph(scanner, GraphType.DIRECTED_SUCCESSOR_LISTS);
 
</syntaxhighlight>
 
  
=== Porovnanie reprezentácií grafov ===
+
=== Existencia cesty medzi dvojicou vrcholov ===
Majme orientovaný graf s ''n'' vrcholmi a ''m'' hranami &ndash; počet hrán ''m'' teda môže byť od 0 po ''n<sup>2</sup>''. V závislosti od použitej reprezentácie grafu sa líši ako časová zložitosť jednotlivých operácií na grafoch, tak aj pamäťová zložitosť samotnej tejto reprezentácie. Napríklad:
 
* Pamäť potrebná na uloženie matice susednosti grafu je vždy rádovo veľkosti ''n<sup>2</sup>''. Pri reprezentácii pomocou zoznamov následníkov resp. susedov je veľkosť reprezentácie grafu rádovo ''n+m''. Hlavne pre riedke grafy (s menším počtom hrán) je teda reprezentácia pomocou zoznamov pamäťovo efektívnejšia.
 
* Operácia <tt>existsEdge</tt> sa pre grafy reprezentované maticou susednosti vykoná v konštantnom čase. Pre grafy reprezentované zoznamami následníkov resp. susedov môže byť zložitosť tejto operácie až lineárna v závislosti od počtu vrcholov grafu (je potrebné prejsť celý zoznam susedov jedného vrchola, ktorý môže obsahovať až ''n'' rôznych vrcholov).
 
* Naopak vytvorenie zoznamu následníkov resp. susedov grafu v metóde <tt>outgoingEdgesDestinations</tt> je efektívnejšie pri reprezentácii pomocou zoznamov.
 
  
=== Ďalšie varianty grafov ===
+
Riešme teraz nasledujúci problém: pre danú dvojicu vrcholov ''u'' a ''v'' nejakého (orientovaného alebo neorientovaného) grafu potrebujeme zistiť, či sú spojené ''sledom'' &ndash; t. j. či sa dá medzi nimi prejsť pomocou postupne na seba nadväzujúcich hrán (počet týchto hrán môže byť aj nulový, takže z každého vrcholu triviálne vedie sled do seba samého). Existencia sledu medzi dvoma vrcholmi je očividne ekvivalentná existencii ''cesty'' &ndash; čiže sledu, v ktorom sa žiaden vrchol neopakuje. Keby totiž ''najkratší'' sled medzi danými dvoma vrcholmi nebol súčasne aj cestou, dal by sa skrátiť tak, ako je to znázornené na nasledujúcom obrázku &ndash; a tým by sme dostali spor.
 +
[[Súbor:Sled_cesta.png|center]]
 +
V nasledujúcom preto budeme hovoriť o existencii ciest.
  
Grafy na tejto prednáške chápeme v relatívne obmedzenom slova zmysle. V praxi sa často zídu aj rôzne rozšírenia definície grafu:
+
''Pojmy sledu a cesty o niečo presnejšie:''
* ''Grafy s násobnými hranami'' (niekde tiež ''multigrafy'') umožňujú viesť medzi danou dvojicou vrcholov viacero paralelných hrán. To možno v pamäti počítača realizovať napríklad nahradením booleovskej matice maticou prirodzených čísel udávajúcich násobnosti jendotlivých hrán, prípadne pridaním informácie o multiplicite do zoznamov následníkov resp. susedov.
+
* ''Sledom'' v grafe rozumieme postupnosť vrcholov ''v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>n</sub>'' takú, že ''n'' je prirodzené číslo (aj nula) a pre ''i = 1,...,n'' existuje v danom grafe hrana z ''v<sub>i-1</sub>'' do ''v<sub>i</sub>''.
* ''Ohodnotené grafy'' obsahujú na hranách nejakú ďalšiu prídavnú informáciu (napríklad pri cestnej sieti si môžeme pamätať dĺžku jednotlivých úsekov, prípadne ich možno využiť aj na reprezentáciu multigrafov). Možno ich reprezentovať nahradením booleovskej matice maticou ohodnotení, prípadne zakomponovaním informácie o ohodnotení hrán do zoznamov následníkov resp. susedov. S ohodnotenými grafmi sa okrajovo stretneme aj tento semester.
+
* ''Cestou'' rozumieme sled ''v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>n</sub>'' taký, že vrcholy ''v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>n</sub>'' sú po dvoch rôzne.
* ''Dynamické grafy'' podporujú aj pridávanie a mazanie vrcholov a/alebo hrán.
+
* ''Dĺžkou sledu'' (alebo cesty) ''v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>n</sub>'' nazveme číslo ''n'', čiže počet hrán tento sled tvoriacich.
  
== Prehľadávanie (orientovaného alebo neorientovaného) grafu do hĺbky ==
+
Z každého vrcholu ''v'' tak vedie do ''v'' sled dĺžky nula, ktorý je súčasne aj cestou; žiadne ďalšie sledy nulovej dĺžky v grafe existovať nemôžu. Sledy dĺžky jedna zodpovedajú hranám grafu a cesty dĺžky jedna zodpovedajú hranám, ktoré nie sú slučkami.
  
=== Existencia cesty medzi dvojicou vrcholov ===
+
Hovoríme, že ''neorientovaný'' graf je ''súvislý'', ak je ľubovoľná dvojica vrcholov tohto grafu spojená cestou.
  
Riešme teraz nasledujúci problém: pre danú dvojicu vrcholov ''u'' a ''v'' nejakého (orientovaného alebo neorientovaného) grafu zistiť, či sú spojené ''sledom'' &ndash; t.j. postupnosťou postupne na seba nadväzujúcich hrán (táto postupnosť môže byť aj prázdna, takže z každého vrcholu triviálne vedie sled do seba samého). Existencia sledu medzi dvoma vrcholmi je očividne ekvivalentná existencii ''cesty'' (t.j. sledu, v ktorom sa žiaden vrchol nezopakuje). V nasledujúcom preto budeme hovoriť o cestách.
+
Pre ''neorientované'' grafy možno problém existencie cesty medzi dvoma vrcholmi chápať aj ako úlohu zistiť, či sú tieto dva vrcholy v rovnakom ''komponente súvislosti'' grafu. Komponent súvislosti neorientovaného grafu je každý jeho (vzhľadom na inklúziu) maximálny podgraf s nenulovým počtom vrcholov, ktorý je súvislý &ndash; komponent súvislosti grafu teda pozostáva z nejakej neprázdnej podmnožiny vrcholov grafu a všetkých hrán pôvodného grafu spájajúcich vrcholy z tejto podmnožiny, pričom ľubovoľné dva vrcholy komponentu sú spojené cestou a žiaden vrchol komponentu nie je v pôvodnom grafe spojený cestou s vrcholom mimo komponentu. Stručnejšie to možno vyjadriť s využitím pozorovania, že existencia cesty v ''neorientovanom'' grafe je zjavne reláciou ekvivalencie na množine jeho vrcholov &ndash; komponenty súvislosti potom zodpovedajú triedam ekvivalencie tejto relácie. Napríklad neorientovaný graf na nasledujúcom obrázku pozostáva z troch komponentov súvislosti.
  
Pre ''neorientované'' grafy možno tento problém chápať aj ako úlohu zistiť, či sú tieto dva vrcholy v rovnakom ''komponente súvislosti'' grafu. Komponent súvislosti neorientovaného grafu je každý jeho (vzhľadom na inklúziu) maximálny podgraf, ktorý je súvislý &ndash; komponent súvislosti grafu teda pozostáva z nejakej podmnožiny jeho vrcholov, všetkých hrán pôvodného grafu spájajúcich vrcholy z tejto podmnožiny, pričom ľubovoľné dva vrcholy komponentu sú spojené cestou a pridaním ľubovoľného ďalšieho vrcholu grafu sa táto vlastnosť poruší. (Pri tejto &bdquo;definícii&rdquo; využívame fakt, že existencia cesty v ''neorientovanom'' grafe je zjavne reláciou ekvivalencie na množine jeho vrcholov.)
+
[[Súbor:Graf3.png|center]]
  
Na riešenie problému existencie cesty pritom použijeme ''prehľadávanie do hĺbky'' &ndash; podobné, ako sme už používali minulý semester pri vyfarbovaní súvislých oblastí v obdĺžnikovej mriežke. Procedúra na grafoch však bude všeobecnejšia:
+
Na riešenie problému existencie cesty použijeme ''prehľadávanie do hĺbky'' (angl. ''depth-first search'') &ndash; podobné, ako ste už používali minulý semester pri vyfarbovaní súvislých oblastí v obdĺžnikovej mriežke. Procedúra na grafoch však bude všeobecnejšia:
 
* Mriežku môžeme reprezentovať neorientovaným grafom, v ktorom vrcholy zodpovedajú políčkam mriežky. Dvojica vrcholov je navyše spojená hranou práve vtedy, keď zodpovedajúce políčka spolu susedia a súčasne majú rovnakú farbu.
 
* Mriežku môžeme reprezentovať neorientovaným grafom, v ktorom vrcholy zodpovedajú políčkam mriežky. Dvojica vrcholov je navyše spojená hranou práve vtedy, keď zodpovedajúce políčka spolu susedia a súčasne majú rovnakú farbu.
 
* Ostrovy rovnakej farby v mriežke potom zodpovedajú komponentom súvislosti výsledného neorientovaného grafu.
 
* Ostrovy rovnakej farby v mriežke potom zodpovedajú komponentom súvislosti výsledného neorientovaného grafu.
  
Na riešenie uvedeného problému napíšeme rekurzívnu metódu <tt>search</tt>, ktorá bude prehľadávať všetkých ešte nenavštívených susedov daného vrcholu. Informáciu o navštívení jednotlivých vrcholov si budeme uchovávať v zozname <tt>visited</tt>. Metóda <tt>existsPath</tt> bude metódu <tt>search</tt> využívať na riešenie horeuvedeného problému.  
+
Prehľadávanie (orientovaného alebo neorientovaného) grafu do hĺbky budeme realizovať rekurzívnou metódou <tt>search</tt>, ktorá prehľadá všetkých ešte nenavštívených následníkov daného vrcholu. Informáciu o navštívení jednotlivých vrcholov si pritom budeme uchovávať v zozname <tt>visited</tt>. Metóda <tt>existsPath</tt> bude metódu <tt>search</tt> využívať na riešenie problému existencie cesty medzi danými dvoma vrcholmi.  
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
/* Pomocna metoda pre metodu existsPath.
+
/**
  Dostane (orientovany alebo neorientovany) graf g, vrchol vertex a zoznam visited
+
* Pomocna metoda pre metodu existsPath, ktora rekurzivne prehlada vsetky doposial nenavstivene vrcholy
  s informaciou o navstiveni jednotlivych vrcholov.
+
* dosiahnutelne z daneho vrcholu.
  Pri volani by malo platit visited.get(vertex) == false.
+
* @param g      Orientovany alebo neorientovany graf, v ktorom sa prehladavanie realizuje.
  Metoda rekurzivne prehlada vsetky doposial nenavstivene vrcholy dosiahnutelne z vrcholu vertex. */
+
* @param vertex  Vrchol grafu g, v ktorom sa prehladavanie zacina.
static void search(Graph g, int vertex, List<Boolean> visited) {
+
* @param visited Zoznam obsahujuci informacie o navstiveni jednotlivych vrcholov grafu. Pri volani metody by malo
 +
*                platit visited.get(vertex) == false.
 +
*/
 +
private static void search(DirectedGraph g, int vertex, List<Boolean> visited) {
 
     visited.set(vertex, true);
 
     visited.set(vertex, true);
     for (int neighbour : g.adjVertices(vertex)) {
+
     for (int successor : g.outgoingEdgesDestinations(vertex)) {
         if (!visited.get(neighbour)) {
+
         if (!visited.get(successor)) {
             search(g, neighbour, visited);
+
             search(g, successor, visited);
         }  
+
         }
 
     }
 
     }
 
}
 
}
+
 
/* Metoda, ktora zisti, ci su vrcholy from a to v grafe g spojene cestou. */  
+
/**
static boolean existsPath(Graph g, int from, int to) {
+
* Metoda, ktora zisti, ci je dvojica vrcholov grafu spojena cestou.
 +
* @param g    Graf, v ktorom sa uloha realizuje.
 +
* @param from Pociatocny vrchol.
 +
* @param to   Koncovy vrchol.
 +
* @return    Vystup je true prave vtedy, ked v grafe g existuje cesta z vrcholu from do vrcholu to.
 +
*/
 +
public static boolean existsPath(DirectedGraph g, int from, int to) {
 
     ArrayList<Boolean> visited = new ArrayList<>();
 
     ArrayList<Boolean> visited = new ArrayList<>();
     for (int i = 0; i <= g.getNumberOfVertices() - 1; i++) {
+
     for (int i = 0; i <= g.getVertexCount() - 1; i++) {
 
         visited.add(false);
 
         visited.add(false);
 
     }
 
     }
Riadok 115: Riadok 518:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
''Cvičenie.'' Vytvorte abstraktnú triedu <tt>AbstractDirectedGraph</tt> implementujúcu rozhranie <tt>DirectedGraph</tt> a upravte triedy <tt>SuccessorListsDirectedGraph</tt> a <tt>AdjacencyMatrixDirectedGraph</tt> tak, aby dedili od triedy <tt>AbstractDirectedGraph</tt>. Prepíšte metódy <tt>existsPath</tt> a <tt>search</tt> uvedené vyššie ako metódy inštancie triedy <tt>AbstractDirectedGraph</tt>. Aký bude mať táto zmena vplyv na argumenty týchto metód?
  
 
=== Hľadanie komponentov súvislosti neorientovaného grafu ===
 
=== Hľadanie komponentov súvislosti neorientovaného grafu ===
Riadok 120: Riadok 525:
 
V prípade, že pracujeme s ''neorientovaným'' grafom a existenciu cesty medzi dvojicami vrcholov by sme chceli testovať veľakrát, oplatí sa nájsť všetky komponenty súvislosti v danom grafe. Komponenty môžeme očíslovať od nuly až po nejaké ''k - 1'', pričom pre každý vrchol si môžeme pamätať číslo jeho komponentu. Túto úlohu realizuje nasledujúca trieda:
 
V prípade, že pracujeme s ''neorientovaným'' grafom a existenciu cesty medzi dvojicami vrcholov by sme chceli testovať veľakrát, oplatí sa nájsť všetky komponenty súvislosti v danom grafe. Komponenty môžeme očíslovať od nuly až po nejaké ''k - 1'', pričom pre každý vrchol si môžeme pamätať číslo jeho komponentu. Túto úlohu realizuje nasledujúca trieda:
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
/* Trieda reprezentujuca rozdelenie neorientovaneho grafu na komponenty suvislosti: */
+
package graphs;
class Components {
+
 
     private UndirectedGraph g;              // Neorientovany graf    
+
import java.util.*;
     private ArrayList<Integer> componentId; // Pre kazdy vrchol si budeme v tomto zozname pamatat cislo jeho komponentu
+
 
     private int numComponents;               // Celkovy pocet komponentov
+
/**
      
+
* Trieda reprezentujuca rozdelenie neorientovaneho grafu na komponenty suvislosti.
     /* Konstruktor, ktory dostane graf a prehladavanim do hlbky najde jeho komponenty suvislosti: */
+
*/
 +
public class Components {
 +
     /**
 +
    * Neorientovany graf, ktoreho komponenty suvislosti su instanciou tejto triedy reprezentovane.
 +
    */
 +
     private UndirectedGraph g;
 +
 
 +
    /**
 +
    * Zoznam, v ktorom si pre kazdy vrchol grafu g budeme pamatat cislo jeho komponentu.
 +
    */
 +
     private ArrayList<Integer> componentId;
 +
 
 +
    /**
 +
    * Celkovy pocet komponentov suvislosti grafu g.
 +
    */
 +
     private int componentCount;
 +
 
 +
     /**
 +
    * Konstruktor, ktory dostane ako argument neorientovany graf, najde komponenty suvislosti tohto grafu a informacie
 +
    * o nich ulozi do premennych instancie.
 +
    * @param g Neorientovany graf, ktoreho komponenty su reprezentovane instanciou tejto triedy.
 +
    */
 
     public Components(UndirectedGraph g) {
 
     public Components(UndirectedGraph g) {
 
         this.g = g;
 
         this.g = g;
         numComponents = 0;                   // Pocet komponentov inicializujeme na 0
+
         componentCount = 0;
         int n = g.getNumberOfVertices();     // Pocet vrcholov grafu
+
         int n = g.getVertexCount();
       
+
 
         componentId = new ArrayList<>();  
+
         componentId = new ArrayList<>();
         for (int i = 0; i <= n - 1; i++) {   // Komponenty jednotlivych vrcholov inicializujeme na -1 (nezmyselna hodnota)
+
         for (int i = 0; i <= n - 1; i++) {
             componentId.add(-1);          
+
             componentId.add(-1);
 
         }
 
         }
       
+
 
         for (int i = 0; i <= n - 1; i++) {   // Prejdeme vsetky vrcholy ...
+
         for (int i = 0; i <= n - 1; i++) {
             if (componentId.get(i) == -1) { // ... a ak najdeme nespracovany ...
+
             if (componentId.get(i) == -1) {
                 search(i, numComponents);   // ... vyrobime novy komponent suvislosti
+
                 search(i, componentCount);
                 numComponents++;
+
                 componentCount++;
 
             }
 
             }
 
         }
 
         }
 
     }
 
     }
   
+
 
     /* Pomocna rekurzivna metoda pouzivana v konstruktore na oznacenie jedneho komponentu suvislosti cislom id: */
+
     /**
 +
    * Pomocna metoda pre konstruktor, ktora oznaci vrcholy jedneho komponentom suvislosti identifikatorom tohto
 +
    * komponentu. Pracuje na baze prehladavania do hlbky.
 +
    * @param vertex Vrchol, z ktoreho sa zacina prehladavanie vrcholov komponentu.
 +
    * @param id    Identifikator komponentu suvislosti.
 +
    */
 
     private void search(int vertex, int id) {
 
     private void search(int vertex, int id) {
 
         componentId.set(vertex, id);
 
         componentId.set(vertex, id);
         for (int neighbour : g.adjVertices(vertex)) {
+
         for (int neighbour : g.outgoingEdgesDestinations(vertex)) {
 
             if (componentId.get(neighbour) == -1) {
 
             if (componentId.get(neighbour) == -1) {
 
                 search(neighbour, id);
 
                 search(neighbour, id);
Riadok 154: Riadok 585:
 
         }
 
         }
 
     }
 
     }
 
+
 
     /* Metoda, ktora vrati true prave vtedy, ked su vrcholy from a to v rovnakom komponente: */
+
     /**
 +
    * Metoda, ktora zisti, ci v grafe, ktoreho komponenty reprezentuje instancia tejto triedy, existuje cesta spajajuca
 +
    * danu dvojicu vrcholov.
 +
    * @param from Pociatocny vrchol.
 +
    * @param to  Koncovy vrchol.
 +
    * @return    Metoda vrati true prave vtedy, ked v grafe existuje cesta z vrcholu from do vrcholu to.
 +
    */
 
     public boolean existsPath(int from, int to) {
 
     public boolean existsPath(int from, int to) {
 
         return componentId.get(from).equals(componentId.get(to));
 
         return componentId.get(from).equals(componentId.get(to));
 
     }
 
     }
   
+
 
     /* Metoda, ktora vrati pocet komponentov grafu: */
+
     /**
     public int getNumberOfComponents() {
+
    * Metoda, ktora vrati celkovy pocet komponentov grafu.
         return numComponents;
+
    * @return Pocet komponentov.
 +
    */
 +
     public int getComponentCount() {
 +
         return componentCount;
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Nasledujúci kód načíta neorientovaný graf a dvojicu jeho vrcholov. Na konzolu následne vypíše, či sú tieto dva vrcholy v danom grafe spojené cestou.
 +
 
 +
<syntaxhighlight lang="java">
 +
public static void main(String[] args) {
 +
    Scanner scanner = new Scanner(System.in);
 +
    System.out.println("Zadaj neorientovany graf:");
 +
    UndirectedGraph g = readUndirectedGraph(scanner, GraphImplementation.LISTS);
 +
    System.out.println("Zadaj dvojicu vrcholov grafu:");
 +
    int from = scanner.nextInt();
 +
    int to = scanner.nextInt();
 +
 
 +
    Components components = new Components(g);
 +
    if (components.existsPath(from, to)) {
 +
        System.out.println("Vrcholy su spojene cestou.");
 +
    } else {
 +
        System.out.println("Vrcholy nie su spojene cestou.");
 
     }
 
     }
 
}
 
}
Riadok 169: Riadok 629:
 
== Prehľadávanie (orientovaného alebo neorientovaného) grafu do šírky ==
 
== Prehľadávanie (orientovaného alebo neorientovaného) grafu do šírky ==
  
* Jednou z tém minulej prednášky bolo ''prehľadávanie grafu do hĺbky'' (angl. ''depth-first search''). Pri ''neorientovaných'' grafoch sme ho použili aj ako nástroj na hľadanie komponentov súvislosti.
+
Podobne ako prehľadávanie do hĺbky možno vo všeobecnejšom kontexte grafov aplikovať aj ''prehľadávanie do šírky'' (angl. ''breadth-first search''), ktorého variant pre obdĺžnikovú mriežku poznáte z minulého semestra. Prehľadávanie grafu do šírky bude možné použiť na hľadanie ''najkratších ciest'' medzi dvojicami vrcholov orientovaného (a teda aj neorientovaného) grafu, kde dĺžka cesty je meraná počtom hrán.  
* Teraz si ukážeme ''prehľadávanie grafu do šírky'' (angl. ''breadth-first search'') &ndash; to sa bude dať použiť na hľadanie najkratších ciest medzi dvojicami vrcholov ''orientovaného'' (a teda aj neorientovaného) grafu.
 
* Opäť pôjde o zovšeobecnenie algoritmu, ktorý sme videli už minulý semester v súvislosti s vyfarbovaním súvislých oblastí mriežky.
 
  
 
=== Hľadanie najkratšej cesty ===
 
=== Hľadanie najkratšej cesty ===
  
''Cestou'' v grafe rozumieme postupnosť vrcholov ''v<sub>0</sub>, v<sub>1</sub>, ..., v<sub>n</sub>'' takú, že žiaden z vrcholov sa v nej nevyskytuje viac ako raz a pre ''i = 1,...,n'' existuje v grafe hrana z ''v<sub>i-1</sub>'' do ''v<sub>i</sub>''.
+
Hľadanie najkratších ciest v grafe &ndash; či už orientovanom alebo neorientovanom &ndash; možno realizovať napríklad nasledujúcou triedou <tt>ShortestPathsFromVertex</tt>:
* ''Dĺžkou cesty'' rozumieme počet hrán na nej &ndash; t.j. číslo ''n''.
 
 
 
Hľadanie najkratších ciest v danom (vo všeobecnosti aj orientovanom) grafe realizuje trieda <tt>ShortestPathsFromVertex</tt>:
 
 
* Jej konštruktor dostane ako parameter graf <tt>g</tt> a nejaký jeho význačný &bdquo;štartovací&rdquo; vrchol <tt>start</tt>. Následne spustí na grafe <tt>g</tt> prehľadávanie do šírky z vrcholu <tt>start</tt>.
 
* Jej konštruktor dostane ako parameter graf <tt>g</tt> a nejaký jeho význačný &bdquo;štartovací&rdquo; vrchol <tt>start</tt>. Následne spustí na grafe <tt>g</tt> prehľadávanie do šírky z vrcholu <tt>start</tt>.
 
* Takto sa postupne prehľadajú vrcholy vo vzdialenosti 1 od <tt>start</tt>, potom vrcholy vo vzdialenosti 2 od <tt>start</tt>, atď. Na zabezpečenie takéhoto poradia sa použije rad, podobne ako pri algoritme na mriežke minulý semester. V každom momente vykonávania algoritmu môže tento rad obsahovať vrcholy najviac dvoch rôznych vzdialeností od <tt>start</tt>.
 
* Takto sa postupne prehľadajú vrcholy vo vzdialenosti 1 od <tt>start</tt>, potom vrcholy vo vzdialenosti 2 od <tt>start</tt>, atď. Na zabezpečenie takéhoto poradia sa použije rad, podobne ako pri algoritme na mriežke minulý semester. V každom momente vykonávania algoritmu môže tento rad obsahovať vrcholy najviac dvoch rôznych vzdialeností od <tt>start</tt>.
* Pre každý vrchol <tt>v</tt> sa počas prehľadávania do <tt>ArrayList</tt>-u <tt>dist</tt> uloží jeho vzdialenosť od <tt>start</tt> a do <tt>ArrayList</tt>-u <tt>prev</tt> sa uloží vrchol <tt>u</tt>, z ktorého bol vrchol <tt>v</tt> objavený (to je nutne predposledný vrchol na najkratšej ceste zo <tt>start</tt> do <tt>v</tt>).
+
* Pre každý vrchol <tt>v</tt> sa počas prehľadávania do zoznamu <tt>distances</tt> uloží jeho vzdialenosť od vrcholu <tt>start</tt> a do zoznamu <tt>predecessors</tt> sa uloží vrchol <tt>u</tt>, z ktorého bol vrchol <tt>v</tt> objavený &ndash; musí pritom vždy ísť o predposledný vrchol na jednej z najkratších ciest zo <tt>start</tt> do <tt>v</tt>.
* Metóda <tt>distanceFromStart</tt> bude pre daný vrchol <tt>vertex</tt> vracať jeho vzdialenosť od vrcholu <tt>start</tt>. Tu sa jednoducho využije hodnota uložená v <tt>ArrayList</tt>-e <tt>dist</tt>.
+
* Metóda <tt>distanceFromStart</tt> bude pre daný vrchol <tt>vertex</tt> vracať jeho vzdialenosť od vrcholu <tt>start</tt>. Tu sa jednoducho využije hodnota uložená v zozname <tt>distances</tt>.
* Metóda <tt>shortestPathFromStart</tt> bude pre daný vrchol <tt>vertex</tt> vracať najkratšiu cestu z vrcholu <tt>start</tt> do vrcholu <tt>vertex</tt> reprezentovanú zoznamom vrcholov. Tú bude konštruovať od konca: začne vo vrchole <tt>vertex</tt> a postupne bude hľadať predchodcov pomocou hodnôt uložených v <tt>ArrayList</tt>-e <tt>prev</tt>.
+
* Metóda <tt>shortestPathFromStart</tt> bude pre daný vrchol <tt>vertex</tt> vracať najkratšiu cestu z vrcholu <tt>start</tt> do vrcholu <tt>vertex</tt> reprezentovanú zoznamom vrcholov. Tú bude konštruovať od konca: začne vo vrchole <tt>vertex</tt> a postupne bude hľadať predchodcov pomocou hodnôt uložených v zozname <tt>predecessors</tt>.
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
/* Trieda, ktora reprezentuje najkratsie cesty a vzdialenosti
+
package graphs;
  z jedneho vrcholu orientovaneho grafu do vsetkych ostatnych vrcholov. */
+
 
class ShortestPathsFromVertex {
+
import java.util.*;
     private final Graph g;  // Orientovany (alebo neorientovany) graf, v ktorom budeme cesty hladat.
+
 
    private final int start; // Vrchol grafu g, v ktorom maju cesty zacinat.
+
/**
   
+
* Trieda reprezentujuca najkratsie cesty z pevne daneho pociatocneho vrcholu do vsetkych ostatnych vrcholov grafu.
     private final ArrayList<Integer> dist; // i-ty prvok zoznamu bude vzdialenost zo start do i
+
*/
     private final ArrayList<Integer> prev; // i-ty prvok zoznamu bude predchodca i na najkratsej ceste zo start do i
+
public class ShortestPathsFromVertex {
   
+
     /**
     /* Konstruktor dostane graf g a startovaci vrchol start a prehladavanim do sirky
+
    * Zoznam obsahujuci pre kazdy vrchol grafu jeho vzdialenost zo "startovacieho" vrcholu z argumentu konstruktora.
      vypocita najkratsie cesty z vrcholu start do ostatnych vrcholov. */
+
    * Ak zo "startovacieho" vrcholu do nejakeho vrcholu nevedie ziadna cesta, bude namiesto jeho vzdialenosti v zozname
     public ShortestPathsFromVertex(Graph g, int start) {
+
    * ulozena hodnota -1.
        this.g = g;
+
    */
        this.start = start;
+
     private List<Integer> distances;
       
+
 
         /* Incializacia zoznamov dist a prev: */
+
    /**
         dist = new ArrayList<>();
+
    * Zoznam obsahujuci pre kazdy vrchol v grafu g jeho predchodcu na najkratsej ceste zo "startovacieho" vrcholu
         prev = new ArrayList<>();
+
    * do vrcholu v. Pre "startovaci" vrchol a vrcholy, do ktorych z neho nevedie cesta, bude v zozname ulozena
         for (int i = 0; i <= g.getNumberOfVertices() - 1; i++) {
+
    * hodnota -1.
             dist.add(-1); // i-ty prvok zoznamu dist bude -1, ak sa zo start neda dostat do i
+
    */
             prev.add(-1); // i-ty prvok zoznamu prev bude -1, ak sa zo start neda dostat do i alebo ak i == start
+
     private List<Integer> predecessors;
 +
 
 +
     /**
 +
    * Konstruktor, ktory pre dany graf a "startovaci" vrchol rovno aj najde najkratsie cesty zo startovacieho vrcholu
 +
    * do vsetkych ostatnych vrcholov grafu.
 +
    * @param g    Graf, v ktorom sa hladanie ciest realizuje.
 +
    * @param start "Startovaci" vrchol.
 +
    */
 +
     public ShortestPathsFromVertex(DirectedGraph g, int start) {
 +
         /* Inicializacia zoznamov distances a predecessors: */
 +
         distances = new ArrayList<>();
 +
         predecessors = new ArrayList<>();
 +
         for (int i = 0; i <= g.getVertexCount() - 1; i++) {
 +
             distances.add(-1);
 +
             predecessors.add(-1);
 
         }
 
         }
  
         /* Prehladavanim do sirky vypocitame vzdialenosti a najkratsie cesty zo start: */    
+
         /* Samotne prehladavanie do sirky: */
         LinkedList<Integer> queue = new LinkedList<>();
+
         Queue<Integer> queue = new LinkedList<>();
         dist.set(start, 0);
+
         distances.set(start, 0);
         queue.addLast(start);     // Vzdialenost zo start do start je 0
+
         queue.add(start);
 
         while (!queue.isEmpty()) {
 
         while (!queue.isEmpty()) {
             int vertex = queue.removeFirst(); // Vyberieme vrchol z radu
+
             // Vyberieme vrchol z radu, prejdeme vsetkych jeho naslednikov, nenavstivenych spracujeme a vlozime do radu:
            /* Prejdeme vsetkych susedov vrcholu vertex; ak este neboli navstiveni,
+
            int vertex = queue.remove();
              nastavime im vzdialenost a predchodcu a vlozime ich do radu:*/           
+
             for (int successor : g.outgoingEdgesDestinations(vertex)) {
             for (int neighbour : g.adjVertices(vertex)) {
+
                 if (distances.get(successor) == -1) {
                 if (dist.get(neighbour) == -1) {
+
                     distances.set(successor, distances.get(vertex) + 1);
                     dist.set(neighbour, dist.get(vertex) + 1);
+
                     predecessors.set(successor, vertex);
                     prev.set(neighbour, vertex);
+
                     queue.add(successor);
                     queue.addLast(neighbour);
 
 
                 }
 
                 }
 
             }
 
             }
 
         }
 
         }
 
     }
 
     }
   
+
 
     /* Metoda, ktora vrati vzdialenost vrcholu vertex od vrcholu start: */
+
     /**
 +
    * Metoda, ktora vrati dlzku najkratsej cesty z vrcholu start do daneho vrcholu.
 +
    * @param vertex Vrchol, vzdialenost ktoreho z vrcholu start sa pocita.
 +
    * @return      Dlzka najkratsej cesty z vrcholu start do vrcholu vertex. Ak ziadna neexistuje, vrati sa -1.
 +
    */
 
     public int distanceFromStart(int vertex) {
 
     public int distanceFromStart(int vertex) {
         return dist.get(vertex);
+
         return distances.get(vertex);
 
     }
 
     }
   
+
 
     /* Metoda, ktora vrati najkratsiu cestu z vrcholu start do vrcholu vertex.
+
     /**
      Reprezentaciou cesty je zoznam vrcholov na nej.
+
    * Metoda, ktora vrati najkratsiu cestu z vrcholu start do daneho vrcholu, reprezentovanu ako zoznam vrcholov.
      V pripade, ze neexistuje ziadna cesta zo start do vertex, vrati null. */
+
    * @param vertex Vrchol, najkratsia cesta do ktoreho z vrcholu start sa pocita.
 +
    * @return      Nemodifikovatelny zoznam obsahujuci postupne vsetky vrcholy najkratsej cesty zo start do vertex.
 +
    *              Ak ziadna cesta zo start do vertex neexistuje, vrati metoda referenciu null.
 +
    */
 
     public List<Integer> shortestPathFromStart(int vertex) {
 
     public List<Integer> shortestPathFromStart(int vertex) {
         if (dist.get(vertex) == -1) { // Ak neexistuje cesta, vrat null
+
         if (distances.get(vertex) == -1) {
 
             return null;
 
             return null;
 
         }
 
         }
 
         LinkedList<Integer> path = new LinkedList<>();
 
         LinkedList<Integer> path = new LinkedList<>();
 
         int v = vertex;
 
         int v = vertex;
         while (v != start) {           // Postupne pridavaj vrcholy od konca cesty
+
         while (v != -1) {
 
             path.addFirst(v);
 
             path.addFirst(v);
             v = prev.get(v);
+
             v = predecessors.get(v);
 
         }
 
         }
        path.addFirst(start);
 
 
         return path;
 
         return path;
 
     }
 
     }
Riadok 256: Riadok 730:
 
     Scanner scanner = new Scanner(System.in);
 
     Scanner scanner = new Scanner(System.in);
 
     System.out.println("Zadaj graf:");
 
     System.out.println("Zadaj graf:");
     Graph g = readGraph(scanner, false, false);
+
     DirectedGraph g = readDirectedGraph(scanner, GraphImplementation.LISTS);
    // PRE NEORIENTOVANY GRAF: Graph g = readGraph(scanner, true, false);  
 
 
     System.out.println("Zadaj pociatocny a koncovy vrchol:");
 
     System.out.println("Zadaj pociatocny a koncovy vrchol:");
 
     int from = scanner.nextInt();
 
     int from = scanner.nextInt();
 
     int to = scanner.nextInt();
 
     int to = scanner.nextInt();
       
+
 
     ShortestPathsFromVertex spfv = new ShortestPathsFromVertex(g, from);
+
     ShortestPathsFromVertex shortestPathsFromVertex = new ShortestPathsFromVertex(g, from);
     System.out.println("Najkratsia cesta ma dlzku " + spfv.distanceFromStart(to));
+
     System.out.println("Najkratsia cesta ma dlzku " + shortestPathsFromVertex.distanceFromStart(to) + ".");
     List<Integer> shortest = spfv.shortestPathFromStart(to);
+
     List<Integer> shortestPath = shortestPathsFromVertex.shortestPathFromStart(to);
     if (shortest != null) {
+
     if (shortestPath != null) {
         System.out.println(shortest);
+
         System.out.println(shortestPath);
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Špeciálny prípad neorientovaných grafov ===
+
== Stromy prehľadávania do hĺbky a do šírky ==
 +
 
 +
Označme pri prehľadávaní do hĺbky aj do šírky tie hrany, ktoré boli použité pri objavovaní nových vrcholov &ndash; to znamená: pri prehľadávaní do hĺbky hrany ''(u,v)'' také, že vo volaní metódy <tt>search</tt> pre vrchol ''u'' sa táto metóda zavolala rekurzívne aj pre vrchol ''v'' a pri prehľadávaní do šírky hrany ''(u,v)'' také, že sa v rámci prehľadávania následníkov vrcholu ''u'' do radu pridal vrchol ''v''.
  
[[Image:PROG-P36-bfs.png|thumb|200px|right|Neorientovaný graf s kostrou najkratších ciest pre <tt>start == 0</tt>.]]
+
* V oboch prípadoch potom takéto hrany tvoria strom zakorenený vo vrchole, v ktorom prehľadávanie začalo (pri orientovaných grafoch sú všetky hrany orientované smerom od koreňa k listom).  
 +
* Hovoríme teda o ''stromoch prehľadávania do hĺbky'' resp. ''do šírky'' (angl. ''DFS Tree'' resp. ''BFS Tree'').
 +
* V prípade, že je graf neorientovaný a súvislý, ide v obidvoch prípadoch o jeho ''kostru'' (t. j. strom zložený zo ''všetkých'' vrcholov grafu a jeho vybraných hrán).
 +
* Pri stromoch prehľadávania ''do šírky'' reprezentujú cesty od koreňa k nejakému inému vrcholu vždy niektorú z najkratších ciest medzi príslušnými vrcholmi grafu.
  
Inštanciu triedy <tt>ShortestPathsFromVertex</tt> možno vytvoriť ako pre orientované, tak aj pre neorientované grafy (neorientované grafy sme implementovali ako špeciálny prípad orientovaných a všetky grafy implementujú spoločné rozhranie <tt>Graph</tt>).
+
''Príklad.'' Nižšie je znázornený diagram orientovaného grafu a jeho stromy prehľadávania do hĺbky a do šírky v prípade, že prehľadávanie začne vo vrchole 0 a pri spracúvaní následníkov sa postupuje vzostupne podľa čísel vrcholov (čo nemusí byť vždy tak).
  
Pre neorientované grafy si v súvislosti s prehľadávaním do šírky možno všimnúť nasledujúce:
+
''Strom prehľadávania do hĺbky:''
* Hrany medzi vrcholmi a ich predchodcami tvoria strom (ak je graf súvislý, je tento strom jeho kostrou) &ndash; to je znázornené na obrázku vpravo.
+
<center>
* Najkratšia cesta zo <tt>start</tt> do <tt>v</tt> je potom (ak existuje) ''jediná'' cesta zo <tt>start</tt> do <tt>v</tt> v tomto strome.
+
[[Súbor:Graf4.png]] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[Súbor:Graf4DFS.png]] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [[Súbor:DFSTree.png]]
 +
</center>
  
(V orientovaných grafoch je situácia podobná; stromy najkratších ciest však navyše majú hrany orientované smerom ''od'' <tt>start</tt>.)
+
''Strom prehľadávania do šírky:''
 +
<center>
 +
[[Súbor:Graf4.png]] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[[Súbor:Graf4BFS.png]] &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [[Súbor:BFSTree.png]]
 +
</center>
  
 
== Prehľadávanie s návratom na grafoch ==
 
== Prehľadávanie s návratom na grafoch ==
  
Pre veľa úloh na grafoch nie sú známe (a v prípade platnosti niektorých hypotéz z teoretickej informatiky často ani neexistujú) efektívne algoritmy. Backtrackingom však vieme spočítať odpoveď aspoň pre malé vstupy.
+
Pre veľa úloh na grafoch nie sú známe &ndash; a v prípade platnosti niektorých hypotéz z teoretickej informatiky často ani neexistujú &ndash; žiadne efektívne algoritmy. Prehľadávaním s návratom však vieme spočítať odpoveď aspoň pre malé vstupy.
  
=== Hľadanie ciest dĺžky ''k'' ===
+
=== Hľadanie ciest danej dĺžky ===
  
Nasledujúca trieda <tt>FixedLengthPaths</tt> pre daný graf <tt>g</tt>, danú dvojicu vrcholov <tt>from, to</tt> a dané prirodzené číslo <tt>length</tt> vypisuje všetky cesty dĺžky ''presne'' <tt>length</tt> vedúce v <tt>g</tt> z vrcholu <tt>from</tt> do vrcholu <tt>to</tt>. Tento proces sa spustí hneď v konštruktore (nebude teda mať veľký význam vytvárať inštancie triedy <tt>FixedLengthPaths</tt>).
+
Nasledujúca trieda <tt>FixedLengthPaths</tt> pre daný graf <tt>g</tt>, danú dvojicu vrcholov <tt>from, to</tt> a dané prirodzené číslo <tt>length</tt> nájde všetky cesty dĺžky ''presne'' <tt>length</tt> vedúce v grafe <tt>g</tt> z vrcholu <tt>from</tt> do vrcholu <tt>to</tt>; takéto cesty môžu evidentne existovať iba v prípade, že je prirodzené číslo <tt>length</tt> menšie, než počet vrcholov grafu. Prehľadávanie s návratom sa spustí hneď v konštruktore a nájdené cesty sa uložia do zoznamu <tt>paths</tt>.
  
Prehľadávaním s návratom budeme v <tt>LinkedList</tt>-e <tt>path</tt> postupne generovať všetky takéto cesty. Pre každý vrchol budeme mať navyše v <tt>ArrayList</tt>-e <tt>visited</tt> poznačené, či sme ho už v generovanej ceste použili. Akonáhle nájdeme cestu požadovanej dĺžky končiacu vo vrchole <tt>to</tt>, vypíšeme ju na výstup.
+
Pri prehľadávaní sa v spájanom zozname <tt>path</tt> budú postupne generovať všetky cesty hľadaného typu. Pre každý vrchol budeme mať navyše v zozname <tt>visited</tt> poznačené, či sme ho už v generovanej ceste použili. Akonáhle nájdeme cestu požadovanej dĺžky končiacu vo vrchole <tt>to</tt>, uložíme ju do zoznamu <tt>paths</tt>.
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
/* Trieda, pomocou ktorej mozno najst vsetky cesty danej dlzky medzi danou dvojicou vrcholov. */
+
package graphs;
class FixedLengthPaths {
+
 
     private Graph g;      // Orientovany (alebo neorientovany) graf
+
import java.util.*;
     private int from, to; // Pociatocny a koncovy vrchol
+
 
     private int length;   // Pozadovana dlzka cesty  
+
/**
   
+
* Trieda realizujuca najdenie vsetkych ciest danej fixnej dlzky medzi danou dvojicou vrcholov daneho grafu.
     private LinkedList<Integer> path;   // Zoznam, v ktorom budeme postupne generovat cesty
+
*/
     private ArrayList<Boolean> visited; // i-ty prvok zoznamu visited bude true, ak sa vrchol i nachadza v path
+
public class FixedLengthPaths {
      
+
    /**
     /* Konstruktor dostane graf, pociatocny a koncovy vrchol a pozadovanu dlzku cesty.
+
    * Graf, v ktorom sa hladanie ciest realizuje.
      Spusti prehladavanie s navratom, ktore hlada vsetky cesty danej dlzky medzi
+
    */
      danymi vrcholmi a rovno aj vypisuje vysledky. */
+
     private DirectedGraph g;
     public FixedLengthPaths(Graph g, int from, int to, int length) {
+
 
 +
    /**
 +
     * Koncovy vrchol hladanych ciest.
 +
    */
 +
     private int to;
 +
 
 +
    /**
 +
    * Pozadovana dlzka hladanych ciest.
 +
    */
 +
     private int length;
 +
 
 +
    /**
 +
    * Pomocny zoznam, v ktorom sa budu pomocou prehladavania s navratom postupne generovat jednotlive cesty.
 +
    */
 +
     private LinkedList<Integer> path;
 +
 
 +
    /**
 +
    * Pomocny zoznam, v ktorom si budeme pocas generovania ciest pre kazdy vrchol pamatat, ci sa nachadza alebo
 +
    * nenachadza v doposial vygenerovanej casti cesty.
 +
    */
 +
     private ArrayList<Boolean> visited;
 +
 
 +
    /**
 +
    * Zoznam, v ktorom budu ulozene vsetky vygenerovane cesty.
 +
    */
 +
     private List<List<Integer>> paths;
 +
 
 +
     /**
 +
    * Konstruktor, ktory rovno aj spusti prehladavanie s navratom a do zoznamu paths postupne ulozi vsetky cesty danej
 +
    * dlzky medzi danou dvojicou vrcholov daneho grafu.
 +
    * @param g      Graf, v ktorom sa hladanie ciest realizuje.
 +
    * @param from  Pociatocny vrchol hladanych ciest.
 +
    * @param to    Koncovy vrchol hladanych ciest.
 +
    * @param length Pozadovana dlzka hladanych ciest.
 +
    */
 +
     public FixedLengthPaths(DirectedGraph g, int from, int to, int length) {
 
         this.g = g;
 
         this.g = g;
        this.from = from;
 
 
         this.to = to;
 
         this.to = to;
 
         this.length = length;
 
         this.length = length;
          
+
         paths = new LinkedList<>();
 +
 
 
         visited = new ArrayList<>();
 
         visited = new ArrayList<>();
         for (int i = 0; i <= g.getNumberOfVertices() - 1; i++) {
+
         for (int i = 0; i <= g.getVertexCount() - 1; i++) {
 
             visited.add(false);
 
             visited.add(false);
 
         }
 
         }
       
 
 
         path = new LinkedList<>();
 
         path = new LinkedList<>();
         path.add(from);             // Kazda cesta z from bude zacinat vo from
+
         path.add(from);
 
         visited.set(from, true);
 
         visited.set(from, true);
         search();                   // Spusti generovanie ciest
+
         search();
 
     }
 
     }
  
     /* Hlavna rekurzivna metoda prehladavania s navratom.
+
     /**
      Ak je vygenerovana cesta kratsia ako length, postupne vyskusa vsetky  
+
    * Metoda realizujuca samotne rekurzivne prehladavanie s navratom. Ak je dosial vygenerovana cast cesty kratsia nez
      moznosti jej predlzenia.
+
    * length, postupne sa vyskusaju vsetky moznosti jej predlzenia. V pripade, ze uz ide o cestu dlzky length, overi
      Ak sa vygeneruje cesta dlzky length, overi sa, ci tato cesta vedie do
+
    * sa, ci cesta konci vo vrchole to a ak ano, prida sa kopia tejto cesty do zoznamu paths.
      vrcholu to; ak ano, vypise sa.
+
    */
    */
 
 
     private void search() {
 
     private void search() {
         if (path.size() == length + 1) { // Ak uz mame cestu pozadovanej dlzky ...
+
         if (path.size() == length + 1) {   // Dlzka zoznamu je vzdy o jedna vacsia, nez dlzka nim reprezentovanej cesty.
             if (path.getLast() == to) {   // ... a konci v pozadovanom stave ...
+
             if (path.getLast() == to) {
                 System.out.println(path); // ... vypis ju
+
                 paths.add(Collections.unmodifiableList(new LinkedList<>(path)));
 
             }
 
             }
 
         } else {
 
         } else {
            /* Ak este nemame cestu pozadovanej dlzky, vyskusaj vsetky moznosti
+
             for (int successor : g.outgoingEdgesDestinations(path.getLast())) {
              jej predlzenia: */
+
                 if (!visited.get(successor)) {
             for (int neighbour : g.adjVertices(path.getLast())) {
+
                     visited.set(successor, true);
                 if (!visited.get(neighbour)) {
+
                     path.add(successor);
                     visited.set(neighbour, true);
 
                     path.addLast(neighbour);
 
 
                     search();
 
                     search();
 
                     path.removeLast();
 
                     path.removeLast();
                     visited.set(neighbour, false);
+
                     visited.set(successor, false);
 
                 }
 
                 }
 
             }
 
             }
 
         }
 
         }
 +
    }
 +
 +
    /**
 +
    * Metoda, ktora vrati nemodifikovatelny pohlad na zoznam vsetkych vygenerovanych ciest.
 +
    * @return Nemodifikovatelny zoznam vsetkych ciest dlzky length veducich v grafe g z vrcholu from do vrcholu to.
 +
    */
 +
    public List<List<Integer>> getPaths() {
 +
        return Collections.unmodifiableList(paths);
 
     }
 
     }
 
}
 
}
Riadok 356: Riadok 877:
 
     Scanner scanner = new Scanner(System.in);
 
     Scanner scanner = new Scanner(System.in);
 
     System.out.println("Zadaj graf:");
 
     System.out.println("Zadaj graf:");
     Graph g = readGraph(scanner, true, false);
+
     DirectedGraph g = readDirectedGraph(scanner, GraphImplementation.LISTS);
    // PRE ORIENTOVANY GRAF: Graph g = readGraph(scanner, false, false);
 
 
     System.out.println("Zadaj pociatocny a koncovy vrchol:");
 
     System.out.println("Zadaj pociatocny a koncovy vrchol:");
 
     int from = scanner.nextInt();
 
     int from = scanner.nextInt();
 
     int to = scanner.nextInt();
 
     int to = scanner.nextInt();
     System.out.println("Zadaj dlzku cesty:");
+
     System.out.println("Zadaj dlzku generovanych ciest:");
     int length = scanner.nextInt();  
+
     int length = scanner.nextInt();
  
 +
    FixedLengthPaths fixedLengthPaths = new FixedLengthPaths(g, from, to, length);
 
     System.out.println("Cesty dlzky " + length + ":");
 
     System.out.println("Cesty dlzky " + length + ":");
     new FixedLengthPaths(g, from, to, length);
+
     for (List<Integer> path : fixedLengthPaths.getPaths()) {
 +
        System.out.println(path);
 +
    }
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
[[Image:PROG-P36-graf1.png|thumb|130px|right]]
+
[[Image:Graf5.png|thumb|130px|right]]
''Príklad'': pre ''neorientovaný'' graf s vrcholmi ''{0,...,4}'' a hranami ''{{0,1},{0,2},{0,3},{1,2},{2,3},{2,4},{3,4}}'', počiatočný vrchol ''0'' a koncový vrchol ''3'' dostávame nasledujúce výstupy
+
''Príklad''. Pre orientovaný graf s vrcholmi ''{0,...,4}'' a hranami ''{(0,1),(0,2),(0,3),(1,2),(2,3),(2,4),(4,3)}'', počiatočný vrchol ''0'' a koncový vrchol ''3'' dostávame nasledujúce výstupy
 
<pre>
 
<pre>
 
Cesty dlzky 1:
 
Cesty dlzky 1:
0 3
+
[0, 3]
 
Cesty dlzky 2:
 
Cesty dlzky 2:
0 2 3
+
[0, 2, 3]
 
Cesty dlzky 3:
 
Cesty dlzky 3:
0 1 2 3
+
[0, 1, 2, 3]
0 2 4 3
+
[0, 2, 4, 3]
 
Cesty dlzky 4:
 
Cesty dlzky 4:
0 1 2 4 3
+
[0, 1, 2, 4, 3]
 
</pre>
 
</pre>
  
 
''Cvičenia'':
 
''Cvičenia'':
* Upravte triedu <tt>FixedLengthPaths</tt> tak, aby namiesto vypisovania ciest iba počítala, koľko ich je.
+
* Upravte triedu <tt>FixedLengthPaths</tt> tak, aby namiesto hľadania a ukladania všetkých ciest danej dĺžky iba počítala, koľko ich je.
 
* Upravte triedu <tt>FixedLengthPaths</tt> tak, aby iba zisťovala, či existuje cesta danej dĺžky (po prvej nájdenej ceste je teda možné prehľadávanie ukončiť).
 
* Upravte triedu <tt>FixedLengthPaths</tt> tak, aby iba zisťovala, či existuje cesta danej dĺžky (po prvej nájdenej ceste je teda možné prehľadávanie ukončiť).
* Navrhnite spôsoby, ako v niektorých prípadoch zistiť, že aktuálne rozrobenú cestu už nie je možné požadovaným spôsobom rozšíriť.
+
* Navrhnite spôsoby, ako v niektorých prípadoch zistiť, že rozrobenú cestu už nie je možné požadovaným spôsobom predĺžiť a danú vetvu prehľadávania s návratom tak možno ukončiť.
  
 
===Hľadanie najdlhšej cesty===
 
===Hľadanie najdlhšej cesty===
  
Uvažujme teraz problém nájdenia nejakej z najdlhších ciest z ''u'' do ''v'' (ak existuje aspoň jedna). Túto úlohu bude realizovať trieda <tt>LongestPath</tt>; oproti predchádzajúcemu programu sa ten nasledujúci bude líšiť iba málo:
+
Uvažujme teraz problém nájdenia nejakej z najdlhších ciest z ''u'' do ''v'' (ak existuje aspoň jedna). Túto úlohu bude realizovať trieda <tt>LongestPath</tt>, ktorá sa oproti triede <tt>FixedLengthPaths</tt> bude líšiť len málo.
* Budeme si pamätať najdlhšiu nájdenú cestu.
+
* Počas prehľadávania si budeme pamätať najdlhšiu doposiaľ nájdenú cestu.
* Vždy, keď prídeme do cieľového vrcholu, porovnáme dĺžku aktuálnej cesty s najdlhšou cestou nájdenou doteraz.
+
* Vždy, keď prídeme do cieľového vrcholu, porovnáme dĺžku práve nájdenej cesty s najdlhšou doposiaľ nájdenou cestou.
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
/* Trieda, pomocou ktorej mozno najst jednu z najdlhsich ciest medzi danou dvojicou vrcholov. */
+
package graphs;
class LongestPath {
+
 
     private Graph g;     // Orientovany (alebo neorientovany) graf
+
import java.util.*;
     private int from, to; // Pociatocny a koncovy vrchol
+
 
   
+
/**
     private int maxLength; // Dlzka doposial najdlhsej najdenej cesty z from do to
+
* Trieda realizujuca najdenie najdlhsej cesty medzi danou dvojicou vrcholov grafu.
   
+
*/
     private LinkedList<Integer> path;        // Zoznam, v ktorom budeme postupne generovat cesty
+
public class LongestPath {
     private LinkedList<Integer> longestPath; // Okrem toho si budeme pamatat najdlhsiu doposial vygenerovanu cestu
+
     /**
     private ArrayList<Boolean> visited;      // i-ty prvok zoznamu visited bude true, ak sa vrchol i nachadza v path
+
    * Graf, v ktorom sa hladanie ciest realizuje.
      
+
     */
     /* Konstruktor dostane graf, pociatocny a koncovy vrchol. Spusti prehladavanie
+
     private DirectedGraph g;
      s navratom, ktore hlada najdlhsiu cestu medzi danymi vrcholmi. */
+
 
     public LongestPath(Graph g, int from, int to) {
+
    /**
 +
    * Koncovy vrchol hladanych ciest.
 +
    */
 +
     private int to;
 +
 
 +
     /**
 +
    * Pomocny zoznam, v ktorom sa budu pomocou prehladavania s navratom postupne generovat jednotlive cesty.
 +
    */
 +
     private LinkedList<Integer> path;
 +
 
 +
    /**
 +
    * Pomocny zoznam, v ktorom si budeme pocas generovania ciest pre kazdy vrchol pamatat, ci sa nachadza alebo
 +
    * nenachadza v doposial vygenerovanej casti cesty.
 +
    */
 +
     private ArrayList<Boolean> visited;
 +
 
 +
    /**
 +
     * Zoznam, v ktorom bude ulozena najdlhsia cesta medzi danou dvojicou vrcholov (pocas prehladavania pojde
 +
    * o najdlhsiu doposial najdenu cestu).
 +
    */
 +
     private List<Integer> longestPath;
 +
 
 +
     /**
 +
    * Konstruktor, ktory rovno aj spusti prehladavanie s navratom a do zoznamu longestPath ulozi niektoru spomedzi
 +
    * najdlhsich ciest medzi danou dvojicou vrcholov grafu.
 +
    * @param g      Graf, v ktorom sa hladanie ciest realizuje.
 +
    * @param from  Pociatocny vrchol hladanych ciest.
 +
    * @param to    Koncovy vrchol hladanych ciest.
 +
    */
 +
     public LongestPath(DirectedGraph g, int from, int to) {
 
         this.g = g;
 
         this.g = g;
        this.from = from;
 
 
         this.to = to;
 
         this.to = to;
               
+
 
 
         visited = new ArrayList<>();
 
         visited = new ArrayList<>();
         for (int i = 0; i <= g.getNumberOfVertices() - 1; i++) {
+
         for (int i = 0; i <= g.getVertexCount() - 1; i++) {
 
             visited.add(false);
 
             visited.add(false);
 
         }
 
         }
       
 
        maxLength = -1;            // Doposial nemame ziadnu cestu
 
 
         path = new LinkedList<>();
 
         path = new LinkedList<>();
         path.add(from);             // Kazda cesta z from bude zacinat vo from
+
         path.add(from);
 
         visited.set(from, true);
 
         visited.set(from, true);
         search();                   // Spusti generovanie ciest
+
         search();
 
     }
 
     }
  
     /* Hlavna rekurzivna metoda prehladavania s navratom.
+
     /**
      Ak cesta dorazila do vrchola to, jej dlzka sa porovna s najdlhsou doposial
+
    * Metoda realizujuca samotne rekurzivne prehladavanie s navratom. V pripade, ze sa vygenerovala cesta konciaca
      najdenou cestou a ak je dlhsia, ulozi sa ako nova doposial najdlhsia cesta.
+
    * vo vrchole to, porovna sa jej dlzka s dlzkou doposial najdlhsej najdenej cesty a ak je dlhsia, ulozi sa ako nova
      Ak este nedorazila do vrchola to, vyskusaju sa vsetky moznosti na jej
+
    * doposial najdlhsia cesta. V opacnom pripade sa vyskusaju vsetky moznosti predlzenia cesty.
      predlzenie.
+
    */
    */
 
 
     private void search() {
 
     private void search() {
         if (path.getLast() == to) { // Ak sme dorazili do cieloveho vrchola, ukonci prehladavanie 
+
         if (path.getLast() == to) {
             if (path.size() - 1 > maxLength) {
+
             if (longestPath == null || path.size() > longestPath.size()) {
                maxLength = path.size() - 1;
 
 
                 longestPath = new LinkedList<>(path);
 
                 longestPath = new LinkedList<>(path);
 
             }
 
             }
         } else {                   // Inak vyskusaj vsetky moznosti predlzenia cesty
+
         } else {
             for (int neighbour : g.adjVertices(path.getLast())) {
+
             for (int successor : g.outgoingEdgesDestinations(path.getLast())) {
                 if (!visited.get(neighbour)) {
+
                 if (!visited.get(successor)) {
                     visited.set(neighbour, true);
+
                     visited.set(successor, true);
                     path.addLast(neighbour);
+
                     path.add(successor);
 
                     search();
 
                     search();
 
                     path.removeLast();
 
                     path.removeLast();
                     visited.set(neighbour, false);
+
                     visited.set(successor, false);
 
                 }
 
                 }
 
             }
 
             }
 
         }
 
         }
 
     }
 
     }
   
+
 
     /* Metoda, ktora vrati najdenu najdlhsiu cestu: */
+
     /**
     public List<Integer> longestPath() {
+
    * Metoda, ktora vrati najdenu najdlhsiu cestu medzi danou dvojicou vrcholov.
 +
    * @return Nemodifikovatelny pohlad na zoznam vrcholov na najdlhsej ceste z vrcholu from do vrcholu to.
 +
    */
 +
     public List<Integer> getLongestPath() {
 
         if (longestPath != null) {
 
         if (longestPath != null) {
 
             return Collections.unmodifiableList(longestPath);
 
             return Collections.unmodifiableList(longestPath);
Riadok 462: Riadok 1 013:
 
Použitie triedy:
 
Použitie triedy:
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
LongestPath lp = new LongestPath(g, from, to);
+
LongestPath longestPath = new LongestPath(g, from, to);
List<Integer> longest = lp.longestPath();
+
List<Integer> longest = longestPath.getLongestPath();
 
if (longest != null) {
 
if (longest != null) {
     System.out.println("Najdlhsia cesta: " + longest);  
+
     System.out.println("Najdlhsia cesta: " + longest);
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Príklad výstupu na rovnakom grafe ako vyššie:
+
Príklad výstupu na rovnakom grafe ako vyššie pre počiatočný vrchol ''0'' a koncový vrchol ''3'':
 
<pre>
 
<pre>
 
Najdlhsia cesta: [0, 1, 2, 4, 3]
 
Najdlhsia cesta: [0, 1, 2, 4, 3]
 
</pre>
 
</pre>

Aktuálna revízia z 16:52, 2. apríl 2024

Oznamy

  • Na začiatku zajtrajších cvičení bude okrem niekoľkých bežných úloh k cvičeniam zverejnená aj druhá bonusová domáca úloha, za ktorú bude možné získať 2 body navyše. Odovzdať ju bude možné do 16. apríla 2024, 9:50 – čiže do začiatku deviatych cvičení.
  • Budúci týždeň počas cvičení – čiže v utorok 16. apríla od 9:50 do 11:20 – bude prebiehať tretí test zameraný na látku z prvých ôsmich týždňov. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6.

Triedy pre grafy z minulej prednášky

package graphs;

/**
 *  Rozhranie implementovane reprezentaciami orientovanych grafov o vrcholoch 0, 1, ..., n-1 pre nejake prirodzene n.
 *  Aj neorientovane grafy budu povazovane za specialny pripad orientovanych grafov; toto rozhranie tak bude
 *  implementovane vsetkymi triedami reprezentujucimi grafy.
 */
public interface DirectedGraph {
    /**
     * Metoda, ktora vrati pocet vrcholov reprezentovaneho grafu.
     *
     * @return Pocet vrcholov grafu.
     */
    int getVertexCount();

    /**
     * Metoda, ktora zisti, ci graf obsahuje vrchol s cislom vertex.
     * @param vertex Vrchol, ktoreho existencia sa ma zistit.
     * @return       Vrati true prave vtedy, ked graf obsahuje vrchol s cislom vertex.
     */
    boolean hasVertex(int vertex);

    /**
     * Metoda, ktora vrati pocet orientovanych hran reprezentovaneho grafu.
     *
     * @return Pocet orientovanych hran grafu.
     */
    int getDirectedEdgeCount();

    /**
     * Metoda, ktora zisti, ci v grafe existuje hrana medzi danou dvojicou vrcholov.
     *
     * @param from Pociatocny vrchol.
     * @param to   Koncovy vrchol.
     * @return     Vrati true prave vtedy, ked v grafe existuje hrana z vrcholu from do vrcholu to.
     */
    boolean hasEdge(int from, int to);

    /**
     * Metoda, ktora vrati vsetkych naslednikov daneho vrcholu -- cize vsetky vrcholy, do ktorych vedie z daneho vrcholu
     * orientovana hrana. Pre neorientovane grafy tak tato metoda vzdy vrati vsetkych susedov daneho vrcholu.
     *
     * @param vertex Lubovolny vrchol grafu.
     * @return       Naslednici vrcholu vertex ako instancia typu Iterable&lt;Integer&gt;.
     */
    Iterable<Integer> outgoingEdgesDestinations(int vertex);
}
package graphs;

public class DirectedEdge {
    private int from, to;

    public DirectedEdge(int from, int to) {
        this.from = from;
        this.to = to;
    }

    public int getFrom() {
        return from;
    }

    public int getTo() {
        return to;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        return this.getClass() == o.getClass() && from == ((DirectedEdge) o).from && to == ((DirectedEdge) o).to;
    }

    @Override
    public int hashCode() {
        return Integer.valueOf(from).hashCode() + 31 * Integer.valueOf(to).hashCode();
    }
}
package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca orientovany graf pomocou zoznamov naslednikov jednotlivych jeho vrcholov.
 */
public class SuccessorListsDirectedGraph implements DirectedGraph {
    /**
     * Pre kazdy vrchol si pamatame zoznam jeho naslednikov.
     */
    private ArrayList<ArrayList<Integer>> successorLists;

    /**
     * Pocet orientovanych hran v grafe (velkost grafu).
     */
    private int directedEdgeCount;

    /**
     * Konstruktor, ktory dostane ako argumenty pocet vrcholov grafu (t. j. jeho rad), ako aj vsetky hrany grafu.
     *
     * @param vertexCount   Rad grafu, cize pocet jeho vrcholov.
     * @param directedEdges Zoskupenie pozostavajuce zo vsetkych orientovanych hran grafu.
     */
    public SuccessorListsDirectedGraph(int vertexCount, Collection<? extends DirectedEdge> directedEdges) {
        if (vertexCount <= 0) {
            throw new IllegalArgumentException("Nonpositive vertex count.");
        }
        successorLists = new ArrayList<>();
        for (int i = 0; i <= vertexCount - 1; i++) {
            successorLists.add(new ArrayList<>());
        }
        directedEdgeCount = 0;
        for (DirectedEdge e : directedEdges) {
            if (!hasVertex(e.getFrom()) || !hasVertex(e.getTo())) {
                throw new IllegalArgumentException("Edge incident to a nonexistent vertex.");
            }
            if (!hasEdge(e.getFrom(), e.getTo())) {
                successorLists.get(e.getFrom()).add(e.getTo());
                directedEdgeCount++;
            } else {
                throw new IllegalArgumentException("Multiple edges connecting the same pair of vertices.");
            }
        }
    }

    @Override
    public int getVertexCount() {
        return successorLists.size();
    }

    @Override
    public boolean hasVertex(int v) {
        return v >= 0 && v <= getVertexCount() - 1;
    }

    @Override
    public int getDirectedEdgeCount() {
        return directedEdgeCount;
    }

    @Override
    public boolean hasEdge(int from, int to) {
        if (!hasVertex(from) || !hasVertex(to)) {
            throw new IllegalArgumentException("Nonexistent vertex.");
        }
        return successorLists.get(from).contains(to);
    }

    @Override
    public Iterable<Integer> outgoingEdgesDestinations(int vertex) {
        if (!hasVertex(vertex)) {
            throw new IllegalArgumentException("Nonexistent vertex.");
        }
        // Nasledujucim prikazom vratime nemodifikovatelny pohlad na zoznam naslednikov vrcholu vertex
        return Collections.unmodifiableList(successorLists.get(vertex));
    }
}
package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca orientovany graf pomocou matice susednosti.
 */
public class AdjacencyMatrixDirectedGraph implements DirectedGraph {
    /**
     * Matica susednosti.
     */
    private boolean[][] adjacencyMatrix;

    /**
     * Pocet orientovanych hran v grafe (velkost grafu).
     */
    private int directedEdgeCount;

    /**
     * Konstruktor, ktory dostane ako argumenty pocet vrcholov grafu (t. j. jeho rad), ako aj vsetky hrany grafu.
     *
     * @param vertexCount   Rad grafu, cize pocet jeho vrcholov.
     * @param directedEdges Zoskupenie pozostavajuce zo vsetkych orientovanych hran grafu.
     */
    public AdjacencyMatrixDirectedGraph(int vertexCount, Collection<? extends DirectedEdge> directedEdges) {
        if (vertexCount <= 0) {
            throw new IllegalArgumentException("Nonpositive vertex count.");
        }
        adjacencyMatrix = new boolean[vertexCount][vertexCount];
        directedEdgeCount = 0;
        for (DirectedEdge e : directedEdges) {
            if (!hasVertex(e.getFrom()) || !hasVertex(e.getTo())) {
                throw new IllegalArgumentException("Edge incident to a nonexistent vertex.");
            }
            if (!hasEdge(e.getFrom(), e.getTo())) {
                adjacencyMatrix[e.getFrom()][e.getTo()] = true;
                directedEdgeCount++;
            } else {
                throw new IllegalArgumentException("Multiple edges connecting the same pair of vertices.");
            }
        }
    }

    @Override
    public int getVertexCount() {
        return adjacencyMatrix.length;
    }

    @Override
    public boolean hasVertex(int v) {
        return v >= 0 && v <= getVertexCount() - 1;
    }

    @Override
    public int getDirectedEdgeCount() {
        return directedEdgeCount;
    }

    @Override
    public boolean hasEdge(int from, int to) {
        if (!hasVertex(from) || !hasVertex(to)) {
            throw new IllegalArgumentException("Nonexistent vertex.");
        }
        return adjacencyMatrix[from][to];
    }

    @Override
    public Iterable<Integer> outgoingEdgesDestinations(int vertex) {
        if (!hasVertex(vertex)) {
            throw new IllegalArgumentException("Nonexistent vertex.");
        }
        List<Integer> a = new ArrayList<>();
        for (int i = 0; i <= getVertexCount() - 1; i++) {
            if (adjacencyMatrix[vertex][i]) {
                a.add(i);
            }
        }
        return Collections.unmodifiableList(a);
    }
}
package graphs;

/**
 *  Rozhranie implementovane reprezentaciami neorientovanych grafov o vrcholoch 0, 1, ..., n-1 pre nejake prirodzene n.
 */
public interface UndirectedGraph extends DirectedGraph {
    /**
     * Metoda, ktora vrati pocet neorientovanych hran reprezentovaneho grafu.
     *
     * @return Pocet neorientovanych hran grafu.
     */
    int getUndirectedEdgeCount();
}
package graphs;

import java.util.*;

public class UndirectedEdge {
    private Set<Integer> incidentVertices;

    public UndirectedEdge(int u, int v) {
        incidentVertices = new HashSet<>();
        incidentVertices.add(u);
        incidentVertices.add(v);
    }

    public Set<Integer> getIncidentVertices() {
        return Collections.unmodifiableSet(incidentVertices);
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        return this.getClass() == o.getClass() && incidentVertices.equals(((UndirectedEdge) o).getIncidentVertices());
    }

    @Override
    public int hashCode() {
        return incidentVertices.hashCode();
    }
}
package graphs;

import java.util.*;

public class Edges {

    /**
     * Metoda, ktora prerobi zoskupenie neorientovanych hran na prislusny zoznam orientovanych hran.
     * @param undirectedEdges Zoznam neorientovanych hran.
     * @return                Prislusny zoznam orientovanych hran.
     */
    public static List<DirectedEdge> undirectedToDirectedEdges(Collection<? extends UndirectedEdge> undirectedEdges) {
        ArrayList<DirectedEdge> result = new ArrayList<>();
        for (UndirectedEdge e : undirectedEdges) {
            ArrayList<Integer> vertices = new ArrayList<>(e.getIncidentVertices());
            if (vertices.size() == 1) {
                result.add(new DirectedEdge(vertices.get(0), vertices.get(0)));
            } else {
                result.add(new DirectedEdge(vertices.get(0), vertices.get(1)));
                result.add(new DirectedEdge(vertices.get(1), vertices.get(0)));
            }
        }
        return Collections.unmodifiableList(result);
    }
}
package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca neorientovany graf pomocou zoznamov susedov jednotlivych jeho vrcholov.
 */
public class AdjacencyListsUndirectedGraph extends SuccessorListsDirectedGraph implements UndirectedGraph {
    private int undirectedEdgeCount;

    public AdjacencyListsUndirectedGraph(int vertexCount, Collection<? extends UndirectedEdge> undirectedEdges) {
        super(vertexCount, Edges.undirectedToDirectedEdges(undirectedEdges));
        undirectedEdgeCount = undirectedEdges.size();
    }

    @Override
    public int getUndirectedEdgeCount() {
        return undirectedEdgeCount;
    }
}
package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca neorientovany graf pomocou matice susednosti.
 */
public class AdjacencyMatrixUndirectedGraph extends AdjacencyMatrixDirectedGraph implements UndirectedGraph {
    private int undirectedEdgeCount;

    public AdjacencyMatrixUndirectedGraph(int vertexCount, Collection<? extends UndirectedEdge> undirectedEdges) {
        super(vertexCount, Edges.undirectedToDirectedEdges(undirectedEdges));
        undirectedEdgeCount = undirectedEdges.size();
    }

    @Override
    public int getUndirectedEdgeCount() {
        return undirectedEdgeCount;
    }
}
package graphs;

import java.io.*;
import java.util.*;

public class Trieda {
    /**
     * Metoda vypise do daneho vystupneho prudu pocet vrcholov a hran orientovaneho grafu, ako aj vsetky dvojice
     * vrcholov tvoriace orientovane hrany grafu.
     *
     * @param g   Graf, pre ktory sa vypis realizuje.
     * @param out Vystupny prud, do ktoreho sa vypis realizuje.
     */
    public static void printDirectedGraph(DirectedGraph g, PrintStream out) {
        int n = g.getVertexCount();
        out.println(n + " " + g.getDirectedEdgeCount());
        for (int u = 0; u <= n - 1; u++) {
            for (int v : g.outgoingEdgesDestinations(u)) {
                out.println(u + " " + v);
            }
        }
    }

    /**
     * Metoda, ktora precita textovu reprezentaciu orientovaneho grafu pozostavajucu z poctu vrcholov n, poctu hran m
     * a z m dvojic vrcholov udavajucich jednotlive orientovane hrany a vytvori podla nej graf urcenej implementacie.
     *
     * @param scanner        Skener, z ktoreho sa reprezentacia grafu cita.
     * @param implementation Implementacia vytvaraneho grafu (zoznamy naslednikov, alebo matica susednosti).
     * @return               Vytvoreny graf.
     */
    public static DirectedGraph readDirectedGraph(Scanner scanner, GraphImplementation implementation) {
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        List<DirectedEdge> edges = new ArrayList<>();
        for (int i = 1; i <= m; i++) {
            edges.add(new DirectedEdge(scanner.nextInt(), scanner.nextInt()));
        }
        DirectedGraph g = null;
        switch (implementation) {
            case LISTS:
                g = new SuccessorListsDirectedGraph(n, edges);
                break;
            case MATRIX:
                g = new AdjacencyMatrixDirectedGraph(n, edges);
                break;
        }
        return g;
    }

    /**
     * Metoda, ktora precita textovu reprezentaciu neorientovaneho grafu pozostavajucu z poctu vrcholov n, poctu hran m
     * a z m dvojic vrcholov udavajucich jednotlive neorientovane hrany a vytvori podla nej graf urcenej implementacie.
     *
     * @param scanner        Skener, z ktoreho sa reprezentacia grafu cita.
     * @param implementation Implementacia vytvaraneho grafu (zoznamy naslednikov, alebo matica susednosti).
     * @return               Vytvoreny graf.
     */
    public static UndirectedGraph readUndirectedGraph(Scanner scanner, GraphImplementation implementation) {
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        List<UndirectedEdge> edges = new ArrayList<>();
        for (int i = 1; i <= m; i++) {
            edges.add(new UndirectedEdge(scanner.nextInt(), scanner.nextInt()));
        }
        UndirectedGraph g = null;
        switch (implementation) {
            case LISTS:
                g = new AdjacencyListsUndirectedGraph(n, edges);
                break;
            case MATRIX:
                g = new AdjacencyMatrixUndirectedGraph(n, edges);
                break;
        }
        return g;
    }
}

Prehľadávanie (orientovaného alebo neorientovaného) grafu do hĺbky

Existencia cesty medzi dvojicou vrcholov

Riešme teraz nasledujúci problém: pre danú dvojicu vrcholov u a v nejakého (orientovaného alebo neorientovaného) grafu potrebujeme zistiť, či sú spojené sledom – t. j. či sa dá medzi nimi prejsť pomocou postupne na seba nadväzujúcich hrán (počet týchto hrán môže byť aj nulový, takže z každého vrcholu triviálne vedie sled do seba samého). Existencia sledu medzi dvoma vrcholmi je očividne ekvivalentná existencii cesty – čiže sledu, v ktorom sa žiaden vrchol neopakuje. Keby totiž najkratší sled medzi danými dvoma vrcholmi nebol súčasne aj cestou, dal by sa skrátiť tak, ako je to znázornené na nasledujúcom obrázku – a tým by sme dostali spor.

Sled cesta.png

V nasledujúcom preto budeme hovoriť o existencii ciest.

Pojmy sledu a cesty o niečo presnejšie:

  • Sledom v grafe rozumieme postupnosť vrcholov v0, v1, ..., vn takú, že n je prirodzené číslo (aj nula) a pre i = 1,...,n existuje v danom grafe hrana z vi-1 do vi.
  • Cestou rozumieme sled v0, v1, ..., vn taký, že vrcholy v0, v1, ..., vn sú po dvoch rôzne.
  • Dĺžkou sledu (alebo cesty) v0, v1, ..., vn nazveme číslo n, čiže počet hrán tento sled tvoriacich.

Z každého vrcholu v tak vedie do v sled dĺžky nula, ktorý je súčasne aj cestou; žiadne ďalšie sledy nulovej dĺžky v grafe existovať nemôžu. Sledy dĺžky jedna zodpovedajú hranám grafu a cesty dĺžky jedna zodpovedajú hranám, ktoré nie sú slučkami.

Hovoríme, že neorientovaný graf je súvislý, ak je ľubovoľná dvojica vrcholov tohto grafu spojená cestou.

Pre neorientované grafy možno problém existencie cesty medzi dvoma vrcholmi chápať aj ako úlohu zistiť, či sú tieto dva vrcholy v rovnakom komponente súvislosti grafu. Komponent súvislosti neorientovaného grafu je každý jeho (vzhľadom na inklúziu) maximálny podgraf s nenulovým počtom vrcholov, ktorý je súvislý – komponent súvislosti grafu teda pozostáva z nejakej neprázdnej podmnožiny vrcholov grafu a všetkých hrán pôvodného grafu spájajúcich vrcholy z tejto podmnožiny, pričom ľubovoľné dva vrcholy komponentu sú spojené cestou a žiaden vrchol komponentu nie je v pôvodnom grafe spojený cestou s vrcholom mimo komponentu. Stručnejšie to možno vyjadriť s využitím pozorovania, že existencia cesty v neorientovanom grafe je zjavne reláciou ekvivalencie na množine jeho vrcholov – komponenty súvislosti potom zodpovedajú triedam ekvivalencie tejto relácie. Napríklad neorientovaný graf na nasledujúcom obrázku pozostáva z troch komponentov súvislosti.

Graf3.png

Na riešenie problému existencie cesty použijeme prehľadávanie do hĺbky (angl. depth-first search) – podobné, ako ste už používali minulý semester pri vyfarbovaní súvislých oblastí v obdĺžnikovej mriežke. Procedúra na grafoch však bude všeobecnejšia:

  • Mriežku môžeme reprezentovať neorientovaným grafom, v ktorom vrcholy zodpovedajú políčkam mriežky. Dvojica vrcholov je navyše spojená hranou práve vtedy, keď zodpovedajúce políčka spolu susedia a súčasne majú rovnakú farbu.
  • Ostrovy rovnakej farby v mriežke potom zodpovedajú komponentom súvislosti výsledného neorientovaného grafu.

Prehľadávanie (orientovaného alebo neorientovaného) grafu do hĺbky budeme realizovať rekurzívnou metódou search, ktorá prehľadá všetkých ešte nenavštívených následníkov daného vrcholu. Informáciu o navštívení jednotlivých vrcholov si pritom budeme uchovávať v zozname visited. Metóda existsPath bude metódu search využívať na riešenie problému existencie cesty medzi danými dvoma vrcholmi.

/**
 * Pomocna metoda pre metodu existsPath, ktora rekurzivne prehlada vsetky doposial nenavstivene vrcholy
 * dosiahnutelne z daneho vrcholu.
 * @param g       Orientovany alebo neorientovany graf, v ktorom sa prehladavanie realizuje.
 * @param vertex  Vrchol grafu g, v ktorom sa prehladavanie zacina.
 * @param visited Zoznam obsahujuci informacie o navstiveni jednotlivych vrcholov grafu. Pri volani metody by malo
 *                platit visited.get(vertex) == false.
 */
private static void search(DirectedGraph g, int vertex, List<Boolean> visited) {
    visited.set(vertex, true);
    for (int successor : g.outgoingEdgesDestinations(vertex)) {
        if (!visited.get(successor)) {
            search(g, successor, visited);
        }
    }
}

/**
 * Metoda, ktora zisti, ci je dvojica vrcholov grafu spojena cestou.
 * @param g    Graf, v ktorom sa uloha realizuje.
 * @param from Pociatocny vrchol.
 * @param to   Koncovy vrchol.
 * @return     Vystup je true prave vtedy, ked v grafe g existuje cesta z vrcholu from do vrcholu to.
 */
public static boolean existsPath(DirectedGraph g, int from, int to) {
    ArrayList<Boolean> visited = new ArrayList<>();
    for (int i = 0; i <= g.getVertexCount() - 1; i++) {
        visited.add(false);
    }
    search(g, from, visited);
    return visited.get(to);
}

Cvičenie. Vytvorte abstraktnú triedu AbstractDirectedGraph implementujúcu rozhranie DirectedGraph a upravte triedy SuccessorListsDirectedGraph a AdjacencyMatrixDirectedGraph tak, aby dedili od triedy AbstractDirectedGraph. Prepíšte metódy existsPath a search uvedené vyššie ako metódy inštancie triedy AbstractDirectedGraph. Aký bude mať táto zmena vplyv na argumenty týchto metód?

Hľadanie komponentov súvislosti neorientovaného grafu

V prípade, že pracujeme s neorientovaným grafom a existenciu cesty medzi dvojicami vrcholov by sme chceli testovať veľakrát, oplatí sa nájsť všetky komponenty súvislosti v danom grafe. Komponenty môžeme očíslovať od nuly až po nejaké k - 1, pričom pre každý vrchol si môžeme pamätať číslo jeho komponentu. Túto úlohu realizuje nasledujúca trieda:

package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca rozdelenie neorientovaneho grafu na komponenty suvislosti.
 */
public class Components {
    /**
     * Neorientovany graf, ktoreho komponenty suvislosti su instanciou tejto triedy reprezentovane.
     */
    private UndirectedGraph g;

    /**
     * Zoznam, v ktorom si pre kazdy vrchol grafu g budeme pamatat cislo jeho komponentu.
     */
    private ArrayList<Integer> componentId;

    /**
     * Celkovy pocet komponentov suvislosti grafu g.
     */
    private int componentCount;

    /**
     * Konstruktor, ktory dostane ako argument neorientovany graf, najde komponenty suvislosti tohto grafu a informacie
     * o nich ulozi do premennych instancie.
     * @param g Neorientovany graf, ktoreho komponenty su reprezentovane instanciou tejto triedy.
     */
    public Components(UndirectedGraph g) {
        this.g = g;
        componentCount = 0;
        int n = g.getVertexCount();

        componentId = new ArrayList<>();
        for (int i = 0; i <= n - 1; i++) {
            componentId.add(-1);
        }

        for (int i = 0; i <= n - 1; i++) {
            if (componentId.get(i) == -1) {
                search(i, componentCount);
                componentCount++;
            }
        }
    }

    /**
     * Pomocna metoda pre konstruktor, ktora oznaci vrcholy jedneho komponentom suvislosti identifikatorom tohto
     * komponentu. Pracuje na baze prehladavania do hlbky.
     * @param vertex Vrchol, z ktoreho sa zacina prehladavanie vrcholov komponentu.
     * @param id     Identifikator komponentu suvislosti.
     */
    private void search(int vertex, int id) {
        componentId.set(vertex, id);
        for (int neighbour : g.outgoingEdgesDestinations(vertex)) {
            if (componentId.get(neighbour) == -1) {
                search(neighbour, id);
            }
        }
    }

    /**
     * Metoda, ktora zisti, ci v grafe, ktoreho komponenty reprezentuje instancia tejto triedy, existuje cesta spajajuca
     * danu dvojicu vrcholov.
     * @param from Pociatocny vrchol.
     * @param to   Koncovy vrchol.
     * @return     Metoda vrati true prave vtedy, ked v grafe existuje cesta z vrcholu from do vrcholu to.
     */
    public boolean existsPath(int from, int to) {
        return componentId.get(from).equals(componentId.get(to));
    }

    /**
     * Metoda, ktora vrati celkovy pocet komponentov grafu.
     * @return Pocet komponentov.
     */
    public int getComponentCount() {
        return componentCount;
    }
}

Nasledujúci kód načíta neorientovaný graf a dvojicu jeho vrcholov. Na konzolu následne vypíše, či sú tieto dva vrcholy v danom grafe spojené cestou.

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Zadaj neorientovany graf:");
    UndirectedGraph g = readUndirectedGraph(scanner, GraphImplementation.LISTS);
    System.out.println("Zadaj dvojicu vrcholov grafu:");
    int from = scanner.nextInt();
    int to = scanner.nextInt();

    Components components = new Components(g);
    if (components.existsPath(from, to)) {
        System.out.println("Vrcholy su spojene cestou.");
    } else {
        System.out.println("Vrcholy nie su spojene cestou.");
    }
}

Prehľadávanie (orientovaného alebo neorientovaného) grafu do šírky

Podobne ako prehľadávanie do hĺbky možno vo všeobecnejšom kontexte grafov aplikovať aj prehľadávanie do šírky (angl. breadth-first search), ktorého variant pre obdĺžnikovú mriežku poznáte z minulého semestra. Prehľadávanie grafu do šírky bude možné použiť na hľadanie najkratších ciest medzi dvojicami vrcholov orientovaného (a teda aj neorientovaného) grafu, kde dĺžka cesty je meraná počtom hrán.

Hľadanie najkratšej cesty

Hľadanie najkratších ciest v grafe – či už orientovanom alebo neorientovanom – možno realizovať napríklad nasledujúcou triedou ShortestPathsFromVertex:

  • Jej konštruktor dostane ako parameter graf g a nejaký jeho význačný „štartovací” vrchol start. Následne spustí na grafe g prehľadávanie do šírky z vrcholu start.
  • Takto sa postupne prehľadajú vrcholy vo vzdialenosti 1 od start, potom vrcholy vo vzdialenosti 2 od start, atď. Na zabezpečenie takéhoto poradia sa použije rad, podobne ako pri algoritme na mriežke minulý semester. V každom momente vykonávania algoritmu môže tento rad obsahovať vrcholy najviac dvoch rôznych vzdialeností od start.
  • Pre každý vrchol v sa počas prehľadávania do zoznamu distances uloží jeho vzdialenosť od vrcholu start a do zoznamu predecessors sa uloží vrchol u, z ktorého bol vrchol v objavený – musí pritom vždy ísť o predposledný vrchol na jednej z najkratších ciest zo start do v.
  • Metóda distanceFromStart bude pre daný vrchol vertex vracať jeho vzdialenosť od vrcholu start. Tu sa jednoducho využije hodnota uložená v zozname distances.
  • Metóda shortestPathFromStart bude pre daný vrchol vertex vracať najkratšiu cestu z vrcholu start do vrcholu vertex reprezentovanú zoznamom vrcholov. Tú bude konštruovať od konca: začne vo vrchole vertex a postupne bude hľadať predchodcov pomocou hodnôt uložených v zozname predecessors.
package graphs;

import java.util.*;

/**
 * Trieda reprezentujuca najkratsie cesty z pevne daneho pociatocneho vrcholu do vsetkych ostatnych vrcholov grafu.
 */
public class ShortestPathsFromVertex {
    /**
     * Zoznam obsahujuci pre kazdy vrchol grafu jeho vzdialenost zo "startovacieho" vrcholu z argumentu konstruktora.
     * Ak zo "startovacieho" vrcholu do nejakeho vrcholu nevedie ziadna cesta, bude namiesto jeho vzdialenosti v zozname
     * ulozena hodnota -1.
     */
    private List<Integer> distances;

    /**
     * Zoznam obsahujuci pre kazdy vrchol v grafu g jeho predchodcu na najkratsej ceste zo "startovacieho" vrcholu
     * do vrcholu v. Pre "startovaci" vrchol a vrcholy, do ktorych z neho nevedie cesta, bude v zozname ulozena
     * hodnota -1.
     */
    private List<Integer> predecessors;

    /**
     * Konstruktor, ktory pre dany graf a "startovaci" vrchol rovno aj najde najkratsie cesty zo startovacieho vrcholu
     * do vsetkych ostatnych vrcholov grafu.
     * @param g     Graf, v ktorom sa hladanie ciest realizuje.
     * @param start "Startovaci" vrchol.
     */
    public ShortestPathsFromVertex(DirectedGraph g, int start) {
        /* Inicializacia zoznamov distances a predecessors: */
        distances = new ArrayList<>();
        predecessors = new ArrayList<>();
        for (int i = 0; i <= g.getVertexCount() - 1; i++) {
            distances.add(-1);
            predecessors.add(-1);
        }

        /* Samotne prehladavanie do sirky: */
        Queue<Integer> queue = new LinkedList<>();
        distances.set(start, 0);
        queue.add(start);
        while (!queue.isEmpty()) {
            // Vyberieme vrchol z radu, prejdeme vsetkych jeho naslednikov, nenavstivenych spracujeme a vlozime do radu:
            int vertex = queue.remove();
            for (int successor : g.outgoingEdgesDestinations(vertex)) {
                if (distances.get(successor) == -1) {
                    distances.set(successor, distances.get(vertex) + 1);
                    predecessors.set(successor, vertex);
                    queue.add(successor);
                }
            }
        }
    }

    /**
     * Metoda, ktora vrati dlzku najkratsej cesty z vrcholu start do daneho vrcholu.
     * @param vertex Vrchol, vzdialenost ktoreho z vrcholu start sa pocita.
     * @return       Dlzka najkratsej cesty z vrcholu start do vrcholu vertex. Ak ziadna neexistuje, vrati sa -1.
     */
    public int distanceFromStart(int vertex) {
        return distances.get(vertex);
    }

    /**
     * Metoda, ktora vrati najkratsiu cestu z vrcholu start do daneho vrcholu, reprezentovanu ako zoznam vrcholov.
     * @param vertex Vrchol, najkratsia cesta do ktoreho z vrcholu start sa pocita.
     * @return       Nemodifikovatelny zoznam obsahujuci postupne vsetky vrcholy najkratsej cesty zo start do vertex.
     *               Ak ziadna cesta zo start do vertex neexistuje, vrati metoda referenciu null.
     */
    public List<Integer> shortestPathFromStart(int vertex) {
        if (distances.get(vertex) == -1) {
            return null;
        }
        LinkedList<Integer> path = new LinkedList<>();
        int v = vertex;
        while (v != -1) {
            path.addFirst(v);
            v = predecessors.get(v);
        }
        return path;
    }
}

Nasledujúci kód načíta graf a dvojicu jeho vrcholov; vypíše najkratšiu cestu medzi danými vrcholmi.

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Zadaj graf:");
    DirectedGraph g = readDirectedGraph(scanner, GraphImplementation.LISTS);
    System.out.println("Zadaj pociatocny a koncovy vrchol:");
    int from = scanner.nextInt();
    int to = scanner.nextInt();

    ShortestPathsFromVertex shortestPathsFromVertex = new ShortestPathsFromVertex(g, from);
    System.out.println("Najkratsia cesta ma dlzku " + shortestPathsFromVertex.distanceFromStart(to) + ".");
    List<Integer> shortestPath = shortestPathsFromVertex.shortestPathFromStart(to);
    if (shortestPath != null) {
        System.out.println(shortestPath);
    }
}

Stromy prehľadávania do hĺbky a do šírky

Označme pri prehľadávaní do hĺbky aj do šírky tie hrany, ktoré boli použité pri objavovaní nových vrcholov – to znamená: pri prehľadávaní do hĺbky hrany (u,v) také, že vo volaní metódy search pre vrchol u sa táto metóda zavolala rekurzívne aj pre vrchol v a pri prehľadávaní do šírky hrany (u,v) také, že sa v rámci prehľadávania následníkov vrcholu u do radu pridal vrchol v.

  • V oboch prípadoch potom takéto hrany tvoria strom zakorenený vo vrchole, v ktorom prehľadávanie začalo (pri orientovaných grafoch sú všetky hrany orientované smerom od koreňa k listom).
  • Hovoríme teda o stromoch prehľadávania do hĺbky resp. do šírky (angl. DFS Tree resp. BFS Tree).
  • V prípade, že je graf neorientovaný a súvislý, ide v obidvoch prípadoch o jeho kostru (t. j. strom zložený zo všetkých vrcholov grafu a jeho vybraných hrán).
  • Pri stromoch prehľadávania do šírky reprezentujú cesty od koreňa k nejakému inému vrcholu vždy niektorú z najkratších ciest medzi príslušnými vrcholmi grafu.

Príklad. Nižšie je znázornený diagram orientovaného grafu a jeho stromy prehľadávania do hĺbky a do šírky v prípade, že prehľadávanie začne vo vrchole 0 a pri spracúvaní následníkov sa postupuje vzostupne podľa čísel vrcholov (čo nemusí byť vždy tak).

Strom prehľadávania do hĺbky:

Graf4.png                            Graf4DFS.png                             DFSTree.png

Strom prehľadávania do šírky:

Graf4.png                            Graf4BFS.png                             BFSTree.png

Prehľadávanie s návratom na grafoch

Pre veľa úloh na grafoch nie sú známe – a v prípade platnosti niektorých hypotéz z teoretickej informatiky často ani neexistujú – žiadne efektívne algoritmy. Prehľadávaním s návratom však vieme spočítať odpoveď aspoň pre malé vstupy.

Hľadanie ciest danej dĺžky

Nasledujúca trieda FixedLengthPaths pre daný graf g, danú dvojicu vrcholov from, to a dané prirodzené číslo length nájde všetky cesty dĺžky presne length vedúce v grafe g z vrcholu from do vrcholu to; takéto cesty môžu evidentne existovať iba v prípade, že je prirodzené číslo length menšie, než počet vrcholov grafu. Prehľadávanie s návratom sa spustí hneď v konštruktore a nájdené cesty sa uložia do zoznamu paths.

Pri prehľadávaní sa v spájanom zozname path budú postupne generovať všetky cesty hľadaného typu. Pre každý vrchol budeme mať navyše v zozname visited poznačené, či sme ho už v generovanej ceste použili. Akonáhle nájdeme cestu požadovanej dĺžky končiacu vo vrchole to, uložíme ju do zoznamu paths.

package graphs;

import java.util.*;

/**
 * Trieda realizujuca najdenie vsetkych ciest danej fixnej dlzky medzi danou dvojicou vrcholov daneho grafu.
 */
public class FixedLengthPaths {
    /**
     * Graf, v ktorom sa hladanie ciest realizuje.
     */
    private DirectedGraph g;

    /**
     * Koncovy vrchol hladanych ciest.
     */
    private int to;

    /**
     * Pozadovana dlzka hladanych ciest.
     */
    private int length;

    /**
     * Pomocny zoznam, v ktorom sa budu pomocou prehladavania s navratom postupne generovat jednotlive cesty.
     */
    private LinkedList<Integer> path;

    /**
     * Pomocny zoznam, v ktorom si budeme pocas generovania ciest pre kazdy vrchol pamatat, ci sa nachadza alebo
     * nenachadza v doposial vygenerovanej casti cesty.
     */
    private ArrayList<Boolean> visited;

    /**
     * Zoznam, v ktorom budu ulozene vsetky vygenerovane cesty.
     */
    private List<List<Integer>> paths;

    /**
     * Konstruktor, ktory rovno aj spusti prehladavanie s navratom a do zoznamu paths postupne ulozi vsetky cesty danej
     * dlzky medzi danou dvojicou vrcholov daneho grafu.
     * @param g      Graf, v ktorom sa hladanie ciest realizuje.
     * @param from   Pociatocny vrchol hladanych ciest.
     * @param to     Koncovy vrchol hladanych ciest.
     * @param length Pozadovana dlzka hladanych ciest.
     */
    public FixedLengthPaths(DirectedGraph g, int from, int to, int length) {
        this.g = g;
        this.to = to;
        this.length = length;
        paths = new LinkedList<>();

        visited = new ArrayList<>();
        for (int i = 0; i <= g.getVertexCount() - 1; i++) {
            visited.add(false);
        }
        path = new LinkedList<>();
        path.add(from);
        visited.set(from, true);
        search();
    }

    /**
     * Metoda realizujuca samotne rekurzivne prehladavanie s navratom. Ak je dosial vygenerovana cast cesty kratsia nez
     * length, postupne sa vyskusaju vsetky moznosti jej predlzenia. V pripade, ze uz ide o cestu dlzky length, overi
     * sa, ci cesta konci vo vrchole to a ak ano, prida sa kopia tejto cesty do zoznamu paths.
     */
    private void search() {
        if (path.size() == length + 1) {   // Dlzka zoznamu je vzdy o jedna vacsia, nez dlzka nim reprezentovanej cesty.
            if (path.getLast() == to) {
                paths.add(Collections.unmodifiableList(new LinkedList<>(path)));
            }
        } else {
            for (int successor : g.outgoingEdgesDestinations(path.getLast())) {
                if (!visited.get(successor)) {
                    visited.set(successor, true);
                    path.add(successor);
                    search();
                    path.removeLast();
                    visited.set(successor, false);
                }
            }
        }
    }

    /**
     * Metoda, ktora vrati nemodifikovatelny pohlad na zoznam vsetkych vygenerovanych ciest.
     * @return Nemodifikovatelny zoznam vsetkych ciest dlzky length veducich v grafe g z vrcholu from do vrcholu to.
     */
    public List<List<Integer>> getPaths() {
        return Collections.unmodifiableList(paths);
    }
}

Nasledujúci kód načíta graf, dvojicu vrcholov from, to a prirodzené číslo length a vypíše všetky cesty dĺžky length z vrcholu from do vrcholu to.

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Zadaj graf:");
    DirectedGraph g = readDirectedGraph(scanner, GraphImplementation.LISTS);
    System.out.println("Zadaj pociatocny a koncovy vrchol:");
    int from = scanner.nextInt();
    int to = scanner.nextInt();
    System.out.println("Zadaj dlzku generovanych ciest:");
    int length = scanner.nextInt();

    FixedLengthPaths fixedLengthPaths = new FixedLengthPaths(g, from, to, length);
    System.out.println("Cesty dlzky " + length + ":");
    for (List<Integer> path : fixedLengthPaths.getPaths()) {
        System.out.println(path);
    }
}
Graf5.png

Príklad. Pre orientovaný graf s vrcholmi {0,...,4} a hranami {(0,1),(0,2),(0,3),(1,2),(2,3),(2,4),(4,3)}, počiatočný vrchol 0 a koncový vrchol 3 dostávame nasledujúce výstupy

Cesty dlzky 1:
[0, 3]
Cesty dlzky 2:
[0, 2, 3]
Cesty dlzky 3:
[0, 1, 2, 3]
[0, 2, 4, 3]
Cesty dlzky 4:
[0, 1, 2, 4, 3]

Cvičenia:

  • Upravte triedu FixedLengthPaths tak, aby namiesto hľadania a ukladania všetkých ciest danej dĺžky iba počítala, koľko ich je.
  • Upravte triedu FixedLengthPaths tak, aby iba zisťovala, či existuje cesta danej dĺžky (po prvej nájdenej ceste je teda možné prehľadávanie ukončiť).
  • Navrhnite spôsoby, ako v niektorých prípadoch zistiť, že rozrobenú cestu už nie je možné požadovaným spôsobom predĺžiť a danú vetvu prehľadávania s návratom tak možno ukončiť.

Hľadanie najdlhšej cesty

Uvažujme teraz problém nájdenia nejakej z najdlhších ciest z u do v (ak existuje aspoň jedna). Túto úlohu bude realizovať trieda LongestPath, ktorá sa oproti triede FixedLengthPaths bude líšiť len málo.

  • Počas prehľadávania si budeme pamätať najdlhšiu doposiaľ nájdenú cestu.
  • Vždy, keď prídeme do cieľového vrcholu, porovnáme dĺžku práve nájdenej cesty s najdlhšou doposiaľ nájdenou cestou.
package graphs;

import java.util.*;

/**
 * Trieda realizujuca najdenie najdlhsej cesty medzi danou dvojicou vrcholov grafu.
 */
public class LongestPath {
    /**
     * Graf, v ktorom sa hladanie ciest realizuje.
     */
    private DirectedGraph g;

    /**
     * Koncovy vrchol hladanych ciest.
     */
    private int to;

    /**
     * Pomocny zoznam, v ktorom sa budu pomocou prehladavania s navratom postupne generovat jednotlive cesty.
     */
    private LinkedList<Integer> path;

    /**
     * Pomocny zoznam, v ktorom si budeme pocas generovania ciest pre kazdy vrchol pamatat, ci sa nachadza alebo
     * nenachadza v doposial vygenerovanej casti cesty.
     */
    private ArrayList<Boolean> visited;

    /**
     * Zoznam, v ktorom bude ulozena najdlhsia cesta medzi danou dvojicou vrcholov (pocas prehladavania pojde
     * o najdlhsiu doposial najdenu cestu).
     */
    private List<Integer> longestPath;

    /**
     * Konstruktor, ktory rovno aj spusti prehladavanie s navratom a do zoznamu longestPath ulozi niektoru spomedzi
     * najdlhsich ciest medzi danou dvojicou vrcholov grafu.
     * @param g      Graf, v ktorom sa hladanie ciest realizuje.
     * @param from   Pociatocny vrchol hladanych ciest.
     * @param to     Koncovy vrchol hladanych ciest.
     */
    public LongestPath(DirectedGraph g, int from, int to) {
        this.g = g;
        this.to = to;

        visited = new ArrayList<>();
        for (int i = 0; i <= g.getVertexCount() - 1; i++) {
            visited.add(false);
        }
        path = new LinkedList<>();
        path.add(from);
        visited.set(from, true);
        search();
    }

    /**
     * Metoda realizujuca samotne rekurzivne prehladavanie s navratom. V pripade, ze sa vygenerovala cesta konciaca
     * vo vrchole to, porovna sa jej dlzka s dlzkou doposial najdlhsej najdenej cesty a ak je dlhsia, ulozi sa ako nova
     * doposial najdlhsia cesta. V opacnom pripade sa vyskusaju vsetky moznosti predlzenia cesty.
     */
    private void search() {
        if (path.getLast() == to) {
            if (longestPath == null || path.size() > longestPath.size()) {
                longestPath = new LinkedList<>(path);
            }
        } else {
            for (int successor : g.outgoingEdgesDestinations(path.getLast())) {
                if (!visited.get(successor)) {
                    visited.set(successor, true);
                    path.add(successor);
                    search();
                    path.removeLast();
                    visited.set(successor, false);
                }
            }
        }
    }

    /**
     * Metoda, ktora vrati najdenu najdlhsiu cestu medzi danou dvojicou vrcholov.
     * @return Nemodifikovatelny pohlad na zoznam vrcholov na najdlhsej ceste z vrcholu from do vrcholu to.
     */
    public List<Integer> getLongestPath() {
        if (longestPath != null) {
            return Collections.unmodifiableList(longestPath);
        } else {
            return null;
        }
    }
}

Použitie triedy:

LongestPath longestPath = new LongestPath(g, from, to);
List<Integer> longest = longestPath.getLongestPath();
if (longest != null) {
    System.out.println("Najdlhsia cesta: " + longest);
}

Príklad výstupu na rovnakom grafe ako vyššie pre počiatočný vrchol 0 a koncový vrchol 3:

Najdlhsia cesta: [0, 1, 2, 4, 3]