/// <summary> /// Odtwarza graf na podstawie zapisu w pliku /// </summary> /// <param name="type">Wskazuje typ tworzonego grafu</param> /// <param name="path">Nazwa pliku</param> /// <returns>Odtworzony graf</returns> /// <exception cref="FormatException"></exception> /// <remarks>Metoda wykorzystuje konstruktor <see cref="Graph(bool, int)"/> i dlatego /// jest on wymagany w każdej klasie pochodenej od klasy <see cref="Graph"/>. /// </remarks> /// <seealso cref="Graph"/> /// <seealso cref="ASD.Graphs"/> internal static Graph Read(Type type, string path) { Graph graph = null; using (var xmlReader = XmlReader.Create(path)) { while (xmlReader.Read()) { if (xmlReader.NodeType != XmlNodeType.Element) { continue; } if (xmlReader.Name != "Graph") { if (xmlReader.Name != "Edge") { continue; } if (!int.TryParse(xmlReader.GetAttribute("from"), out var from)) { throw new FormatException("Invalid graph format in file " + path); } if (!int.TryParse(xmlReader.GetAttribute("to"), out var to)) { throw new FormatException("Invalid graph format in file " + path); } if (!double.TryParse(xmlReader.GetAttribute("weight"), out var weight)) { throw new FormatException("Invalid graph format in file " + path); } graph?.AddEdge(from, to, weight); } else { if (!bool.TryParse(xmlReader.GetAttribute("directed"), out var flag)) { throw new FormatException("Invalid graph format in file " + path); } if (!int.TryParse(xmlReader.GetAttribute("verticescount"), out var num)) { throw new FormatException("Invalid graph format in file " + path); } graph = Create(flag, num, type); } } } return(graph); }
/// <summary> /// Wyszukiwanie "wąskich gardeł" w sieci przesyłowej /// </summary> /// <param name="g">Graf przepustowości krawędzi</param> /// <param name="c">Graf kosztów rozbudowy sieci (kosztów zwiększenia przepustowości)</param> /// <param name="p">Tablica mocy produkcyjnych/zapotrzebowania w poszczególnych węzłach</param> /// <param name="flowValue">Maksymalna osiągalna produkcja (parametr wyjściowy)</param> /// <param name="cost">Koszt rozbudowy sieci, aby możliwe było osiągnięcie produkcji flowValue (parametr wyjściowy)</param> /// <param name="flow">Graf przepływu dla produkcji flowValue (parametr wyjściowy)</param> /// <param name="ext">Tablica rozbudowywanych krawędzi (parametr wyjściowy)</param> /// <returns> /// 0 - zapotrzebowanie można zaspokoić bez konieczności zwiększania przepustowości krawędzi<br/> /// 1 - zapotrzebowanie można zaspokoić, ale trzeba zwiększyć przepustowość (niektórych) krawędzi<br/> /// 2 - zapotrzebowania nie można zaspokoić (zbyt małe moce produkcyjne lub nieodpowiednia struktura sieci /// - można jedynie zwiększać przepustowości istniejących krawędzi, nie wolno dodawać nowych) /// </returns> /// <remarks> /// Każdy element tablicy p opisuje odpowiadający mu wierzchołek<br/> /// wartość dodatnia oznacza moce produkcyjne (wierzchołek jest źródłem)<br/> /// wartość ujemna oznacza zapotrzebowanie (wierzchołek jest ujściem), /// oczywiście "możliwości pochłaniające" ujścia to moduł wartości elementu<br/> /// "zwykłym" wierzchołkom odpowiada wartość 0 w tablicy p<br/> /// <br/> /// Jeśli funkcja zwraca 0, to<br/> /// parametr flowValue jest równy modułowi sumy zapotrzebowań<br/> /// parametr cost jest równy 0<br/> /// parametr ext jest pustą (zeroelementową) tablicą<br/> /// Jeśli funkcja zwraca 1, to<br/> /// parametr flowValue jest równy modułowi sumy zapotrzebowań<br/> /// parametr cost jest równy sumarycznemu kosztowi rozbudowy sieci (zwiększenia przepustowości krawędzi)<br/> /// parametr ext jest tablicą zawierającą informację o tym o ile należy zwiększyć przepustowości krawędzi<br/> /// Jeśli funkcja zwraca 2, to<br/> /// parametr flowValue jest równy maksymalnej możliwej do osiągnięcia produkcji /// (z uwzględnieniem zwiększenia przepustowości)<br/> /// parametr cost jest równy sumarycznemu kosztowi rozbudowy sieci (zwiększenia przepustowości krawędzi)<br/> /// parametr ext jest tablicą zawierającą informację o tym o ile należy zwiększyć przepustowości krawędzi<br/> /// Uwaga: parametr ext zawiera informacje jedynie o krawędziach, których przepustowości trzeba zwiększyć // (każdy element tablicy to opis jednej takiej krawędzi) /// </remarks> public static int BottleNeck(this Graph g, Graph c, int[] p, out int flowValue, out int cost, out Graph flow, out Edge[] ext) { int n = g.VerticesCount; Graph network = g.IsolatedVerticesGraph(true, 2 * n + 2); Graph costs = network.IsolatedVerticesGraph(); // 0, ..., n - 1 - regular vertices // n, ..., 2n - 1 - additional vertices (used for extending the network) int s = 2 * n; int t = 2 * n + 1; double totalDemand = 0; for (int v = 0; v < n; v++) { if (p[v] > 0) { // v is a source network.AddEdge(s, v, p[v]); costs.AddEdge(s, v, 0); } else if (p[v] < 0) { // v consumes the flow network.AddEdge(v, t, -p[v]); costs.AddEdge(v, t, 0); totalDemand += -p[v]; } foreach (Edge e in g.OutEdges(v)) { // x - y network.AddEdge(e); costs.AddEdge(v, e.To, 0); // x - x2 network.AddEdge(v, n + v, int.MaxValue); costs.AddEdge(v, n + v, c.GetEdgeWeight(v, e.To)); // x2 - y network.AddEdge(n + v, e.To, int.MaxValue); costs.AddEdge(n + v, e.To, 0); } } double networkFlowValue = network.MinCostFlow(costs, s, t, out double networkCost, out Graph networkFlow); flowValue = Convert.ToInt32(networkFlowValue); cost = Convert.ToInt32(networkCost); List <Edge> extensionEdges = new List <Edge>(); flow = g.IsolatedVerticesGraph(); for (int v = 0; v < n; v++) { foreach (Edge e in g.OutEdges(v)) { double totalFlow = networkFlow.GetEdgeWeight(v, e.To); double extendedFlow = networkFlow.GetEdgeWeight(n + v, e.To); if (!extendedFlow.IsNaN() && extendedFlow > 0) { totalFlow += extendedFlow; extensionEdges.Add(new Edge(v, e.To, extendedFlow)); } flow.AddEdge(v, e.To, totalFlow); } } ext = extensionEdges.ToArray(); if (extensionEdges.Count == 0) { return(0); } return(flowValue == totalDemand ? 1 : 2); }
/// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera algorytmem zachłannym "kruskalopodobnym" /// </summary> /// <param name="g">Badany graf</param> /// <param name="cycle">Znaleziony cykl (parametr wyjściowy)</param> /// <returns>Długość znalezionego cyklu (suma wag krawędzi)</returns> /// <remarks> /// Elementy (krawędzie) umieszczone są w tablicy <i>cycle</i> w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<br/> /// <br/> /// Jeśli algorytm "kruskalopodobny" nie znajdzie w badanym grafie cyklu Hamiltona /// (co oczywiście nie znaczy, że taki cykl nie istnieje) to metoda zwraca <b>null</b>, /// parametr wyjściowy <i>cycle</i> również ma wówczas wartość <b>null</b>.<br/> /// <br/> /// Metodę można stosować dla grafów skierowanych i nieskierowanych.<br/> /// <br/> /// Metodę można stosować dla dla grafów z dowolnymi (również ujemnymi) wagami krawędzi. /// </remarks> public static double TSP_Kruskal(this Graph g, out Edge[] cycle) { // ToDo - algorytm "kruskalopodobny" int n = g.VerticesCount; EdgesMinPriorityQueue edgesQueue = new EdgesMinPriorityQueue(); for (int v = 0; v < n; v++) { foreach (Edge e in g.OutEdges(v)) { // For undirected graphs only add edges once if (!g.Directed && e.From >= e.To) { continue; } edgesQueue.Put(e); } } UnionFind uf = new UnionFind(n); Graph minSpanningTree = g.IsolatedVerticesGraph(); while (!edgesQueue.Empty && minSpanningTree.EdgesCount < n - 1) { Edge e = edgesQueue.Get(); if (uf.Find(e.From) == uf.Find(e.To)) // Edge would preemptively create a cycle { continue; } if (g.Directed) { if (minSpanningTree.OutDegree(e.From) != 0 || minSpanningTree.InDegree(e.To) != 0) // Two out edges or two in edges for some vertex { continue; } } else { if (minSpanningTree.OutDegree(e.From) == 2 || minSpanningTree.OutDegree(e.To) == 2) // Edge would create a diversion on the path { continue; } } minSpanningTree.AddEdge(e); uf.Union(e.From, e.To); } if (minSpanningTree.EdgesCount < n - 1) { // Unable to construct a spanning path with n-1 edges cycle = null; return(double.NaN); } // Look for vertices at the beginning and end of the path int cycleBeginV = -1, cycleEndV = -1; for (int v = 0; v < n; v++) { if (!minSpanningTree.Directed) { if (minSpanningTree.OutDegree(v) == 1) { if (cycleBeginV == -1) { cycleBeginV = v; } else { cycleEndV = v; break; } } } else { if (minSpanningTree.OutDegree(v) == 0) { cycleBeginV = v; } if (minSpanningTree.InDegree(v) == 0) { cycleEndV = v; } if (cycleBeginV != -1 && cycleEndV != -1) { break; } } } if (cycleBeginV == -1 || cycleEndV == -1) { // This if is superfluous, but I'm leaving it just for clarity cycle = null; return(double.NaN); } // Closing the cycle minSpanningTree.AddEdge(new Edge(cycleBeginV, cycleEndV, g.GetEdgeWeight(cycleBeginV, cycleEndV))); cycle = new Edge[n]; int currentCycleV = 0; double cycleLength = 0; for (int i = 0; i < n; i++) { Edge?cycleEdge = minSpanningTree.OutEdges(currentCycleV).First(); if (!minSpanningTree.Directed && i > 0) { // Make sure the edge goes further into the cycle, not backwards (only for undirected graphs) foreach (Edge e in minSpanningTree.OutEdges(currentCycleV)) { if (e.To != cycle[i - 1].From) { cycleEdge = e; break; } } } cycle[i] = cycleEdge.Value; currentCycleV = cycleEdge.Value.To; cycleLength += cycleEdge.Value.Weight; } return(cycleLength); } // TSP_Kruskal
} // TSP_Kruskal /// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera tworząc cykl Hamiltona na podstawie drzewa rozpinającego /// </summary> /// <param name="g">Badany graf</param> /// <param name="cycle">Znaleziony cykl (parametr wyjściowy)</param> /// <returns>Długość znalezionego cyklu (suma wag krawędzi)</returns> /// <remarks> /// Elementy (krawędzie) umieszczone są w tablicy <i>cycle</i> w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<br/> /// <br/> /// Jeśli algorytm bazujący na drzewie rozpinającym nie znajdzie w badanym grafie cyklu Hamiltona /// (co oczywiście nie znaczy, że taki cykl nie istnieje) to metoda zwraca <b>null</b>, /// parametr wyjściowy <i>cycle</i> również ma wówczas wartość <b>null</b>.<br/> /// <br/> /// Metodę można stosować dla grafów nieskierowanych.<br/> /// Zastosowana do grafu skierowanego zgłasza wyjątek <see cref="System.ArgumentException"/>.<br/> /// <br/> /// Metodę można stosować dla dla grafów z dowolnymi (również ujemnymi) wagami krawędzi.<br/> /// <br/> /// Dla grafu nieskierowanego spełniajacego nierówność trójkąta metoda realizuje algorytm 2-aproksymacyjny. /// </remarks> public static double TSP_TreeBased(this Graph g, out Edge[] cycle) { if (g.Directed) { throw new System.ArgumentException("Graph is directed"); } int n = g.VerticesCount; EdgesMinPriorityQueue edgesQueue = new EdgesMinPriorityQueue(); for (int v = 0; v < n; v++) { foreach (Edge e in g.OutEdges(v)) { // Only add edges once if (e.From >= e.To) { continue; } edgesQueue.Put(e); } } Graph minSpanningTree = g.IsolatedVerticesGraph(); UnionFind uf = new UnionFind(n); while (!edgesQueue.Empty && minSpanningTree.EdgesCount < n - 1) { Edge e = edgesQueue.Get(); if (uf.Find(e.From) == uf.Find(e.To)) // Same component, would create a cycle { continue; } minSpanningTree.AddEdge(e); } if (minSpanningTree.EdgesCount < n - 1) { } int[] preorderVertices = new int[n]; int i = 0; minSpanningTree.GeneralSearchAll <EdgesQueue>(v => { preorderVertices[i++] = v; return(true); }, null, null, out int cc); cycle = new Edge[n]; double cycleLength = 0; for (i = 1; i < n; i++) { int v1 = preorderVertices[i - 1], v2 = preorderVertices[i]; Edge e = new Edge(v1, v2, g.GetEdgeWeight(v1, v2)); cycle[i - 1] = e; cycleLength += e.Weight; if (e.Weight.IsNaN()) { break; // Added an edge that doesn't exist } } if (cycleLength.IsNaN()) { cycle = null; return(double.NaN); } double closingEdgeWeight = g.GetEdgeWeight(preorderVertices[n - 1], preorderVertices[0]); cycle[n - 1] = new Edge(preorderVertices[n - 1], preorderVertices[0], closingEdgeWeight); cycleLength += closingEdgeWeight; return(cycleLength); } // TSP_TreeBased