/// <summary> /// Gibt das Ergebnis in der vorgegebenen Form in einem StreamWriter aus /// </summary> /// <remarks> /// Historie: 23.05.2012 (awi) Erstellt /// </remarks> /// <param name="sw">Ein StreamWriter zur Ausgabe</param> /// <param name="e">Ein Ergebnis Objekt</param> private void ErgebnisAusgabe(StreamWriter sw, Ergebnis e) { if (sw != null) { sw.WriteLine(e.sKommentar); sw.WriteLine("Startzelle: {0}, {1}, Zielzelle: {2}, {3}", e.si + 1, e.sk + 1, e.zi + 1, e.zk + 1); sw.WriteLine("Abschätzung der Kostenobergrenze: {0} KE", e.iKostenAbsch); sw.WriteLine("Minimalkosten: {0} KE", e.iKostenMinimal); sw.Write("Weg: "); for (int iKnoten = 0; iKnoten < e.kuerzesterWeg.GetLength(0); iKnoten++) { if (iKnoten == 0) { sw.Write("S; "); } else if (iKnoten == e.kuerzesterWeg.GetLength(0) - 1) { sw.Write("Z\n"); } else { sw.Write("{0},", e.ZeileZuKnoten(e.kuerzesterWeg[iKnoten]) + 1); sw.Write("{0}; ", e.SpalteZuKnoten(e.kuerzesterWeg[iKnoten]) + 1); } } } }
/// <summary> /// Gibt eine Adjazenzmatrix des Ergebnis Objekts auf dem Bildschirm zu Testzwecken aus /// Hinweise: Unendlich wird als INF (infinity) dargestellt /// Historie: 22.05.2012 (awi) Erstellt /// </summary> /// <param name="e">Ein Ergebnis Objekt</param> public void AdjazenzMatrixAusgabe(Ergebnis e) { String s; // über alle Zeilen for (int i = 0; i < e.AdjazenzMatrix.GetLength(0); i++) { s = ""; // String für die Zeilenweise Ausgabe // über alle Spalten for (int k = 0; k < e.AdjazenzMatrix.GetLength(1); k++) { if (e.AdjazenzMatrix[i, k] == Konstante.UNENDLICH) { // Unendlich wird als INF (infinity) ausgegeben s = s + "INF , "; } else { // Wert ausgeben s = s + String.Format("{0,3}", e.AdjazenzMatrix[i, k]) + " , "; } } Console.WriteLine(s); } }
/// <summary> /// Gibt das Ergebnis in der vorgegebenen Form in einer Datei aus /// </summary> /// <remarks> /// Historie: 22.05.2012 (awi) Erstellt /// 23.05.2012 (awi) Ausgabe in gesonderte Methode gekapselt /// </remarks> /// <param name="sDateiname">Ein String mit einem Dateinamen</param> /// <param name="e">Ein Ergebnis Objekt</param> public void ErgebnisDateiAusgabe(String sDateiname, Ergebnis e) { // Datei als SW öffnen StreamWriter sw; try { sw = new StreamWriter(sDateiname, false, Encoding.UTF8); } catch { throw new FieldAccessException("Datei (" + sDateiname + ") konnte nicht zum Schreiben geöffnet werden."); } try { ErgebnisAusgabe(sw, e); } catch { Console.WriteLine("Ergebnis konnte nicht in Datei (" + sDateiname + ") geschrieben werden."); } sw.Close(); }
/// <summary> /// Gibt das Ergebnis in der vorgegebenen Form auf dem Bildschirm aus /// </summary> /// <remarks> /// Historie: 22.05.2012 (awi) Erstellt /// 23.05.2012 (awi) Ausgabe in gesonderte Methode gekapselt /// </remarks> /// <param name="e">Ein Ergebnis Objekt</param> public void ErgebnisAusgabe(Ergebnis e) { // Console als SW übergeben StreamWriter sw = new StreamWriter(Console.OpenStandardOutput(), Console.OutputEncoding); sw.AutoFlush = true; ErgebnisAusgabe(sw, e); sw.Close(); }
/// <summary> /// Errechnet eine Kostenabschätzung /// </summary> /// <remarks> /// Hinweise: - errechnet eine triviale Lösung des Problems. /// Läuft im eingelesenen Feld zunächst vom Start auf /// die Höhe des Ziels und von dort zum Ziel. /// Die summierten Kosten dieses Weges ist die /// Kostenabschätzung /// - Feld im Ergebnis Objekt muss bereits angelegt sein /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="e">Ein Ergebnis Objekt</param> static void KostenAbsch(Ergebnis e) { int iKosten = 0; int iIterator; if (e.Feld == null) { throw new Exception("Feld im Ergebnis Objekt nicht angelegt"); } //vertikale Richtung if (e.si < e.zi) { // Ziel liegt "unterhalb" von Start iIterator = 1; } else { // Ziel liegt "oberhalb" von Start iIterator = -1; } // Weg gehen und Kosten summieren for (int i = e.si; i != e.zi; i = i + iIterator) { iKosten += e.Feld[i, e.sk]; } //horizontale Richtung if (e.sk < e.zk) { // Ziel liegt "rechts" von Start iIterator = 1; } else { // Ziel liegt "links" von Start iIterator = -1; } // Weg gehen und Kosten summieren for (int k = e.sk; k != e.zk; k = k + iIterator) { iKosten += e.Feld[e.zi, k]; } //Kosten speichern e.iKostenAbsch = iKosten; }
/// <summary> /// Gibt ein Feld des Ergebnis Objekts auf dem Bildschirm zu Testzwecken aus /// </summary> /// <remarks> /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="e">Ein Ergebnis Objekt</param> public void FeldAusgabe(Ergebnis e) { String s; // über alle Zeilen for (int i = 0; i < e.Feld.GetLength(0); i++) { s = ""; // String zur zeilenweisen Ausgabe // über alle Spalten for (int k = 0; k < e.Feld.GetLength(1); k++) { s = s + e.Feld[i, k] + " , "; } Console.WriteLine(s); } }
/// <summary> /// wandelt ein Feld in eine Adjazenzmatrix um /// </summary> /// <remarks> /// Hinweise: Die Knoten werden Zeilenweise eingelesen, nummeriert. Also erst Zeile 0, /// dann Zeile 1, usw... /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="e">Ein Ergebnis Objekt</param> public void FeldUmwandeln(Ergebnis e) { //AdjazenzMatrix erzeugen, ein Feld der Größe m*n x m*n e.AdjazenzMatrix = new int[e.Feld.GetLength(0) * e.Feld.GetLength(1), e.Feld.GetLength(0) * e.Feld.GetLength(1)]; int iZeile, iSpalte, kZeile, kSpalte; int iBreiteFeld = e.Feld.GetLength(1); // über alle Zeilen der Adjazenmatrix for (int i = 0; i < e.AdjazenzMatrix.GetLength(1); i++) { // Spalte und Zeile im Feld des Zeilen - Knotens berechnen iZeile = (int)(i / iBreiteFeld); iSpalte = i % iBreiteFeld; // über alle Spalten der Adjazenzmatrix for (int k = 0; k < e.AdjazenzMatrix.GetLength(0); k++) { // Spalte und Zeile im Feld des Spalten - Knotens berechnen kZeile = (int)(k / iBreiteFeld); kSpalte = k % iBreiteFeld; if (i == k) { // Weg auf sich selbst ist 0 e.AdjazenzMatrix[i, k] = 0; } else { // alle anderen Verbindungen, prüfen ob Knoten benachbart if ((iZeile == kZeile && Math.Abs(iSpalte - kSpalte) == 1) || (iSpalte == kSpalte && Math.Abs(iZeile - kZeile) == 1)) { e.AdjazenzMatrix[i, k] = e.Feld[kZeile, kSpalte]; } else { // nicht benachbart, unendlich zuweisen e.AdjazenzMatrix[i, k] = Konstante.UNENDLICH; } } } } }
/// <summary> /// Die Main-Methode, steuert den Programmablauf /// </summary> /// <remarks> /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="args">Die Programmparameter. Es kann ein Dateiname zum Einlesen angegeben werden</param> static void Main(string[] args) { // Zeitmessung zur Performance-Verbesserung Stopwatch watch = new Stopwatch(); watch.Start(); // Initialisierung Ergebnis erg = new Ergebnis(); Ausgabe ausg = new Ausgabe(); Eingabe eing = new Eingabe(); String sDateinameEingabe = ""; String sDateinameAusgabe = ""; if (args.Length > 0) { // Parameter zum einlesen der Datei sDateinameEingabe = args[0]; } else { // Standard Input verwenden sDateinameEingabe = @"Testfaelle/gebiet.txt"; } // Ausgabe-Dateinamen festlegen (NameEingabedatei_Ergebnis.txt) sDateinameAusgabe = Path.GetDirectoryName(sDateinameEingabe) + @"\" + Path.GetFileNameWithoutExtension(sDateinameEingabe) + "_Ergebnis.txt"; try { // Datei einlesen eing.DateiEinlesen(sDateinameEingabe, erg); } catch (Exception e) { ausg.FehlerAusgabe("Fehler beim Einlesen der Datei: " + e.Message, sDateinameAusgabe); return; } // Feld in Adjazenz Matrix umwandeln eing.FeldUmwandeln(erg); // Kosten abschätzen KostenAbsch(erg); // Minimale Kosten + Weg bestimmen KostenMinimal(erg); watch.Stop(); ausg.StringAusgabe("Ausführungszeit: " + watch.Elapsed); // Ergebnis ausgeben ausg.ErgebnisAusgabe(erg); // Ergebnis in Datei ausgeben ausg.ErgebnisDateiAusgabe(sDateinameAusgabe, erg); // Programm beenden // Console.ReadKey(); }
/// <summary> /// Errechnet einen optimalen Weg mit Kostenminimum /// </summary> /// <remarks> /// Hinweise: - errechnet eine optimale Lösung des Problems inklusive dem Weg. /// Es wird ein angepasster Dijkstra-Algorithmus verwendet /// - die Adjazenzmatrix des Ergebnis Objekts muss bereits erstellt /// sein /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="e">Ein Ergebnis Objekt</param> static void KostenMinimal(Ergebnis e) { if (e.AdjazenzMatrix == null) { throw new Exception("Adjazenzmatrix im Ergebnis Objekt muss angelegt sein"); } int iMinimum; int iKnoten; //Initialisierung int iAnzahlKnoten = e.AdjazenzMatrix.GetLength(0); int[] kosten = new int[iAnzahlKnoten]; // Array für Kosten int[] distanz = new int[iAnzahlKnoten]; // Array für Entfernung int[] vorgaenger = new int[iAnzahlKnoten]; // Array für Vorgängerknoten int iKostenNeu, iDistanzNeu; bool[] markiert = new bool[iAnzahlKnoten]; for (int i = 0; i < iAnzahlKnoten; ++i) { // kein Knoten markiert markiert[i] = false; // Kosten auf unendlich setzen kosten[i] = Konstante.UNENDLICH; // Distanz auf unendlich setzen distanz[i] = Konstante.UNENDLICH; // keine Vorgänger vorgaenger[i] = Konstante.KEIN_VORGAENGER; } // Startknoten mit Kosten/Distanz 0 kosten[e.iStartknoten] = 0; distanz[e.iStartknoten] = 0; bool bWeitereKnoten = true; while (bWeitereKnoten) { // minimale Kosten initialisieren iMinimum = Int32.MaxValue; // zugehöriger (minimaler) Knoten iKnoten = 0; // Minimale Distanz ermitteln for (int j = 0; j < iAnzahlKnoten; j++) { // Knoten schon markiert -> überspringen (Vermeidung von Zyklen!) if (markiert[j]) { continue; } // Distanz kleiner als Minimum -> neues Minimum gefunden if (kosten[j] != Konstante.UNENDLICH && kosten[j] < iMinimum) { iMinimum = kosten[j]; iKnoten = j; } } // Distanz aktualisieren, wenn Zielknoten über den gefundenen // Minimumknoten billiger erreichbar for (int j = 0; j < iAnzahlKnoten; j++) { // wenn nicht markiert und nicht Verbindung gleicher Knoten und Verbindung vorhanden if (!markiert[j] && iKnoten != j && e.AdjazenzMatrix[iKnoten, j] != Konstante.UNENDLICH) { // Neuberechnung von Distanz und Kosten iKostenNeu = kosten[iKnoten] + e.AdjazenzMatrix[iKnoten, j]; iDistanzNeu = distanz[iKnoten] + 1; // besteht Verbesserung? Unendlich ist immer zu verbessern, // ansonsten vergleichen, ob neuer Wert besser als alter Wert // oder, Kosten gleich, kürzeren Weg wählen if (kosten[j] == Konstante.UNENDLICH || kosten[j] > iKostenNeu || (kosten[j] == iKostenNeu && distanz[j] > iDistanzNeu)) { // Verbesserung gefunden kosten[j] = iKostenNeu; distanz[j] = iDistanzNeu; vorgaenger[j] = iKnoten; } } } // gerade bestimmten Minimumknoten markieren markiert[iKnoten] = true; // sind noch Knoten nicht markiert -> weiter, sonst Ende bWeitereKnoten = false; for (int j = 0; j < iAnzahlKnoten && !bWeitereKnoten; j++) { bWeitereKnoten = !markiert[j]; } } // kuerzester Weg speichern // Länge des kürzesten Weges e.kuerzesterWeg = new int[distanz[e.iZielknoten] + 1]; // Vorgänger verfolgen bis zum Ziel ("Rückwärtssuche") // Rückwärtssuche startet beim Zielknoten iKnoten = e.iZielknoten; // Ergebnis Array von hinten befüllen int kuerzesterWegIterator = distanz[e.iZielknoten]; while (vorgaenger[iKnoten] != Konstante.KEIN_VORGAENGER) { // Knoten speichern e.kuerzesterWeg[kuerzesterWegIterator] = iKnoten; // Vorgänger festlegen iKnoten = vorgaenger[iKnoten]; kuerzesterWegIterator--; } // Startknoten speichern e.kuerzesterWeg[0] = e.iStartknoten; // Minimalkosten speichern e.iKostenMinimal = kosten[e.iZielknoten]; }
/// <summary> /// Liest eine Datei ein ein und speichert in Ergebnis Objekt /// </summary> /// <remarks> /// Hinweise: Löst verschiedene Ausnahmen aus, wenn Datei nicht geladen werden kann oder Datei /// fehlerhaft /// S, Z werden als 0 im Feld markiert /// Historie: 22.05.2012 (awi) Erstellt /// </remarks> /// <param name="sDateiname">Ein String mit einem Dateinamen</param> /// <param name="e">Ein Ergebnis Objekt</param> public void DateiEinlesen(String sDateiname, Ergebnis e) { bool bStartgefunden = false; bool bZielgefunden = false; int iSpalten = 0; int iZeile = 0; String sTmp, sZelle, sTmpAusn; String[] sTmpArr; int iTmp; StreamReader sr; int[,] tmpFeld = new int[Konstante.MAX_ZEILE, Konstante.MAX_SPALTE]; if (File.Exists(sDateiname)) { // Einlesen mit ISO-8859-1 (Westeuropäisch), wird wegen Umlauten in Kommentar verwendet sr = new StreamReader(File.Open(sDateiname, FileMode.Open), Encoding.GetEncoding(28591)); } else { throw new FileNotFoundException("Die angegebene Datei wurde nicht gefunden"); } if (sr.Peek() == -1) { throw new FileLoadException("Die angegebene Datei ist leer"); } // erste Zeile lesen, erwartet Kommentar sTmp = sr.ReadLine(); if (sTmp.StartsWith(";")) { // Kommentar in Ergebnis Objekt speichern e.sKommentar = sTmp.Substring(1).Trim(); } else { throw new FileLoadException("Die angegebene Datei erhält eine fehlerhafte 1. Zeile"); } // weitere Zeilen durchlaufen while (sr.Peek() >= 0) { if (iZeile >= Konstante.MAX_ZEILE) { sTmpAusn = String.Format("Das Feld darf nicht mehr als {0} Zeilen haben.", Konstante.MAX_ZEILE); throw new FileLoadException(sTmpAusn); } sTmp = sr.ReadLine(); if (sTmp.StartsWith(";")) { // Kommentar gefunden continue; } // Zeile aufgrund von ',' trennen sTmpArr = sTmp.Split(','); if (iZeile == 0) { // erste Zeile --> Spaltenzahl festlegen iSpalten = sTmpArr.GetLength(0); if (iSpalten > Konstante.MAX_SPALTE) { sTmpAusn = String.Format("Es darf nicht mehr als {0} Spalten geben.", Konstante.MAX_SPALTE); throw new FileLoadException(sTmpAusn); } } else { // prüfen ob einheitliche Spaltenlänge if (sTmpArr.GetLength(0) != iSpalten) { throw new FileLoadException("Unterschiedliche Spaltenanzahl in Datei gefunden."); } } // über alle Einträge im getrennten Zeilenstring for (int k = 0; k < sTmpArr.GetLength(0); k++) { // Leerzeichen vorne und hinten abschneiden / Groß- / Kleinschreibung nicht beachten sZelle = sTmpArr[k].Trim().ToUpper(); if (sZelle.Equals("S")) { // Startzelle gefunden if (bStartgefunden) { // Startzelle wurde bereits gefunden throw new FileLoadException("Es darf nur eine Startzelle definiert sein."); } // Startzelle wird in Feld als 0 gespeichert iTmp = 0; bStartgefunden = true; //Startknoten festlegen e.si = iZeile; e.sk = k; e.iStartknoten = iZeile * iSpalten + k; } else if (sZelle.Equals("Z")) { // Zielzelle gefunden if (bZielgefunden) { // Ziel bereits gefunden throw new FileLoadException("Es darf nur eine Zielzelle definiert sein."); } // Zielzelle wird in Feld als 0 gespeichert iTmp = 0; bZielgefunden = true; //Zielknoten festlegen e.zi = iZeile; e.zk = k; e.iZielknoten = iZeile * iSpalten + k; } else { // wenn nicht S oder Z muss ein Zahlenwert vorliegen: try { iTmp = Int32.Parse(sZelle); } catch { sTmpAusn = "Es sind nur ganzzahlige Zahlenwerte " + "zwiwschen 1 und 9 und S,Z in den Zellen erlaubt."; throw new ArithmeticException(sTmp); } // liegt Zahlenwert im definierten Bereich? if (iTmp < 1 || iTmp > 9) { sTmpAusn = "Es sind nur Zahlenwerte" + "zwischen 1 und 9 erlaubt."; throw new FileLoadException(sTmpAusn); } } // gelesenen Wert in temporären Feld speichern tmpFeld[iZeile, k] = iTmp; } iZeile++; // nächste Zeile } sr.Close(); // sind Start / Ziel gefunden? if (!bStartgefunden) { throw new FileLoadException("In der Datei muss eine Startzelle angegeben sein."); } if (!bZielgefunden) { throw new FileLoadException("In der Datei muss eine Zielzelle angegeben sein."); } //tmpFeld in Ergebnis - Feld übertragen e.Feld = new int[iZeile, iSpalten]; for (int i = 0; i < iZeile; i++) { for (int k = 0; k < iSpalten; k++) { e.Feld[i, k] = tmpFeld[i, k]; } } }