Graph: Unterschied zwischen den Versionen

Aus SibiWiki
Zur Navigation springen Zur Suche springen
 
(64 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
[[Kategorie:Informatik]]
[[Kategorie:Informatik]]
[[Kategorie:Datenstrukturen]]
[[Kategorie:Informatik-Abitur]]
[[Kategorie:Komplexe Datenstrukturen]]
[[Kategorie:Informatik-Q1]]
Ein Graph ist in der Graphentheorie eine abstrakte Struktur, die eine Menge von Objekten zusammen mit den zwischen diesen Objekten bestehenden Verbindungen repräsentiert.
[[Kategorie:Datenstrukturen(IF)]]


Die mathematischen Abstraktionen der Objekte werden dabei Knoten des Graphen genannt. Die paarweisen Verbindungen zwischen Knoten heißen Kanten.
[[File:Graph_staedte_entfernungen.png|thumb|Graph für die Distanzen zwischen Städten |400px]]
<font color='red'>'''nur relevant für den Leistungskurs!'''</font>


Ein Graph kann entweder in als Graph, in einer Adjazenzliste oder in einer Adjazenzmatrix dargestellt werden.
Ein Graph ist in der Graphentheorie eine Struktur, die eine Menge von Objekten zusammen mit den zwischen diesen Objekten bestehenden Verbindungen repräsentiert.
==Schnittstellenbeschreibung==
Die mathematischen Abstraktionen der Objekte werden dabei '''Knoten''' des Graphen genannt. Die paarweisen Verbindungen zwischen Knoten heißen '''Kanten'''.
* [[Datei:Graph.pdf]]
 
* [[Datei:GraphNode.pdf]]
Ein Graph kann entweder als Graph, als '''Adjazenzliste''' oder als '''Adjazenzmatrix''' dargestellt werden.
TODO: Erklärungen zur Schnittstelle
 
==Traversierungen von Graphen==
=Erklärvideos=
Wie bei Bäumen gibt es auch bei Graphen viele Anwendungen, in denen ein Graph knotenweise durchlaufen werden muss.
 
Die Traversierungsverfahren ähneln denen bei Bäumen, berücksichtigen allerdings noch die speziellen Gegebenheiten von Graphen, nämlich:  
* [https://youtu.be/bZLxTuG8cY0 Breiten- und Tiefendurchlauf: Theorie]
* Graphenknoten können mehr als zwei Nachbarn haben
 
* Graphen können Querverbindungen und "Kreise" enthalten
* [https://youtu.be/2mHKVXy0xqo Breitendurchlauf: Implementierung]
 
* [https://youtu.be/cdNonZrj_WE Tiefendurchlauf: Implementierung]
 
=Fachbegriffe=


=== Tiefendurchlauf (DFS)===
Graph, Knoten, markieren, Kante, Gewicht, Traversierung, Breitendurchlauf, Tiefendurchlauf
Der Tiefendurchlauf durch einen Graphen funktioniert rekursiv und entspricht der [[Binärbaum#Beispiel:_preorder-Traversierung|Preorder-Traversierung eines Baumes]].


<code>
=Schnittstellenbeschreibung=
  public List erreichbareNodes(Graph pGraph, GraphNode pNode){
    List knoten = new List();
    pNode.mark();
    //Alle Nachbarn des Startknoten holen
    List nachbarnListe = pGraph.getNeighbours(pNode);
    //Nachbarn mit while-Schleife durchlaufen
    nachbarnListe.toFirst();
    while(nachbarnListe.hasAccess()){
      GraphNode curNode = (GraphNode)h.getObject();
      //Wenn der aktuelle Nachbar nicht markiert ist, also noch nicht abgelaufen wurde,
      //zur Liste hinzufügen und auch seine Nachbarn durch den rekursiven Aufruf ablaufen.
      if( !curNode.isMarked() ){
        knoten.append(curNode);
        curNode.mark();
        //Rekursiver Aufruf
        knoten.concat(erreichbareNodes(pGraph, curNode));
      }
      nachbarnListe.next();
    }
    return knoten;
  }
</code>


=== Breitendurchlauf (BFS)===
[[Medium:Graph_Schnittstellen_Abitur_2018.pdf|Graph Schnittstellenbeschreibung (ab Abi 2018)]]
TODO: Erklärungen zum Breitendurchlauf


<code>
==Adjazenzmatrix==
  TODO: gut kommentierter Quellcode des Breitendurchlaufs
Knoten und Kanten eines Graphen können in Form einer Matrix dargestellt werden.
</code>
Die Matrix ist dabei spiegelsymmetrisch.


== Kürzeste Wege in Graphen: Der Dijkstra-Algorithmus ==
Der oben dargestellte Graph hat folgende '''Adjazenzmatrix''':
=== Idee ===


TODO: Erklärungen zu Dijkstra (besondere Schnittstelle erklären, Strategie erklären, Besonderheiten,...)
{| class="wikitable"
|-
!|        ||Berlin || Bremen || Dortmund || Frankfurt || Hamburg || Hannover || Kassel || Koeln
|-
||<b>Berlin</b>  ||      ||        ||          ||          || 289    || 290      ||        ||
|-
||<b>Bremen</b>  ||      ||        || 234      ||          || 119    || 122      ||        ||     
|-
||<b>Dortmund</b> ||      || 234    ||          ||          ||        || 210      || 160    || 93
|-
||<b>Frankfurt</b>||      ||        ||          ||          ||        ||          || 193    || 189
|-
||<b>Hamburg</b>  || 289  || 119    ||          ||          ||        || 150      ||        ||
|-
||<b>Hannover</b> || 290  || 122    || 210      ||          || 150    ||          || 167    ||
|-
||<b>Kassel</b>  ||      ||        || 160      || 193      ||        || 167      ||        ||
|-
||<b>Koeln</b>    ||      ||        || 93      || 189      ||        ||          ||        ||
|}


=== Implementierung ===
TODO: Algorithmus zum Erzeugen eines Graphen aus einem 2D-Array (Adjazenzmatrix)
TODO: kommmentieren!


<code>
==Adjazenzliste==
<b>public class DijkstraAlgorithmus {</b>
Man kann die Informationen eines Graphen auch als Adjazenzliste darstellen. Das bietet sich z.B. an, wenn es in einem Graphen nur wenige Kanten gibt; dann ist die komplette Adjazenzmatrix Platzverschwendung.
  GraphWithViewer map;
  ListWithViewer gelbeListe;
 
 
  public void initMap(){
      map = new GraphWithViewer();
      DijkstraGraphNode kiel = new DijkstraGraphNode("Kiel");
      map.addNode(kiel);
      DijkstraGraphNode luebeck = new DijkstraGraphNode("Luebeck");
      map.addNode(luebeck);
      DijkstraGraphNode hamburg = new DijkstraGraphNode("Hamburg");
      map.addNode(hamburg);
      DijkstraGraphNode berlin = new DijkstraGraphNode("Berlin");
      map.addNode(berlin);
      DijkstraGraphNode bremen = new DijkstraGraphNode("Bremen");
      map.addNode(bremen);
      DijkstraGraphNode hannover = new DijkstraGraphNode("Hannover");
      map.addNode(hannover);
      DijkstraGraphNode dortmund = new DijkstraGraphNode("Dortmund");
      map.addNode(dortmund);
      DijkstraGraphNode bochum = new DijkstraGraphNode("Bochum");
      map.addNode(bochum);
      DijkstraGraphNode koeln = new DijkstraGraphNode("Koeln");
      map.addNode(koeln);
      DijkstraGraphNode bonn = new DijkstraGraphNode("Bonn");
      map.addNode(bonn);
      DijkstraGraphNode mainz = new DijkstraGraphNode("Mainz");
      map.addNode(mainz);
      DijkstraGraphNode frankfurt = new DijkstraGraphNode("Frankfurt");
      map.addNode(frankfurt);
      DijkstraGraphNode kassel = new DijkstraGraphNode("Kassel");
      map.addNode(kassel);
      DijkstraGraphNode wuerzburg = new DijkstraGraphNode("Wuerzburg");
      map.addNode(wuerzburg);
      DijkstraGraphNode leipzig = new DijkstraGraphNode("Leipzig");
      map.addNode(leipzig);
      DijkstraGraphNode nuernberg = new DijkstraGraphNode("Nuernberg");
      map.addNode(nuernberg);
      DijkstraGraphNode stuttgart = new DijkstraGraphNode("Stuttgart");
      map.addNode(stuttgart);
      DijkstraGraphNode muenchen = new DijkstraGraphNode("Muenchen");
      map.addNode(muenchen);
      DijkstraGraphNode karlsruhe = new DijkstraGraphNode("Karlsruhe");
      map.addNode(karlsruhe);


      map.addEdge(kiel, hamburg, 97);
In der Adjazenzliste werden '''<u>links</u> von oben nach unten''' alle Knoten aufgeführt, z.B. in alphabetischer Reihenfolge; die Reihenfolge ist aber frei wählbar.
      map.addEdge(kiel, luebeck, 84);
      map.addEdge(luebeck, hamburg, 74);
      map.addEdge(luebeck, berlin, 284);
      map.addEdge(berlin, hamburg, 289);
      map.addEdge(hamburg, bremen, 119);
      map.addEdge(bremen, hannover, 122);
      map.addEdge(hannover, hamburg, 150);
      map.addEdge(berlin, hannover, 290);
      map.addEdge(berlin, leipzig, 188);
      map.addEdge(hannover, kassel, 167);
      map.addEdge(leipzig, kassel, 250);
      map.addEdge(kassel, dortmund, 160);
      map.addEdge(dortmund, bremen, 234);
      map.addEdge(dortmund, hannover, 210);
      map.addEdge(dortmund, bochum, 22);
      map.addEdge(dortmund, koeln, 93);
      map.addEdge(koeln, bochum, 82);
      map.addEdge(koeln, bonn, 29);
      map.addEdge(bonn, mainz, 169);
      map.addEdge(frankfurt, mainz, 45);
      map.addEdge(frankfurt, kassel, 193);
      map.addEdge(leipzig, nuernberg, 278);
      map.addEdge(kassel, wuerzburg, 209);
      map.addEdge(wuerzburg, nuernberg, 110);
      map.addEdge(wuerzburg, frankfurt, 119);
      map.addEdge(nuernberg, muenchen, 166);
      map.addEdge(muenchen, stuttgart, 223);
      map.addEdge(nuernberg, stuttgart, 208);
      map.addEdge(stuttgart, karlsruhe, 82);
      map.addEdge(karlsruhe, frankfurt, 147);
      map.addEdge(frankfurt, koeln, 189);
      map.switchToISOMLayout();
  }
 
  <b>public void dijkstraAlgorithmus(String startpunkt) {</b>
      gelbeListe = new ListWithViewer();
      DijkstraGraphNode startNode = (DijkstraGraphNode) map.getNode(startpunkt);
      startNode.setDistanz(0);
      startNode.setWeg(new List());
      gelbeListe.append(startNode);
      while(gelbeListe.isEmpty() == false){
        gelbeListe.toFirst();
        DijkstraGraphNode node = (DijkstraGraphNode) gelbeListe.getObject();
        //System.out.println("node: "+node);
        gelbeListe.remove();
        node.mark();
        node.ausgeben();
        List neighbours = map.getNeighbours(node);
        neighbours.toFirst();
        while(neighbours.hasAccess()){
            DijkstraGraphNode currentNeighbour = (DijkstraGraphNode) neighbours.getObject();
            //System.out.println("currentNeighbour: "+currentNeighbour);
            double streckeNodeCurrentNeighbour = map.getEdgeWeight(node, currentNeighbour);
            if(currentNeighbour.getDistanz() > node.getDistanz()+streckeNodeCurrentNeighbour){
              // ueber node fuehrt eine kuerzere Strecke zu currentNeighbour!
              currentNeighbour.setWeg(node.getWeg());
              currentNeighbour.setDistanz(node.getDistanz()+streckeNodeCurrentNeighbour);
              // currentNeighbour in gelbeListe einfuegen bzw. ersetzen
              inGelbeListeUpdaten(currentNeighbour);
            }
            neighbours.next();
        }
      }
  }


  private void inGelbeListeUpdaten(DijkstraGraphNode node) {
<u>'''Nach rechts'''</u> werden alle Nachbarknoten des linken Knoten mit den zugehörigen Entfernungen eingetragen. Auch hier ist die Reihenfolge frei wählbar.
      boolean inserted = false;
      gelbeListe.toFirst();
      while(gelbeListe.hasAccess()){
        DijkstraGraphNode aktuell = (DijkstraGraphNode) gelbeListe.getObject();
        if(aktuell.getDistanz() >= node.getDistanz()){
            gelbeListe.insert(node);
            inserted = true;
            break;
        }
        gelbeListe.next();
      }
      if(inserted){
        // ggf. taucht currentNeighbour nochmal in der Liste auf!
        // dann muss er entfernt werden!
        while(gelbeListe.hasAccess()){
            DijkstraGraphNode aktuell = (DijkstraGraphNode) gelbeListe.getObject();
            if(aktuell.getName().equals(node.getName())){
              gelbeListe.remove();
              break;
            }
            gelbeListe.next();
        }
      }
      else{
        // der Knoten wurde noch nicht eingefuegt!
        gelbeListe.append(node);
      }
     
  }
 
  public static void main(String[] args) {
      DijkstraAlgorithmus da = new DijkstraAlgorithmus();
      da.initMap();
      da.dijkstraAlgorithmus("Muenchen");
  }
}


Der oben dargestelle Graph hat folgende Adjazenzliste:


<code>
'''Berlin''' &rarr; Hamburg (289) &rarr; Hannover (290)
&nbsp;&nbsp;&nbsp;&darr;
'''Bremen'''  &rarr; Dortmund (234) &rarr; Hamburg (119) &rarr; Hannover (122)
&nbsp;&nbsp;&nbsp;&darr;
'''Dortmund''' &rarr; Bremen (234) &rarr; Hannover (210) &rarr; Kassel (190) &rarr; Köln (93)
&nbsp;&nbsp;&nbsp;&darr;
'''Frankfurt''' &rarr; Kassel (193) &rarr; Köln (189)
&nbsp;&nbsp;&nbsp;&darr;
'''Hamburg''' &rarr; Berlin (289) &rarr; Bremen (119) &rarr; Hannover (150)
&nbsp;&nbsp;&nbsp;&darr;
'''Hannover''' &rarr; Berlin (290) &rarr; Bremen (122) &rarr; Dortmund (210) &rarr; Hamburg (150) &rarr; Kassel (167)
&nbsp;&nbsp;&nbsp;&darr;
'''Kassel''' &rarr; Dortmund (160) &rarr; Frankfurt (193) &rarr; Hannover (167)
&nbsp;&nbsp;&nbsp;&darr;
'''Koeln''' &rarr; Dortmund (93) &rarr; Frankfurt (189)
</code>


==Traversierungen von Graphen==
Wie bei Bäumen gibt es auch bei Graphen viele Anwendungen, in denen ein Graph knotenweise durchlaufen werden muss.
Die Traversierungsverfahren ähneln denen bei Bäumen, berücksichtigen allerdings noch die speziellen Gegebenheiten von Graphen, nämlich:
* Graphenknoten können mehr als zwei Nachbarn haben
* Graphen können Querverbindungen und "Kreise" enthalten


<b>public class DijkstraGraphNode extends GraphNode implements Comparable {</b>
=== Tiefendurchlauf===
  private List weg;
====Erläuterung====
  private double distanz;
Beim Tiefendurchlauf ''(engl. Depth First Search - DFS)'' durch einen Baum nimmt man ausgehend von einem Startknoten den ersten Nachbarknoten. Von diesem nimmt man wieder den ersten Nachbarknoten usw. Wenn man dann in eine Sackgasse gerät, geht man eine Stufe zurück und nimmt den nächsten Nachbarknoten.
 
Natürlich werden Knoten, die man schon besucht hat, nicht noch einmal berücksichtigt.
  public DijkstraGraphNode(String name) {
      super(name);
      distanz = 1000000;
      weg = new List();
  }
 
  public void setDistanz(double distanz){
      this.distanz = distanz;
  }
 
  public double getDistanz(){
      return distanz;
  }
 
  public void setWeg(List neuerWeg){
      this.weg = new List();
      neuerWeg.toFirst();
      while(neuerWeg.hasAccess()){
        weg.append(neuerWeg.getObject());
        neuerWeg.next();
      }
      // den Knoten selber anhaengen!
      weg.append(this.getName());
  }
 
  public void ausgeben(){
      System.out.println(toString());
      weg.toFirst();
      String wegString = "";
      while(weg.hasAccess()){
        wegString+= weg.getObject();
        wegString+= "->";
        weg.next();
      }
      System.out.println(wegString);
  }
 
  public String toString(){
      String ergebnis = this.getName()+": "+this.distanz;
      return ergebnis;
  }


  @Override
Der Tiefendurchlauf entspricht der [[Binärbaum#Beispiel:_preorder-Traversierung|Preorder-Traversierung eines Baumes]].
  public int compareTo(Object arg0) {
      DijkstraGraphNode dgn2 = (DijkstraGraphNode) arg0;
      if(this.distanz < dgn2.distanz){
        return -1;
      }
      if(this.distanz > dgn2.distanz){
        return 1;
      }
      return 0;
  }


  public List getWeg() {
====Beispiel:====
      return weg;
  }


}
''Es gibt für jeden Startknoten mehrere mögliche Tiefendurchläufe, denn man kann sich bei den Nachbarknoten frei entscheiden, welchen man zuerst nimmt. In diesem Beispiel werden die Nachbarknoten immer nach alphabetischer Ordnung genommen.''


</code>
'''Tiefendurchlauf für den Startknoten Frankfurt''':


== Anwendungsbeispiele zu Graphen ==
''Zu Anfang kann man immer direkt weitergehen:''
===Beispiel: Minimaler Spannbaum===
Diese Methode erstellt aus einem Graphen die kürzeste Methode alle Knoten des Graphen zu verbinden.


'''Hauptmethode'''
Frankfurt -> Kassel -> Dortmund -> Bremen -> Hamburg -> Berlin -> Hannover


Alle Kanten werden ausgelesen und ihrem Gewicht nach, bei der kuerzesten angefangen,durchgegangen.
''Von Hannover aus ist kein freier Knoten mehr erreichbar. Deswegen muss man jetzt in der Liste zurckgehen, bis man zu einem Knoten kommt, der noch einen freien Nachbarknoten hat. Das ist in diesem Fall '''Dortmund''' (der freie Nachbarknoten ist '''Koeln'''). Das heißt, Koeln wird als nächstes angehängt, und man würde von Köln aus weitersuchen (wenn es noch freie Knoten gäbe...)
Kann die momentan betrachtete Kante eingefuegt werden, ohne dass ein Kreis entsteht, wird sie eingefuegt.
<code>
  public Graph minimalerSpannbaum(Graph pGraph){
    System.out.println("minimalerSpannbaum()");
    //es wird ein neuer Graph erstellt, der das Ergebnis hinterher anzeigt
    Graph ergebnis = new Graph();
    //alle Knoten des uebergebenen Graphen werden in den spaeteren Ergebnisgraphen eingetragen
    List nnodes = pGraph.getNodes();
    nnodes.toFirst();
    while(nnodes.hasAccess()){
        GraphNode akt = (GraphNode)nnodes.getObject();
        ergebnis.addNode(akt);
        nnodes.next();
    }
    //alle Kanten werden ihrem Gewicht nach sortiert in einem Vector uebergeben
    Vector<Edge> edges = alleKantenSortiert(pGraph);
    //dieser Vector wird duchgegangen
    for(int i = 0; i < edges.size(); i++){
    //kann die aktuell im Vector betrachtete Kante eingefuegt werden, ohne dass ein Kreis entsteht,
    //wird sie eingefuegt 
    Edge akt = edges.elementAt(i);
        if(!erzeugtKreis(ergebnis, edges.elementAt(i))){
          ergebnis.addEdge(akt.getNodeA(), akt.getNodeB(), akt.getWeight());
          System.out.println("addEdge");
        }
    }
    return ergebnis;
  }
</code>


'''Hilfsmethoden'''
Ergebnis:


Fuer die Ueberpruefung ob ein Kreis erzeugt wird ist eine eigene Methode hilfreich.
'''Frankfurt -> Kassel -> Dortmund -> Bremen -> Hamburg -> Berlin -> Hannover -> Koeln'''
Hier wird von einem Knoten der einzufuegenden Kantre ausgehend ueberprueft, ob der andere Knoten erreichbar ist.
Wenn ja, dann wuerde durch einfuegen ein Kreis erzeugt, wenn nein nicht.


<code>
====Implementierung====
  private boolean erzeugtKreis(Graph pGraph, Edge pEdge) {
Der Tiefendurchlauf durch einen Graphen wird am einfachsten '''rekursiv''' programmiert.  
    //von dem einen Knoten aus erreichbare Nodes werden ausgelesen un in einer Liste gespeichert
    List erreichbare = erreichbareNodes(pGraph, pEdge.getNodeA());
    //und werden durchgegangen
    erreichbare.toFirst();
    while(erreichbare.hasAccess()){
        //wird der Knoten, von dem ausgegangen wurde gefunden wuerde ein Kreis erzeugt und true ausgegeben
        if(pEdge.getNodeB() == (GraphNode)erreichbare.getObject()){
          return true;
        }
        erreichbare.next();
    }
    //wird der KNoten nicht gefunden wird false zurueckgegeben, da kein Kreis erzeugt wuerde
    return false;
  }
</code>


'''Anmerkung:''' Es gibt natürlich noch weitere Möglichkeiten die Strategie zu realisieren. Eine sehr einfache kommt ohne die Methode ''erreichbareNodes(...)'' aus: Man markiert alle bereits verbundenen Knoten (im Bsp. eines Stromnetzes: alle bereits "angeschlossenen" Knoten), also beim Einfügen jeder Kante die zu ihr gehörenden Knoten, falls diese noch nicht markiert sind. Diese Taktik macht das Überprüfen, ob beim Einfügen ein Kreis entsteht, sehr einfach: Falls beide zur Kante gehörenden Knoten bereits markiert sind, entstünde ein Kreis und die Kante darf nicht eingefügt werden.
Voraussetzung: kein Knoten ist markiert!


===Beispiel: Erreichbare Knoten ===
<code>
Um alle erreichbaren Knoten zu bekommen erstellen wir erst eine Liste ergebnis,
  public List<Vertex> tiefendurchlauf(Graph pGraph, Vertex pNode){
die am Ende ausgegen wird.Danach wird der Liste ergebnis der startNode angehangen.
    List<Vertex> ergebnis = new List<>();
Dieses Node wird dann im Graphen makiert. Danach wird eine while-Schleife geöffnet,
die solange läuft wie die Liste ergebnis ein aktuelles Objekt besitzt.
    '''//Abbruchbedingung'''
Sie fügt die Nachbarn des aktuellen Nodes in eine Liste nachbarliste1 ein. Eine weitere
    if(pNode.isMarked()){
while-Schleife wird geöffnet, die solange läuft, wie die Liste nachbarliste1 ein
      return ergebnis;
aktuelles Objekt hat. Wenn das aktuelle Node nicht markiert ist wird das Objekt
    }
dem ergebnis angefügt (also der erreichbare Nachbar wird in das Ergebnis geschrieben)
und das Node wird makiert. So werden am Ende mit der Liste ergebnis
alle erreichbare Nachbarn ausgegeben.
    
    
<code>
    // pNode an die ergebnis-Liste anhaengen und markieren
public List erreichbareNodes(Graph pGraph,GraphNode pNode){
    ergebnis.append(pNode);
  pGraph.resetMarks();
    pNode.setMark(true);
  List ergebnis = new List();
  ergebnis.append(pNode);
     //Alle Nachbarn von pNode
  pNode.mark();
     List<Vertex> nachbarn = pGraph.getNeighbours(pNode);
  ergebnis.toFirst();
  while(ergebnis.hasAccess()){
     //Nachbarn mit for-Schleife durchlaufen
     GraphNode akt1=(GraphNode) ergebnis.getObject();
    '''for(nachbarn.toFirst(); nachbarn.hasAccess(); nachbarn.next()){'''
     List nachbarliste1 =pGraph.getNeighbours(akt1);
       Vertex aktuellerNachbar = nachbarn.getContent();
     nachbarliste1.toFirst();
       '''//Rekursiver Aufruf'''
    while(nachbarliste1.hasAccess()){
      List<Vertex> erg2 = <b><u>tiefendurchlauf(pGraph, aktuellerNachbar)</u></b>;
       GraphNode akt2=(GraphNode) nachbarliste1.getObject();
       ergebnis.concat(erg2);
       if(akt2.isMarked()==false){
        ergebnis.append(akt2);
        akt2.mark();
       }
    nachbarliste1.next();
     }
     }
     ergebnis.next();
     return ergebnis;
   }
   }
  return ergebnis;
  </code>
  }
 
</code>
=== Breitendurchlauf===
Der Breitendurchlauf ''(engl. breadth first search - BFS)'' ist eine Methode, um alle Knoten eines Graphen aufzuzählen.
 
====Erläuterung====
Mit dem Breitendurchlauf werden die Knoten in folgender Reihenfolge aufgezählt:
# zuerst der Startknoten,
# dann die Nachbarknoten des Startknotens, d.h. alle Knoten, die vom Startknoten aus über '''eine''' Kante erreichbar sind.
# dann die Knoten, die vom Startknoten aus mit '''zwei''' Kanten erreichbar sind.
# usw.
Knoten, die schon einmal aufgezählt wurden, werden natürlich nicht wieder aufgezählt.
 
Im Binärbaum ist der Breitendurchlauf genau [[Binärbaum_(Methoden)#Levelorder|Levelorder]].
 
 
====Beispiel====
''Beim Breitendurchlauf gibt es für jeden Startknoten mehrere Möglichkeiten, denn mann kann zwischen den Nachbarknoten wählen. Hier werden die Nachbarknoten immer in alphabetischer Reihenfolge betrachtet.''
 
'''Breitendurchlauf für den Startknoten Frankfurt'''
 
''Erst der Startknoten und seine Nachbarknoten:''


===Beispiel: Knoten mit ungerader Kantenzahl markieren ===
Frankfurt -> Kassel -> Koeln
TODO: Methode erklären
<code>
  TODO: Quelltext gut kommentiert einfügen
</code>


===Beispiel: Summe der Kantenkosten eines Knotens bestimmen===
''Jetzt wird von den Nachbarknoten der erste genommen und dessen Nachbarknoten werden betrachtet:''
<code>
  public int Kantenkosten(GraphWithViewer pGraph, GraphNode pNode)
  {
  int ergebnis = 0;  // Variable zum Speichern der Kosten wird deklariert
  List nachbarn = pGraph.getNeighbours(pNode);
  nachbarn.toFirst();
  while(nachbarn.hasAccess()) // Schleife durchläuft einmal alle Nachbarn
  {
  GraphNode akt = (GraphNode)nachbarn.getObject();
  int help = (int) pGraph.getEdgeWeight(pNode, akt);
  ergebnis=ergebnis+help; //Distanz wird hinzugefügt
  nachbarn.next();
  }
  return ergebnis; // Distanz wird ausgegeben
  }
</code>


===Beispiel: Knoten mit maximalen Kantenkosten bestimmen ===
Frankfurt -> <b>Kassel</b> -> Koeln -> Dortmund -> Hannover
TODO: Methode erklären
<code>
  TODO: Quelltext gut kommentiert einfügen
</code>


===Beispiel: Nächsten Nachbarn eines Knotens bestimmen ===
''Der nächste Knoten in der Liste, der noch freie Nachbarknoten hat, ist Dortmund:''


====Strategie: ====
Frankfurt -> Kassel -> Koeln -> <b>Dortmund</b> -> Hannover -> Bremen
Man entfernt alle markierten Nodes aus der NachbarListe und durchläuft diese Liste und guckt, welches die kürzeste Kante zu pNode hat.


<code>
''Schließlich die Nachbarknoten von Hannover:''


  public GraphNode gibNaechstenNachbarn(GraphWithViewer gwv, GraphNode pNode)
'''Frankfurt -> Kassel -> Koeln -> Dortmund -> <b>Hannover</b> -> Bremen -> Berlin -> Hamburg
  {
'''
      GraphNode ergebnis = null;
      double gewicht;
      List nachbarListe = gwv.getNeighbours(pNode);
      //  alle Nachbarn werden in die NachbarListe eingefügt
      nachbarListe.toFirst();
        while(nachbarListe.hasAccess())
        {
        GraphNode aktKn=(GraphNode)nachbarListe.getObject();
        if(aktKn.isMarked())
        //  wenn ein Objekt der NachbarListe markiert ist, dann wird es entfernt.
        {
        nachbarListe.remove();
        }
        else{
            nachbarListe.next();
        }
      }
      nachbarListe.toFirst();
      // das erste Objekt der nachbarListe wird als Ergebnis deklariert
      // und das Gewicht der Kante von diesem Objekt zu pNode als Gewicht
      ergebnis = (GraphNode)nachbarListe.getObject();
      gewicht = gwv.getEdgeWeight(pNode, ergebnis);
      nachbarListe.next();
      while(nachbarListe.hasAccess())
      {
        GraphNode aktuellerNode = (GraphNode)nachbarListe.getObject();
        double aktuellesGewicht = gwv.getEdgeWeight(pNode, aktuellerNode);
        if(aktuellesGewicht<gewicht)
        // wenn ein Gewicht einer Kante von einem Nachbarn zu pNode kleiner ist als das Gewicht,
        // dann wird dieses das neue Gewicht und der Nachbar wird das Ergebnis
        {
            ergebnis = aktuellerNode;
            gewicht = gwv.getEdgeWeight(pNode, aktuellerNode);
        }
        nachbarListe.next();
      }
      return ergebnis;
  }
</code>


===Beispiel: Überprüfen, ob im Graphen ein Dreierkreis existiert ===
''Beim Breitendurchlauf wird also zuerst die "nähere Umgebung" betrachtet.''
TODO: Methode erklären
<code>
  TODO: Quelltext gut kommentiert einfügen
</code>


===Beispiel: Alle Nachbarn zählen===
====Implementierung====
TODO: Quellcode erklären und im Quellcode kommentieren
Die Implementierung setzt darauf auf, dass der Graph '''linearisiert''' wird:
<code>
  public int zaehleNachbarn(GraphWithViewer pGraph, GraphNode pNode){
    int ergebnis = 0;
    List neighbours = graph.getNeighbours(pNode);
 
    neighbours.toFirst();
    while(neighbours.hasAccess()){
        ergebnis++;
        neighbours.next();
    }
    return ergebnis;
  }
</code>


===Beispiel: Wer hat die meisten Nachbarn===
Man braucht eine <code>knotenListe</code>; in diese werden nach und nach alle Knoten des Graphen gemäß der Breitendurchlauf-Reihenfolge eingefügt.
TODO: Quellcode erklären und im Quellcode kommentieren
# zuerst wird der Startknoten als ''besucht'' markiert und in <code>knotenListe</code> eingefügt.
# Dann wird <code>knotenListe</code> von Anfang bis Ende durchlaufen. Dabei passiert folgendes:
## Für jeden Nachbarknoten des aktuellen Knoten wird überprüft, ob er schon ''besucht'' wurde. Wenn nein, dann wird er als ''besucht'' markiert und in <code>knotenListe</code> eingefügt.
So wächst die <code>knotenListe</code> von einem Element (=dem Startknoten) beginnend immer weiter an, während sie durchlaufen wird. Die [[Schleife (Informatik)|Schleife]] kommt zum Ende, wenn alle Knoten eingefügt und als ''besucht'' gekennzeichnet sind.


<code>
<code>  
     public String meisteNachbarn(GraphWithViewer Ggraph){
     public List<Vertex> breitenDurchlauf(Graph pGraph, Vertex pStart){
         List hilfe=new List();
         List<Vertex> ergebnis = new List<Vertex>();
         hilfe.concat(Ggraph.getNodes());
         // alle Markierungen loeschen
         hilfe.toFirst();
        pGraph.setAllVertexMarks(false);
         GraphNode aktNode =null;
        pGraph.setAllEdgeMarks(false);
         int max=-1;
        // den Startknoten markieren und in ergebnis einfuegen
         while(hilfe.hasAccess())
        '''pStart.setMark(true);'''
        {
         '''ergebnis.append(pStart);'''
             GraphNode aktuell=(GraphNode)hilfe.getObject();
         // ergebnis durchlaufen:
             int akt=this.kantenzahl(Ggraph, aktuell);
        // ergebnis hat zu Anfang nur ein Element...
             if(akt>max)
         // ... waechst dann aber immer weiter!
            {
         '''for(ergebnis.toFirst(); ergebnis.hasAccess(); ergebnis.next()){'''
                 max=akt;
             Vertex aktuell = ergebnis.getContent();
                 aktNode=(GraphNode)hilfe.getObject();
             List<Vertex> nachbarn = pGraph.getNeighbours(aktuell);
            }
             // die Nachbarn mit einer Schleife durchlaufen
            else{
            '''for(nachbarn.toFirst(); nachbarn.hasAccess(); nachbarn.next()){'''
                hilfe.next();
                 Vertex aktuellerNachbar = nachbarn.getContent();
             }
                 // Wenn der aktuelleNachbar nicht markiert ist,
         }
                // wird er zu ergebnis hinzugefuegt
        System.out.println(max);
                '''if(!aktuellerNachbar.isMarked()){'''
         return aktNode.getName();
                    Edge kante = pGraph.getEdge(aktuell, aktuellerNachbar);
                    kante.setMark(true);
                    aktuellerNachbar.setMark(true);
                    '''ergebnis.append(aktuellerNachbar);'''
                }
             } // ende des Durchlaufs durch die Nachbarn.
         } // ende des Durchlaufs durch ergebnis
         return ergebnis;  
     }
     }
</code>
</code>
 
== Backtracking: kürzeste Wege auf Graphen ==
siehe [[Backtracking]]
 
== Dijkstra-Algorithmus: kürzeste Wege auf Graphen ==
siehe [[Dijkstra-Algorithmus]]
 
== Anwendungsbeispiele zu Graphen ==
 
===Minimaler Spannbaum===
siehe [[Minimaler Spannbaum]]

Aktuelle Version vom 23. Januar 2022, 16:58 Uhr


Graph für die Distanzen zwischen Städten

nur relevant für den Leistungskurs!

Ein Graph ist in der Graphentheorie eine Struktur, die eine Menge von Objekten zusammen mit den zwischen diesen Objekten bestehenden Verbindungen repräsentiert. Die mathematischen Abstraktionen der Objekte werden dabei Knoten des Graphen genannt. Die paarweisen Verbindungen zwischen Knoten heißen Kanten.

Ein Graph kann entweder als Graph, als Adjazenzliste oder als Adjazenzmatrix dargestellt werden.

Erklärvideos

Fachbegriffe

Graph, Knoten, markieren, Kante, Gewicht, Traversierung, Breitendurchlauf, Tiefendurchlauf

Schnittstellenbeschreibung

Graph Schnittstellenbeschreibung (ab Abi 2018)

Adjazenzmatrix

Knoten und Kanten eines Graphen können in Form einer Matrix dargestellt werden. Die Matrix ist dabei spiegelsymmetrisch.

Der oben dargestellte Graph hat folgende Adjazenzmatrix:

Berlin Bremen Dortmund Frankfurt Hamburg Hannover Kassel Koeln
Berlin 289 290
Bremen 234 119 122
Dortmund 234 210 160 93
Frankfurt 193 189
Hamburg 289 119 150
Hannover 290 122 210 150 167
Kassel 160 193 167
Koeln 93 189

TODO: Algorithmus zum Erzeugen eines Graphen aus einem 2D-Array (Adjazenzmatrix)

Adjazenzliste

Man kann die Informationen eines Graphen auch als Adjazenzliste darstellen. Das bietet sich z.B. an, wenn es in einem Graphen nur wenige Kanten gibt; dann ist die komplette Adjazenzmatrix Platzverschwendung.

In der Adjazenzliste werden links von oben nach unten alle Knoten aufgeführt, z.B. in alphabetischer Reihenfolge; die Reihenfolge ist aber frei wählbar.

Nach rechts werden alle Nachbarknoten des linken Knoten mit den zugehörigen Entfernungen eingetragen. Auch hier ist die Reihenfolge frei wählbar.

Der oben dargestelle Graph hat folgende Adjazenzliste:


Berlin → Hamburg (289) → Hannover (290)
   ↓
Bremen  → Dortmund (234) → Hamburg (119) → Hannover (122)
   ↓
Dortmund → Bremen (234) → Hannover (210) → Kassel (190) → Köln (93)
   ↓
Frankfurt → Kassel (193) → Köln (189)
   ↓
Hamburg → Berlin (289) → Bremen (119) → Hannover (150)
   ↓
Hannover → Berlin (290) → Bremen (122) → Dortmund (210) → Hamburg (150) → Kassel (167)
   ↓
Kassel → Dortmund (160) → Frankfurt (193) → Hannover (167)
   ↓
Koeln → Dortmund (93) → Frankfurt (189)

Traversierungen von Graphen

Wie bei Bäumen gibt es auch bei Graphen viele Anwendungen, in denen ein Graph knotenweise durchlaufen werden muss. Die Traversierungsverfahren ähneln denen bei Bäumen, berücksichtigen allerdings noch die speziellen Gegebenheiten von Graphen, nämlich:

  • Graphenknoten können mehr als zwei Nachbarn haben
  • Graphen können Querverbindungen und "Kreise" enthalten

Tiefendurchlauf

Erläuterung

Beim Tiefendurchlauf (engl. Depth First Search - DFS) durch einen Baum nimmt man ausgehend von einem Startknoten den ersten Nachbarknoten. Von diesem nimmt man wieder den ersten Nachbarknoten usw. Wenn man dann in eine Sackgasse gerät, geht man eine Stufe zurück und nimmt den nächsten Nachbarknoten. Natürlich werden Knoten, die man schon besucht hat, nicht noch einmal berücksichtigt.

Der Tiefendurchlauf entspricht der Preorder-Traversierung eines Baumes.

Beispiel:

Es gibt für jeden Startknoten mehrere mögliche Tiefendurchläufe, denn man kann sich bei den Nachbarknoten frei entscheiden, welchen man zuerst nimmt. In diesem Beispiel werden die Nachbarknoten immer nach alphabetischer Ordnung genommen.

Tiefendurchlauf für den Startknoten Frankfurt:

Zu Anfang kann man immer direkt weitergehen:

Frankfurt -> Kassel -> Dortmund -> Bremen -> Hamburg -> Berlin -> Hannover

Von Hannover aus ist kein freier Knoten mehr erreichbar. Deswegen muss man jetzt in der Liste zurckgehen, bis man zu einem Knoten kommt, der noch einen freien Nachbarknoten hat. Das ist in diesem Fall Dortmund (der freie Nachbarknoten ist Koeln). Das heißt, Koeln wird als nächstes angehängt, und man würde von Köln aus weitersuchen (wenn es noch freie Knoten gäbe...)

Ergebnis:

Frankfurt -> Kassel -> Dortmund -> Bremen -> Hamburg -> Berlin -> Hannover -> Koeln

Implementierung

Der Tiefendurchlauf durch einen Graphen wird am einfachsten rekursiv programmiert.

Voraussetzung: kein Knoten ist markiert!


 public List<Vertex> tiefendurchlauf(Graph pGraph, Vertex pNode){
   List<Vertex> ergebnis = new List<>();

   //Abbruchbedingung
   if(pNode.isMarked()){
      return ergebnis;
   }
 
   // pNode an die ergebnis-Liste anhaengen und markieren
   ergebnis.append(pNode);
   pNode.setMark(true);

   //Alle Nachbarn von pNode
   List<Vertex> nachbarn = pGraph.getNeighbours(pNode);

   //Nachbarn mit for-Schleife durchlaufen
   for(nachbarn.toFirst(); nachbarn.hasAccess(); nachbarn.next()){
     Vertex aktuellerNachbar = nachbarn.getContent();
     //Rekursiver Aufruf
     List<Vertex> erg2 = tiefendurchlauf(pGraph, aktuellerNachbar);
     ergebnis.concat(erg2);
   }
   return ergebnis;
 }

Breitendurchlauf

Der Breitendurchlauf (engl. breadth first search - BFS) ist eine Methode, um alle Knoten eines Graphen aufzuzählen.

Erläuterung

Mit dem Breitendurchlauf werden die Knoten in folgender Reihenfolge aufgezählt:

  1. zuerst der Startknoten,
  2. dann die Nachbarknoten des Startknotens, d.h. alle Knoten, die vom Startknoten aus über eine Kante erreichbar sind.
  3. dann die Knoten, die vom Startknoten aus mit zwei Kanten erreichbar sind.
  4. usw.

Knoten, die schon einmal aufgezählt wurden, werden natürlich nicht wieder aufgezählt.

Im Binärbaum ist der Breitendurchlauf genau Levelorder.


Beispiel

Beim Breitendurchlauf gibt es für jeden Startknoten mehrere Möglichkeiten, denn mann kann zwischen den Nachbarknoten wählen. Hier werden die Nachbarknoten immer in alphabetischer Reihenfolge betrachtet.

Breitendurchlauf für den Startknoten Frankfurt

Erst der Startknoten und seine Nachbarknoten:

Frankfurt -> Kassel -> Koeln

Jetzt wird von den Nachbarknoten der erste genommen und dessen Nachbarknoten werden betrachtet:

Frankfurt -> Kassel -> Koeln -> Dortmund -> Hannover

Der nächste Knoten in der Liste, der noch freie Nachbarknoten hat, ist Dortmund:

Frankfurt -> Kassel -> Koeln -> Dortmund -> Hannover -> Bremen

Schließlich die Nachbarknoten von Hannover:

Frankfurt -> Kassel -> Koeln -> Dortmund -> Hannover -> Bremen -> Berlin -> Hamburg

Beim Breitendurchlauf wird also zuerst die "nähere Umgebung" betrachtet.

Implementierung

Die Implementierung setzt darauf auf, dass der Graph linearisiert wird:

Man braucht eine knotenListe; in diese werden nach und nach alle Knoten des Graphen gemäß der Breitendurchlauf-Reihenfolge eingefügt.

  1. zuerst wird der Startknoten als besucht markiert und in knotenListe eingefügt.
  2. Dann wird knotenListe von Anfang bis Ende durchlaufen. Dabei passiert folgendes:
    1. Für jeden Nachbarknoten des aktuellen Knoten wird überprüft, ob er schon besucht wurde. Wenn nein, dann wird er als besucht markiert und in knotenListe eingefügt.

So wächst die knotenListe von einem Element (=dem Startknoten) beginnend immer weiter an, während sie durchlaufen wird. Die Schleife kommt zum Ende, wenn alle Knoten eingefügt und als besucht gekennzeichnet sind.

 
   public List<Vertex> breitenDurchlauf(Graph pGraph, Vertex pStart){
       List<Vertex> ergebnis = new List<Vertex>();
       // alle Markierungen loeschen
       pGraph.setAllVertexMarks(false);
       pGraph.setAllEdgeMarks(false);
       // den Startknoten markieren und in ergebnis einfuegen
       pStart.setMark(true);
       ergebnis.append(pStart);
       // ergebnis durchlaufen:
       // ergebnis hat zu Anfang nur ein Element...
       // ... waechst dann aber immer weiter!
       for(ergebnis.toFirst(); ergebnis.hasAccess(); ergebnis.next()){
           Vertex aktuell = ergebnis.getContent();
           List<Vertex> nachbarn = pGraph.getNeighbours(aktuell);
           // die Nachbarn mit einer Schleife durchlaufen
           for(nachbarn.toFirst(); nachbarn.hasAccess(); nachbarn.next()){
               Vertex aktuellerNachbar = nachbarn.getContent();
               // Wenn der aktuelleNachbar nicht markiert ist,
               // wird er zu ergebnis hinzugefuegt
               if(!aktuellerNachbar.isMarked()){
                   Edge kante = pGraph.getEdge(aktuell, aktuellerNachbar);
                   kante.setMark(true);
                   aktuellerNachbar.setMark(true);
                   ergebnis.append(aktuellerNachbar);
               }
           }  // ende des Durchlaufs durch die Nachbarn.
       } // ende des Durchlaufs durch ergebnis
       return ergebnis;   
   }

Backtracking: kürzeste Wege auf Graphen

siehe Backtracking

Dijkstra-Algorithmus: kürzeste Wege auf Graphen

siehe Dijkstra-Algorithmus

Anwendungsbeispiele zu Graphen

Minimaler Spannbaum

siehe Minimaler Spannbaum