package graph; import imp.*; import java.util.List; import java.util.ArrayList; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; /** * Dies ist das Herz vom "GraphTester" - der Graph selber, gepeichert als Adjazenzliste. * Die Klasse erlaubt durch geeignete Methoden: * - die Speicherung als Adjazenzmatrix, * - das Hinzufuegen und Loeschen von knoten und Kanten, * - das Markieren von Knoten und Kanten, * - eine Aussage darueber, ob Knoten oder Kanten enthalten sind und * - eine Ausgabe des Graphen in textueller Form sowie als csv-Datei. * * * @author Dirk Zechnall, Thomas Schaller * @version 31.01.2021 (v6.5) */ public class Graph { private ArrayList kList; // Liste aller Knoten private ArrayList kaList; // Liste aller Kanten private ArrayList> adList; //Adjazenzliste private boolean gerichtet; private boolean gewichtet; /** * Der Konstruktor erstellt einen neuen Graphen (genauer eine neue Adjazenzliste) * @param isGerichtet gibt an, ob es sich um einen gerichteten Graphen handelt * @param isGewichtet gibt an, ob die Kanten gewichtet sind. */ public Graph (boolean isGerichtet, boolean isGewichtet) { loescheGraph(); gerichtet = isGerichtet; gewichtet = isGewichtet; } /** * Der Konstruktor erstellt einen neuen ungerichteten, ungewichteten Graphen (genauer eine neue Adjazenzliste) */ public Graph() { loescheGraph(); } /** * Löscht alle Knoten und Kanten eines Graphen und stellt auf ungerichtet und ungewichtet zurück. */ public void loescheGraph() { gerichtet = false; gewichtet = false; kList = new ArrayList(); kaList = new ArrayList(); adList = new ArrayList>(); } /** * Ein Graph wird aus einer csv-Datei erstellt, die entweder * eine Matrix oder eine Adjazenzliste enthält, die den Graph beschreibt. * * @param csvParser Eine CSV-Datei als Table-Objekt * @return gibt zurück, ob das Laden erfolgreich war */ public boolean ladeGraph(Table csvParser) { boolean geschafft = false; try { int knotenAnzahl = 0; boolean hasInfotext = false; boolean hasStatus = false; geschafft = true; int start; int posx = 0; for(start = 0; start < csvParser.getRowCount(); start++) { TableRow row = csvParser.getRow(start); String m = row.getString(0); if(row.getString(0).equals("directed")) { setGerichtet(row.getInt(1) == 1); } if(row.getString(0).equals("weighted")) { setGewichtet(row.getInt(1) == 1); } if(row.getString(0).equals("matrix") || row.getString(0).equals("list")) { String n = row.getString(1); if(n.equals("infotext")) { hasInfotext = true; posx++; } break; } } // Knoten erstellen for(int i=start+1; i < csvParser.getRowCount(); i++) { TableRow row = csvParser.getRow(i); if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') { Knoten k = new Knoten(row.getInt(posx), row.getInt(posx+1)); if(hasInfotext) k.setInfotext(row.getString(0)); neuerKnoten(k); knotenAnzahl++; } } // Kanten erstellen int k=0; if(csvParser.getString(start,0).equals("matrix")){ for(int i = start+1; i < csvParser.getRowCount(); i++) { TableRow row = csvParser.getRow(i); if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') { //Ueberpruefung auf Kommentare # for(int j = posx+2; j < row.getColumnCount(); j+=1) { if(!(row.getString(j).equals("-"))) { neueKante(getKnoten(k), getKnoten(j - 2 -posx), row.getDouble(j)); } } k++; } } } else { for(int i = start+1; i < csvParser.getRowCount(); i++) { TableRow row = csvParser.getRow(i); if(row.getString(0).isEmpty() || row.getString(0).charAt(0) != '#') { // Ueberpruefung auf Kommentare # int z = row.getColumnCount(); z++; for(int j = posx+2; j < row.getColumnCount(); j+= 1) { if(isGewichtet()){ neueKante(getKnoten(k), getKnoten(row.getInt(j)), row.getDouble(j + 1)); j++; } else { neueKante(getKnoten(k), getKnoten(row.getInt(j)), 0); } } k++; } } } } catch(Exception e) { geschafft=false; } return geschafft; } /** * Legt fest, ob der Graph gewichtet oder ungewichtet ist. * * @param isGewichtet neuer Wert */ public void setGewichtet(boolean isGewichtet) { gewichtet = isGewichtet; } /** * Gibt zurueck, ob der Graph gewichtet oder ungewichtet ist * * @return gewichtet? (true/false) */ public boolean isGewichtet() { return gewichtet; } /** * Legt fest, ob der Graph gerichtet oder ungerichtet ist. * * @param isGerichtet neuer Wert */ public void setGerichtet(boolean isGerichtet) { gerichtet = isGerichtet; if(gerichtet) { for(Kante k: kaList) { adList.get(kList.indexOf(k.getZiel())).remove(k); } } } /** * Gibt zurueck, ob der Graph gerichtet oder ungerichtet ist. * * @return gerichtet? (true/false) */ public boolean isGerichtet() { return gerichtet; } /** Gibt die Nummer eines Knotens im Graphen zurück * @param k gesuchter Knoten * @return Nummer des Knotens (mit 0 beginnend) */ public int getNummer(Knoten k) { return kList.indexOf(k); } /** Gibt die Nummer einer Kante zurück * @param k gesuchte Kante * @return Nummer der Kante (mit 0 beginnend) */ public int getNummer(Kante k) { return kaList.indexOf(k); } /** * Die Methode getAdjazenzMatrix() gibt die Adjazenzmatrix zurueck. * * @return double[][] Die AdjazenzMatrix als zweidimensionales Array */ public double[][] getAdjazenzMatrix() { double[][] matrix = new double[getAnzahlKnoten()][getAnzahlKnoten()]; for (int i=0; i hat jedoch einen deutlich groeßeren Speicheraufwand! if(gewichtet) matrix[startKnotenNummer][zielKnotenNummer] = k.getGewicht(); else matrix[startKnotenNummer][zielKnotenNummer] = 0; } return matrix; } /** * Gibt eine Liste aller Kanten des Graphen zurück. * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste aller Kanten */ public List getAlleKanten(Predicate... filter) { List kanten = new ArrayList(kaList); if(filter.length>0) { kanten = kanten.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return kanten; } /** * Entfernt die Markierung bei allen Knoten des Graphen. */ public void entferneMarkierungBeiAllenKnoten() { for (Knoten k : kList) { k.setMarkiert(false); } } /** * Initialisiert alle Knoten des Graphen. */ public void initialisiereAlleKnoten() { for (Knoten k : kList) { k.init(); } } /** * Initialisiert alle Kanten des Graphen. */ public void initialisiereAlleKanten() { for (Kante k: kaList) { k.init(); } } /** * Ueberprueft, ob ein Knoten in der Knotenliste enthalten ist. * Sobald in der Knotenliste der Knoten k gefunden wird, wird true ausgegeben. * * @param k Der gesuchte Knoten * @return true= ist enthalten, false = ist nicht enthalten */ public boolean isKnotenEnthalten (Knoten k) { return kList.contains(k); } /** * Gibt die Anzahl der Knoten im Graph zurueck * * @return Anzahl der Knoten */ public int getAnzahlKnoten() { return kList.size(); } /** * Gibt eine Liste aller Knoten des Graphen zurueck. * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Die Knotenliste. Falls leer wird eine leere Liste zurueckgegeben */ public List getAlleKnoten(Predicate... filter) { List knoten = new ArrayList(); knoten.addAll(kList); if(filter.length>0) { knoten = knoten.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return knoten; } /** * Gibt die Liste aller Nachbarknoten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. * * @param k Der Knoten, zu dem die Adjazenzliste gesucht wird * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste der Nachbarknoten */ public List getNachbarknoten (Knoten k, Predicate... filter) { if(kList.contains(k)) { List nachbarn = new ArrayList(); for (Kante e : adList.get(kList.indexOf(k))) { nachbarn.add(e.getAnderesEnde(k)); } // end of for if(filter.length>0) { nachbarn = nachbarn.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return nachbarn; } else { return null; } } /** * Gibt eine Liste der ausgehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten * unterschieden. * * @param k Der Knoten, zu dem die Kanten gesucht werden * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste der ausgehenden Kanten. */ public List getAusgehendeKanten (Knoten k, Predicate... filter) { if(kList.contains(k)) { List kanten = new ArrayList(); kanten.addAll(adList.get(kList.indexOf(k))); if(filter.length>0) { kanten = kanten.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return kanten; } else { return null; } } /** * Gibt eine Liste der ausgehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten * unterschieden. * * @param knotennr Nummer des Knoten, zu dem die Kanten gesucht werden * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste der ausgehenden Kanten. */ public List getAusgehendeKanten (int knotennr, Predicate... filter) { if(knotennr < getAnzahlKnoten()) { List kanten = new ArrayList(); kanten.addAll(adList.get(knotennr)); if(filter.length>0) { kanten = kanten.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return kanten; } else { return null; } } /** * Gibt eine Liste der eingehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten * unterschieden. * * @param knotennr Die Nummer des Knotens, zu dem die Kanten gesucht werden * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste der eingehenden Kanten. */ public List getEingehendeKanten (int knotennr, Predicate... filter) { if(knotennr >= getAnzahlKnoten()) { return null; } return getEingehendeKanten(this.getKnoten(knotennr),filter); } /** * Gibt eine Liste der eingehenden Kanten eines Knotens k zurueck, falls k in der Knotenliste vorhanden ist. * Bei ungerichteten Graphen wird nicht zwischen eingehenden und ausgehenden Kanten * unterschieden. * * @param k Knoten, zu dem die Kanten gesucht werden * @param filter optionaler Filter, der auf die Liste angewendet wird. Er muss einen boolean-Wert zurückgeben: z.B. @literal{k->k.isMarkiert() && k.isBesucht()} * @return Liste der eingehenden Kanten. */ public List getEingehendeKanten (Knoten k, Predicate... filter) { if(!kList.contains(k)) { return null; } List kanten = new ArrayList(); if(this.isGerichtet()) { for (Kante e: kaList) { if(e.getZiel()== k) { kanten.add(e); } } // end of for } else { kanten = getAusgehendeKanten(k); } // end of if-else if(filter.length>0) { kanten = kanten.stream() // convert list to stream .filter(filter[0]) .collect(Collectors.toCollection(ArrayList::new)); } return kanten; } /** * Liefert einen Knoten des Graphen * @param knotennr Nummer der Knoten (beginnend mit 0) * @return liefert den Knoten mit dieser Nummer */ public Knoten getKnoten(int knotennr) { if(knotennr < getAnzahlKnoten()) { return kList.get(knotennr); } else { return null; } } /** * Fügt einen Knoten dem Graph hinzu. * @param k Der Knoten, der hinzugefuegt werden soll */ public void neuerKnoten (Knoten k) { if (!isKnotenEnthalten(k)){ kList.add(k); adList.add(new ArrayList()); k.setGraph(this); } } /** * Entfernt einen Knoten aus dem Graphen * * @param knotennr Nummer des Knotens, der geloescht werden soll */ public void entferneKnoten (int knotennr) { entferneKnoten(getKnoten(knotennr)); } /** * Entfernt einen Knoten aus dem Graphen * * @param k Knoten, der geloescht werden soll * @return gibt zurück, ob der Knoten erfolgreich entfernt wurde. */ public boolean entferneKnoten (Knoten k) { if (!kList.contains(k)) return false; int index = kList.indexOf(k); for (List a : adList) { int index2 = 0; while(index2 < a.size()){ Kante ka = a.get(index2); if(ka.getStart()==k || ka.getZiel()==k){ if(kaList.contains(ka)) kaList.remove(ka); a.remove(index2); } else { index2++; } } } adList.remove(index); kList.remove(k); k.setGraph(null); return true; } /** * Ueberprueft, ob eine Kante im Graphen enthalten ist. * * @param e Die zu suchende Kante * @return Kante enthalten (true) oder nicht (false) */ public boolean isKanteEnthalten (Kante e) { return kaList.contains(e); } /** * Ueberprueft, ob eine Kante im Graphen enthalten ist. * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden * * @param startNr Nummer des Startknotens * @param zielNr Nummer des Zielknotens * @return boolean Kante enthalten (true) oder nicht (false) */ public boolean isKanteEnthalten (int startNr, int zielNr) { return getKante(startNr, zielNr)!=null; } /** * Ueberprueft, ob eine Kante im Graphen enthalten ist. * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden * * @param start Startknoten * @param ziel Zielknoten * @return boolean Kante enthalten (true) oder nicht (false) */ public boolean isKanteEnthalten (Knoten start, Knoten ziel) { return getKante(start, ziel)!=null; } /** * Gibt eine gesuchte Kante aus dem Graphen zurueck. * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden * * @param start Der StartKnoten * @param ziel Der StartKnoten * @return Die gesuchte Kante */ public Kante getKante (Knoten start, Knoten ziel) { for(Kante k: adList.get(kList.indexOf(start))) { if(k.getStart()==start && k.getZiel() == ziel) { return k; } if(!gerichtet && k.getStart()==ziel && k.getZiel() == start) { return k; } } return null; } /** * Gibt eine gesuchte Kante aus dem Graphen zurueck. * In ungerichteten Graphen wird nicht zwischen Start- und Zielknoten unterschieden * * @param startnr Der Nummer des StartKnoten * @param zielnr Die Nummer des Zielknoten * @return Die gesuchte Kante */ public Kante getKante (int startnr, int zielnr) { return getKante(getKnoten(startnr), getKnoten(zielnr)); } /** * Fuegt eine Kante dem Graphen hinzu. * Dabei wird ueberprueft, ob die Kante schon im Graphen enthalten ist. * * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" erstellt. * * @param e Die Kante, die hinzugefuegt werden soll */ public void neueKante(Kante e) { adList.get(kList.indexOf(e.getStart())).add(e); if(!gerichtet) adList.get(kList.indexOf(e.getZiel())).add(e); kaList.add(e); e.setGraph(this); } /** * Fuegt eine Kante dem Graphen hinzu. * Dabei wird ueberprueft, ob die Kante schon im Graphen enthalten ist. * * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" erstellt. * * @param start Der StartKnoten der Kante, die hinzugefuegt werden soll * @param ziel Der ZielKnoten der Kante, die hinzugefuegt werden soll * @param gewicht Das Gewicht der Kante, die hinzugefuegt werden soll */ public void neueKante(Knoten start, Knoten ziel, double gewicht) { if(!kList.contains(start) || !kList.contains(ziel) || getKante(start,ziel) != null) return; neueKante(new Kante(start,ziel, gewicht)); } /** * Entfernt eine Kante aus dem Graphen. * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt. * * @param e Die zu entfernende Kante */ public void entferneKante (Kante e) { entferneKante(e.getStart(), e.getZiel()); } /** * Entfernt eine Kante aus dem Graphen. * Dabei wird ueberprueft, ob die Kante ueberhaupt im Graphen enthalten ist. * * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt. * * @param start StartKnotens * @param ziel ZielKnotens */ public void entferneKante (Knoten start, Knoten ziel) { Kante e1 = getKante(start, ziel); Kante e2 = getKante(ziel, start); for(List a: adList) { a.remove(e1); if(!gerichtet) { a.remove(e2); } } kaList.remove(e1); e1.setGraph(null); if(!gerichtet) { kaList.remove(e2); e2.setGraph(null); } } /** * Entfernt eine Kante aus dem Graphen. * Ist der Graph ungerichtet, werden sowohl "Hin-" und "RueckKante" entfernt. * * @param startnr Nummer des StartKnotens * @param zielnr Nummer des ZielKnotens */ public void entferneKante (int startnr, int zielnr) { entferneKante(getKnoten(startnr), getKnoten(zielnr)); } /** * Ueberprueft, ob die Adjazenzliste leer ist, d.h. keine Knoten im Graphen enthalten sind. * * @return true, wenn die Liste leer ist, sonst false */ public boolean isLeer() { return adList.isEmpty(); } /** * Loescht den gesamten Graphen */ public void loescheAlles() { for(Kante k : kaList) k.setGraph(null); for(Knoten k : kList) k.setGraph(null); adList.clear(); kList.clear(); kaList.clear(); } /** * Die Methode erstellt eine CSV-Ausgabe des Graphen entweder als Adjazenzliste oder als Adjazenzmatrix. * * @param asMatrix true, falls die CSV-Ausgabe eine AdjazenzMatrix sein soll, sonst false * @return CSV-Ausgabe */ public String toCSVString(boolean asMatrix) { String s=""; //if(adList.isLeer()) return s; s += "# gewichtet 1, ungewichtet 0\n"; s += "weighted,"+(isGewichtet() ? "1" : "0")+"\n"; s += "# gerichtet 1, ungerichtet 0\n"; s += "directed,"+(isGerichtet() ? "1" : "0")+"\n"; // Kontrollieren, ob irgendwo ein Infotext vorhanden ist. boolean hasInfotext = false; for(Knoten k: kList) { if(!k.getInfotext().equals("")) hasInfotext = true; } if(asMatrix){ s += "# Der Graph liegt hier in Form einer Adjazenzmatrix vor. \n"; s += "# Jede Zeile steht fuer einen Knoten, durch Kommas getrennt stehen die Kantengewicht zu jedem anderen Knoten.\n"; s += "matrix"; if(hasInfotext) s+=",infotext"; double[][] matrix = getAdjazenzMatrix(); // hier muss ueberprueft werden, ob matrixinhalt ist NaN --> '-' speichern for (int i=0; i a = adList.get(knr); if(hasInfotext) s+=k.getInfotext()+","; s+=(k.getX())+","+(k.getY()); if (a.size()>0) s+=","; for (Kante ka : a) { s+=""+(getNummer(ka.getAnderesEnde(k))); if (isGewichtet()) s+=","+ka.getGewicht(); if (a.get(a.size()-1)!=ka) s+=","; } } } return s; } /** * Textuelle Repraesentation des Graphen. * * @return Der Graph als Stringrepraesentation */ @Override public String toString() { String s=""; s+="Adjazenzliste:\n"; if(adList.isEmpty()) return "Liste ist leer!\n"; for(int i=0; i< kList.size(); i++) { s+="K"+i+kList.get(i).toString()+": "; List a =adList.get(i); for (Kante k : a) { s+=k.toString()+"K"+(kList.indexOf(k.getAnderesEnde(kList.get(i))))+" | "; } s=s.substring(0,s.length()-3)+"\n"; } return s; } /** * Konsolenausgabe der textuellen Repraesentation des Graphen. */ public void ausgabe() { System.out.println(toString()); } /** * Info über einen Knoten zurückgeben * @param k Knoten, des Info ermittelt werden soll * @param zeigeWert Soll der Wert des Knoten in der Info enthalten sein * @return Infotext oder Nummer des Knoten und ggf. sein Wert */ public String getKnoteninfo(Knoten k, boolean zeigeWert) { String info = ""; if (!k.getInfotext().equals("")) { info += k.getInfotext(); } else { info += "Knoten Nr. "+getNummer(k); } if( zeigeWert) { info += " (Wert "+k.getDoubleWert()+")"; } return info; } /** Speichert den Zustand des Graphen als String-List * @return List mit Zustand */ public List getStatus() { List status = new ArrayList(); for(Knoten k : getAlleKnoten()) { status.add(k.getStatus()); } for(Kante k : getAlleKanten()) { status.add(k.getStatus()); } return status; } /** Stellt den Zustand des Graphen aus String-List wieder her * @param status List mit Zustand */ public void setStatus(List status) { int i=0; for(Knoten k : getAlleKnoten()) { k.setStatus(status.get(i++)); } for(Kante k : getAlleKanten()) { k.setStatus(status.get(i++)); } } }