/// <summary>Dopełnienie grafu</summary> /// <param name="g">Graf wejściowy</param> /// <returns>Graf będący dopełnieniem grafu wejściowego</returns> /// <remarks> /// 0.5 pkt. /// Dopełnienie grafu to graf o tym samym zbiorze wierzchołków i zbiorze krawędzi równym VxV-E-"pętle" /// Graf wejściowy pozostaje niezmieniony. /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga 1 : w przypadku stwierdzenia ze graf wejściowy jest grafem ważonym zgłosić wyjątek ArgumentException /// Uwaga 2 : W grafach nieskierowanych nie probować dodawawać po 2 razy tej samej krawędzi /// </remarks> public static Graph Complement(this Graph g) { Graph ret = g.IsolatedVerticesGraph(); HashSet <Edge> elist = new HashSet <Edge>(); for (int i = 0; i < g.VerticesCount; i++) { for (int j = 0; j < g.VerticesCount; j++) { if (i == j) { continue; } if (g.GetEdgeWeight(i, j) == null) { Edge e = new Edge(i, j, 1); if (!elist.Contains(new Edge(j, i, 1)) && !elist.Contains(e)) { ret.AddEdge(i, j); elist.Add(e); } } else if (g.GetEdgeWeight(i, j) > 1) { throw new ArgumentException(); } } } return(ret); // zmienic ! }
public void graphToFile(Graph g) { var edges = new List <Edge>(); for (int i = 0; i < g.VerticesCount; i++) { for (int j = 0; j < i; j++) { if (!double.IsNaN(g.GetEdgeWeight(i, j))) { edges.Add(new Edge(i, j, g.GetEdgeWeight(i, j))); } } } using (StreamWriter sr = new StreamWriter(path)) { sr.WriteLine(g.VerticesCount); foreach (var edge in edges) { sr.WriteLine($"{edge.From} {edge.To} {edge.Weight}"); } } }
public static void Clique(int k, Graph g) // k - iteracja, g - graf { int n = g.VerticesCount; if (k == n) { return; } for (int i = 0; i < n; i++) { if (!klika.Contains(i)) { bool ok = true; foreach (var j in klika) { if (g.GetEdgeWeight(i, j) == null || g.GetEdgeWeight(j, i) == null) { ok = false; break; } } if (ok && (i > klika[klika.Count - 1])) { klika.Add(i); if (klika.Count > maxKlika.Count) { maxKlika = new List <int>(klika); } Clique(k + 1, g); klika.Remove(i); } } } }
/// <summary> /// Bada izomorfizm grafów metodą pełnego przeglądu (rekurencyjnie) /// </summary> /// <param name="vh">Aktualnie rozważany wierzchołek</param> /// <param name="map">Mapowanie wierzchołków grafu h na wierzchołki grafu g</param> /// <returns>Informacja czy znaleziono mapowanie definiujące izomotfizm</returns> internal bool FindMapping(int vh, int[] map) { // Wskazówki // 1) w sposób systematyczny sprawdzać wszystkie potencjalne mapowania // 2) unikać wielokrotnego sprawdzania tego samego mapowania // 3) zastosować algorytm z powrotami (backtracking) // 4) do badania krawędzi pomiędzy wierzchołkami i oraz j użyć metody GetEdgeWeight(i,j) if (vh == g.VerticesCount) { bool ok = true; for (int i = 0; i < g.VerticesCount; i++) { foreach (Edge e in g.OutEdges(i)) { if (!h.GetEdgeWeight(map[e.From], map[e.To]).HasValue) { ok = false; } } } return(ok); } for (int i = 0; i < g.VerticesCount; i++) { if (used[i] == false) { bool ok = true; foreach (Edge e in g.OutEdges(i)) { if (used[e.To] == true) { if (h.GetEdgeWeight(i, map[e.To]).HasValue == false) { ok = false; break; } } } if (ok) { used[i] = true; map[vh] = i; if (FindMapping(vh++, map)) { return(true); } used[i] = false; } } } return(false); }
/// <summary> /// Bada czy zadane grafy są jednakowe /// </summary> /// <param name="g">Pierwszy badany graf</param> /// <param name="h">Drugi badany graf</param> /// <returns>Informacja czy zadane grafy są jednakowe</returns> /// <remarks>Badana jest struktura grafu, sposób reprezentacji nie ma znaczenia.</remarks> /// <seealso cref="GraphHelperExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool IsEqual(this Graph g, Graph h) { if (g.VerticesCount != h.VerticesCount || g.EdgesCount != h.EdgesCount) { return(false); } if (g.Directed != h.Directed) { return(false); } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutDegree(i) != h.OutDegree(i) || g.InDegree(i) != h.InDegree(i)) { return(false); } } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutEdges(i).Any(edge => h.GetEdgeWeight(i, edge.To) != edge.Weight)) { return(false); } } return(true); }
private static void CloseCycle(Graph g, ref Graph tree) { if (tree == null) { return; } int first = -1, last = -1; for (int i = 0; i < tree.VerticesCount; i++) { if (tree.InDegree(i) == 0) { first = i; } if (tree.OutDegree(i) == 0) { last = i; } } if (first == -1 || last == -1) { tree = null; return; } int?weight = g.GetEdgeWeight(last, first); if (weight.HasValue) { tree.AddEdge(last, first, weight.Value); } else { tree = null; } }
/// <summary> /// Bada czy zadane grafy są jednakowe /// </summary> /// <param name="g">Pierwszy badany graf</param> /// <param name="h">Drugi badany graf</param> /// <returns>Informacja czy zadane grafy są jednakowe</returns> /// <remarks> /// Badana jest struktura grafu, sposób reprezentacji nie ma znaczenia.<para/> /// Metoda wykonuje obliczenia równolegle w wielu wątkach. /// </remarks> /// <seealso cref="GraphHelperExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool IsEqualParallel(this Graph g, Graph h) { if (g.VerticesCount != h.VerticesCount || g.EdgesCount != h.EdgesCount) { return(false); } if (g.Directed != h.Directed) { return(false); } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutDegree(i) != h.OutDegree(i) || g.InDegree(i) != h.InDegree(i)) { return(false); } } var result = Parallel.For(0, g.VerticesCount, (i, state) => { foreach (var edge in g.OutEdges(i)) { if (h.GetEdgeWeight(i, edge.To) != edge.Weight) { state.Stop(); } } }); return(result.IsCompleted); }
public static EdgesMinPriorityQueue getGraphEdgesMin(Graph graph) { EdgesMinPriorityQueue edges = new EdgesMinPriorityQueue(); int verticesCount = graph.VerticesCount; for (int i = 0; i < verticesCount; i++) { for (int j = 0; j < i; j++) { if (!double.IsNaN(graph.GetEdgeWeight(i, j))) { edges.Put(new Edge(i, j, graph.GetEdgeWeight(i, j))); } } } return(edges); }
/// <summary> /// Znajduje rozwiązanie dokładne problemu komiwojażera metodą pełnego przeglądu (backtracking) /// </summary> /// <param name="g">Badany graf</param> /// <returns> /// Krotka (weight, cycle) składająca się z długości (sumy wag krawędzi) /// znalezionego cyklu i tablicy krawędzi tworzących ten cykl) /// </returns> /// <exception cref="ArgumentException">Gdy uruchomiona dla grafu zawierającego krawędź o wadze ujemnej</exception> /// <remarks> /// Metoda przeznaczona jest dla grafów z nieujemnymi wagami krawędzi.<para/> /// Uruchomiona dla grafu zawierającego krawędź o wadze ujemnej zgłasza wyjątek ArgumentException. /// (Warunek ten sprawdzany jest w sposób przybliżony, /// jedynie przy próbie dodania krawędzi o ujemnej wadze do konstruowanego cyklu).<para/> /// Elementy (krawędzie) umieszczone są w tablicy cycle /// w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<para/> /// Jeśli w badanym grafie nie istnieje cykl Hamiltona metoda zwraca krotkę (NaN,null).<para/> /// Metodę można stosować dla grafów skierowanych i nieskierowanych. /// </remarks> /// <seealso cref="BacktrackingTSPGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Edge[] cycle) BacktrackingTSP(this Graph g) { if (g.VerticesCount <= (g.Directed ? 1 : 2)) { return(double.NaN, null); } Edge[] bestCycle = null; var bestWeight = double.PositiveInfinity; var tempCycle = new Edge[g.VerticesCount]; var visited = new bool[g.VerticesCount]; void Rec(int currVertex, int i, double currWeight) { if (currWeight >= bestWeight) { return; } if (i == g.VerticesCount - 1) { var edgeWeight = g.GetEdgeWeight(currVertex, 0); if (!(currWeight + edgeWeight < bestWeight)) { return; } if (edgeWeight < 0.0) { throw new ArgumentException("Negative weights are not allowed"); } bestWeight = currWeight + edgeWeight; tempCycle[i] = new Edge(currVertex, 0, edgeWeight); bestCycle = (Edge[])tempCycle.Clone(); return; } visited[currVertex] = true; foreach (var edge in g.OutEdges(currVertex)) { if (visited[edge.To]) { continue; } if (edge.Weight < 0.0) { throw new ArgumentException("Negative weights are not allowed"); } tempCycle[i] = edge; Rec(edge.To, i + 1, currWeight + edge.Weight); } visited[currVertex] = false; } Rec(0, 0, 0.0); return(double.IsPositiveInfinity(bestWeight) ? (double.NaN, null) : (bestWeight, bestCycle)); }
/// <summary> /// Bada czy zadane mapowanie wierzchołków definiuje izomorfizm grafów /// </summary> /// <param name="g">Pierwszy badany graf</param> /// <param name="h">Drugi badany graf</param> /// <param name="map">Zadane mapowanie wierzchołków</param> /// <returns></returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Mapowanie wierzchołków zdefiniowane jest w ten sposób, /// że wierzchołkowi v w grafie g odpowiada wierzchołek map[v] w grafie h.<para/> /// Badana jest struktura grafu, sposób reprezentacji nie ma znaczenia.<para/> /// Metoda wykonuje obliczenia równolegle w wielu wątkach. /// </remarks> /// <seealso cref="IsomorphismGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool IsIsomorphicParallel(this Graph g, Graph h, int[] map) { if (g.VerticesCount != h.VerticesCount) { return(false); } if (g.EdgesCount != h.EdgesCount) { return(false); } if (g.Directed != h.Directed) { return(false); } if (map == null) { throw new ArgumentException("Invalid mapping"); } if (map.Length != g.VerticesCount) { throw new ArgumentException("Invalid mapping"); } var used = new bool[g.VerticesCount]; for (var i = 0; i < g.VerticesCount; i++) { if (map[i] < 0 || map[i] >= g.VerticesCount || used[map[i]]) { throw new ArgumentException("Invalid mapping"); } used[map[i]] = true; } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutDegree(i) != h.OutDegree(map[i]) || g.InDegree(i) != h.InDegree(map[i])) { return(false); } } var result = Parallel.For(0, g.VerticesCount, (i, state) => { foreach (var edge in g.OutEdges(i)) { if (edge.Weight != h.GetEdgeWeight(map[edge.From], map[edge.To])) { state.Stop(); } } }); return(result.IsCompleted); }
/// <summary> /// Funkcja sprawdza, czy drugi argument jest poprawnym cyklem Hamiltona dla grafu w pierwszym argumencie. /// Oczywiście chodzi o cykl Hamiltona utworzony z możliwością przeskoków. /// </summary> public static bool isCorrectHamiltonianCycle(Graph g, List <int> cycle) { // Cykl Hamiltona musi być tak długi, jak ilość wierzchołków w grafie if (cycle.Count() != g.VerticesCount) { return(false); } // W cyklu Hamiltona wierzchołki nie mogą się powtarzać bool isUnique = cycle.Distinct().Count() == cycle.Count(); if (!isUnique) { return(false); } var onesGraph = new AdjacencyMatrixGraph(false, g.VerticesCount); for (int i = 0; i < g.VerticesCount; i++) { for (int j = 0; j < i; j++) { if (!double.IsNaN(g.GetEdgeWeight(i, j))) { onesGraph.AddEdge(i, j); } } } List <PathsInfo[]> pathsFromVertices = new List <PathsInfo[]>(); for (int i = 0; i < onesGraph.VerticesCount; i++) { PathsInfo[] path; onesGraph.DijkstraShortestPaths(i, out path); pathsFromVertices.Add(path); } bool good = true; for (int i = 0; i < cycle.Count - 1; i++) { if (pathsFromVertices[cycle[i]][cycle[i + 1]].Dist > 3) { good = false; Console.WriteLine($"{cycle[i]}, {cycle[i + 1]}, cost: {pathsFromVertices[cycle[i]][cycle[i + 1]].Dist}"); } } return(good); }
} // TSP_TreeBased public static int?buildCycle(Graph g, out Edge[] cycle) { cycle = null; int[] verticesOrder = new int[g.VerticesCount]; int orderCounter = 0; Graph tree; g.Kruskal(out tree); Predicate <int> preVisit = delegate(int i) { verticesOrder[orderCounter++] = i; return(true); }; tree.DFSearchFrom(0, preVisit, null); cycle = new Edge[g.VerticesCount]; int sum = 0; int weight; for (int i = 0; i < g.VerticesCount - 1; i++) { if (!g.GetEdgeWeight(verticesOrder[i], verticesOrder[i + 1]).HasValue) { cycle = null; return(null); } weight = g.GetEdgeWeight(verticesOrder[i], verticesOrder[i + 1]).Value; sum += weight; cycle[i] = new Edge(verticesOrder[i], verticesOrder[i + 1], weight); } weight = g.GetEdgeWeight(verticesOrder[tree.VerticesCount - 1], verticesOrder[0]).Value; sum += weight; cycle[tree.VerticesCount - 1] = new Edge(verticesOrder[tree.VerticesCount - 1], verticesOrder[0], weight); return(sum); }
/// <summary>Domknięcie grafu</summary> /// <param name="g">Graf wejściowy</param> /// <returns>Graf będący domknięciem grafu wejściowego</returns> /// <remarks> /// 1.5 pkt. /// Domknięcie grafu to graf, w którym krawędzią połączone są wierzchołki, /// pomiędzy którymi istnieje ścieżka w wyjściowym grafie (pętle wykluczamy). /// Graf wejściowy pozostaje niezmieniony. /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga 1 : w przypadku stwierdzenia ze graf wejściowy jest grafem ważonym zgłosić wyjątek ArgumentException /// </remarks> public static Graph Closure(this Graph g) { Graph ret = g.IsolatedVerticesGraph(); PathsInfo[] tab = new PathsInfo[g.VerticesCount]; HashSet <Edge> elist = new HashSet <Edge>(); // Dla każdego wierzchołka i znajdź ścieżki do pozostałych wierzchołków: for (int i = 0; i < g.VerticesCount; i++) { if (g.DijkstraShortestPaths(i, out tab)) { for (int j = 0; j < g.VerticesCount; j++) { // Pomiń pętle: if (i == j) { continue; } if (g.GetEdgeWeight(i, j) > 1) { throw new ArgumentException(); } Edge e = new Edge(i, j, 1); if (tab[j].Dist != null && elist.Contains(new Edge(j, i, 1)) == false && elist.Contains(e) == false) { //Console.WriteLine("Adding edge {0} - {1}", i, j); elist.Add(e); ret.AddEdge(e); } } } } // n = |V| /* * for i=1 to n * do for j = 1 to n * do if i = j lub (i,j) e E(g) * then t(i,j) = 1 * else t(i,j) = 0 * for k = 1 to n * do for i = 1 to n * do for j = 1 to n * t(i,j) = t(i,j) lub (t(i,k) i t(k,j)) */ return(ret); // zmienic ! }
/// <summary> /// Bada czy zadane mapowanie wierzchołków definiuje izomorfizm grafów /// </summary> /// <param name="g">Pierwszy badany graf</param> /// <param name="h">Drugi badany graf</param> /// <param name="map">Zadane mapowanie wierzchołków</param> /// <returns></returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Mapowanie wierzchołków zdefiniowane jest w ten sposób, /// że wierzchołkowi v w grafie g odpowiada wierzchołek map[v] w grafie h.<para/> /// Badana jest struktura grafu, sposób reprezentacji nie ma znaczenia. /// </remarks> /// <seealso cref="IsomorphismGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool IsIsomorphic(this Graph g, Graph h, int[] map) { if (g.VerticesCount != h.VerticesCount || g.EdgesCount != h.EdgesCount) { return(false); } if (g.Directed != h.Directed) { return(false); } if (map == null) { throw new ArgumentException("Invalid mapping"); } if (map.Length != g.VerticesCount) { throw new ArgumentException("Invalid mapping"); } var used = new bool[g.VerticesCount]; for (var i = 0; i < g.VerticesCount; i++) { if (map[i] < 0 || map[i] >= g.VerticesCount || used[map[i]]) { throw new ArgumentException("Invalid mapping"); } used[map[i]] = true; } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutDegree(i) != h.OutDegree(map[i]) || g.InDegree(i) != h.InDegree(map[i])) { return(false); } } for (var i = 0; i < g.VerticesCount; i++) { if (g.OutEdges(i).Any(edge => edge.Weight != h.GetEdgeWeight(map[edge.From], map[edge.To]))) { return(false); } } return(true); }
/// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera naiwnym algorytmem zachłannym /// </summary> /// <param name="g">Badany graf</param> /// <returns>Krotka (weight, cycle) składająca się z długości (sumy wag krawędzi) znalezionego cyklu i tablicy krawędzi tworzących ten cykl)</returns> /// <remarks> /// Elementy (krawędzie) umieszczone są w tablicy cycle w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<para/> /// Jeśli naiwny algorytm zachłanny nie znajdzie w badanym grafie cyklu Hamiltona (co oczywiście nie znaczy, że taki cykl nie istnieje) to metoda zwraca krotkę (NaN,null).<para/> /// Metodę można stosować dla grafów skierowanych i nieskierowanych.<para/> /// Metodę można stosować dla dla grafów z dowolnymi (również ujemnymi) wagami krawędzi. /// </remarks> /// <seealso cref="AproxTSPGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Edge[] cycle) SimpleGreedyTSP(this Graph g) { if (g.VerticesCount <= (g.Directed ? 1 : 2)) { return(double.NaN, null); } var edge = new Edge(-1, -1, 0.0); var visited = new bool[g.VerticesCount]; var cycle = new Edge[g.VerticesCount]; var weight = 0.0; var vertex = 0; for (var i = 0; i < g.VerticesCount - 1;) { visited[vertex] = true; edge = new Edge(vertex, -1, double.PositiveInfinity); foreach (var e in g.OutEdges(vertex)) { if (!visited[e.To] && edge.Weight > e.Weight) { edge = e; } } if (edge.To == -1) { return(double.NaN, null); } cycle[i++] = edge; weight += edge.Weight; vertex = edge.To; } vertex = edge.To; edge = new Edge(vertex, 0, g.GetEdgeWeight(vertex, 0)); if (edge.Weight.IsNaN()) { return(double.NaN, null); } cycle[g.VerticesCount - 1] = edge; weight += edge.Weight; return(weight, cycle); }
// Helper method so as not to duplicate code in the CliqueNumberRecursive method private static void CheckNewCliqueVertex(Graph g, int v, int cliqueSize, ref bool[] isVertexInClique, ref bool[] biggestClique, ref int biggestCliqueSize) { // If vertex has a lower degree than there are already vertices in the clique // it definitely cannot be added to the clique if (g.OutDegree(v) < cliqueSize || g.InDegree(v) < cliqueSize) { return; } // Check if there are edges between this vertex (v) and all the vertices already in the clique bool validVertex = true; for (int cliqueVertex = 0; cliqueVertex < v; cliqueVertex++) { if (isVertexInClique[cliqueVertex] && (g.GetEdgeWeight(v, cliqueVertex).IsNaN() || g.GetEdgeWeight(cliqueVertex, v).IsNaN())) { validVertex = false; break; } } if (!validVertex) { return; } isVertexInClique[v] = true; bool[] newClique = isVertexInClique.Clone() as bool[]; int newCliqueSize = CliqueNumberRecursive(g, ref newClique, cliqueSize + 1, v + 1); if (newCliqueSize > biggestCliqueSize) { biggestClique = newClique; biggestCliqueSize = newCliqueSize; } isVertexInClique[v] = false; }
/// <summary> /// Bada izomorfizm grafów metodą pełnego przeglądu (rekurencyjnie) /// </summary> /// <param name="currentV">Aktualnie rozważany wierzchołek</param> /// <param name="map">Mapowanie wierzchołków grafu h na wierzchołki grafu g</param> /// <returns>Informacja czy znaleziono mapowanie definiujące izomotfizm</returns> internal bool FindMapping(int currentV, int[] map, bool[] isVertexInMapping) { // Wskazówki // 1) w sposób systematyczny sprawdzać wszystkie potencjalne mapowania // 2) unikać wielokrotnego sprawdzania tego samego mapowania // 3) zastosować algorytm z powrotami (backtracking) // 4) do badania krawędzi pomiędzy wierzchołkami i oraz j użyć metody GetEdgeWeight(i,j) // We assume that vertices 0 - (currentV - 1) are already mapped int n = g.VerticesCount; if (currentV >= n) { return(true); } // I assume that map[i] = j means, that vertex i in graph G is isomorphic with vertex j in graph H // We check every possible vertex from graph H that is not yet used // and try to match it with currentV (from graph G) for (int vH = 0; vH < n; vH++) { if (isVertexInMapping[vH]) { continue; } if (g.OutDegree(currentV) != h.OutDegree(vH) || g.InDegree(currentV) != h.InDegree(vH)) { continue; } // Check every neighbor of currentV that is already in mapping, if it matches neighbors of vH map[currentV] = vH; bool validMatch = true; foreach (Edge eG in g.OutEdges(currentV)) { // Edge leads to a neighbor that is not yet in the mapping (because we already analyzed vertices 0 - (currentVertex - 1)) if (eG.To > currentV) { continue; } // Edge weights should be equal in both graphs double edgeWeightH = h.GetEdgeWeight(vH, map[eG.To]); if (eG.Weight != edgeWeightH) { validMatch = false; break; } // The same goes for reverse edges double reverseEdgeWeightG = g.GetEdgeWeight(eG.To, currentV); double reverseEdgeWeightH = h.GetEdgeWeight(map[eG.To], vH); if ((!reverseEdgeWeightG.IsNaN() || !reverseEdgeWeightH.IsNaN()) && reverseEdgeWeightG != reverseEdgeWeightH) { validMatch = false; break; } } if (!validMatch) { continue; } // vH in H is isomorphic with currentV in G isVertexInMapping[vH] = true; bool isomorphismFound = FindMapping(currentV + 1, map, isVertexInMapping); if (isomorphismFound) { return(true); } isVertexInMapping[vH] = false; // Isomorphism with this matching not found, look for other possibilities of vertices isomorphic with currentV } // No isomorphism found return(false); }
/// <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
/// <summary> /// Bada izomorfizm grafów metodą pełnego przeglądu (backtracking) /// </summary> /// <param name="g">Pierwszy badany graf</param> /// <param name="h">Drugi badany graf</param> /// <returns>Znalezione mapowanie wierzchołków</returns> /// <remarks> /// Jeśli grafy są izomorficzne metoda zwraca mapowanie wierzchołków grafu g na wierzchołki grafu h, /// w przeciwnym przypadku metoda zwraca null<para/> /// Odpowiedniość wierzchołków zdefiniowana jest w ten sposób, /// że wierzchołkowi v w grafie g odpowiada wierzchołek map[v] w grafie h.<para/> /// Badana jest struktura grafu, sposób reprezentacji nie ma znaczenia. /// </remarks> /// <seealso cref="IsomorphismGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static int[] Isomorphism(this Graph g, Graph h) { if (g.VerticesCount != h.VerticesCount || g.EdgesCount != h.EdgesCount || g.Directed != h.Directed) { return(null); } var vertCount = g.VerticesCount; var mapping = new int[vertCount]; var mapped = new bool[vertCount]; var int_2 = new int[vertCount]; var int_3 = new int[vertCount]; for (var i = 0; i < vertCount; i++) { int_3[i] = -1; } var order = 0; var visited = new bool[vertCount]; bool PreVisitVertex(int i) { visited[i] = true; int_2[order++] = i; return(true); } bool VisitEdge(Edge edge) { if (!visited[edge.To] && int_3[edge.To] == -1) { int_3[edge.To] = edge.From; } return(true); } g.GeneralSearchAll <EdgesStack>(PreVisitVertex, null, VisitEdge, out _); bool FindIsomorphism(int vert) { (int int_0, int int_1)s; s.int_1 = vert; if (s.int_1 == vertCount) { return(true); } s.int_0 = int_2[s.int_1]; if (int_3[s.int_0] != -1) { return(h.OutEdges(mapping[int_3[s.int_0]]).Any(edge => Check(edge.To, ref s))); } for (var i = 0; i < vertCount; i++) { if (Check(i, ref s)) { return(true); } } return(false); } bool Check(int int_4, ref (int int_0, int int_1) pair) { if (mapped[int_4] || g.OutDegree(pair.int_0) != h.OutDegree(int_4) || g.InDegree(pair.int_0) != h.InDegree(int_4)) { return(false); } for (var i = 0; i < pair.int_1; i++) { var w1 = g.GetEdgeWeight(int_2[i], pair.int_0); var w2 = h.GetEdgeWeight(mapping[int_2[i]], int_4); if (w1 != w2 && (!w1.IsNaN() || !w2.IsNaN())) { return(false); } w1 = g.GetEdgeWeight(pair.int_0, int_2[i]); w2 = h.GetEdgeWeight(int_4, mapping[int_2[i]]); if (w1 != w2 && (!w1.IsNaN() || !w2.IsNaN())) { return(false); } } mapped[int_4] = true; mapping[pair.int_0] = int_4; if (FindIsomorphism(pair.int_1 + 1)) { return(true); } mapped[int_4] = false; return(false); } return(FindIsomorphism(0) ? mapping : null); }
/// <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="version">Wersja algorytmu (prosta, Christofidesa lub zmodyfikowana Christofidesa)</param> /// <returns>Krotka (weight, cycle) składająca się z długości (sumy wag krawędzi) znalezionego cyklu i tablicy krawędzi tworzących ten cykl)</returns> /// <remarks> /// Parametr version opisany jest w wyliczeniu <see cref="TSPTreeBasedVersion"/>.<para/> /// Elementy (krawędzie) umieszczone są w tablicy cycle w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<para/> /// 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 krotkę (NaN,null).<para/> /// Metodę można stosować dla grafów nieskierowanych.<para/> /// Zastosowana do grafu skierowanego zgłasza wyjątek <see cref="ArgumentException"/>>.<para/> /// Dla wartości parametru <see cref="TSPTreeBasedVersion.Simple"/>) metodę można stosować dla dla grafów z dowolnymi (również ujemnymi) wagami krawędzi, /// dla innych wartości parametru version wagi krawędzi powinny być z przedziału <float.MinValue,float.MaxValue>.<para/> /// Dla grafu nieskierowanego spełniajacego nierówność trójkąta metoda realizuje 1.5-aproksymacyjny algorytm Christofidesa /// (gdy parametr version ma wartość <see cref="TSPTreeBasedVersion.Christofides"/>) lub algorytm 2-aproksymacyjny (dla innych wartości parametru version). /// </remarks> /// <seealso cref="AproxTSPGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Edge[] cycle) TreeBasedTSP(this Graph g, TSPTreeBasedVersion version = TSPTreeBasedVersion.Simple) { if (g.Directed) { throw new ArgumentException("Directed graphs are not allowed"); } if (g.VerticesCount <= 2) { return(double.NaN, null); } var tree = g.Prim().mst; if (tree.EdgesCount != g.VerticesCount - 1) { return(double.NaN, null); } if (version != TSPTreeBasedVersion.Simple) { var oddDegreeVertices = new bool[g.VerticesCount]; if (version == TSPTreeBasedVersion.Christofides) { for (var i = 0; i < g.VerticesCount; i++) { oddDegreeVertices[i] = (tree.OutDegree(i) % 2 == 1); } } else { var count = 0; for (var i = 0; i < g.VerticesCount; i++) { if (tree.OutDegree(i) != 1) { continue; } oddDegreeVertices[i] = true; count++; } if (count % 2 == 1) { var i = 0; for (; tree.OutDegree(i) % 2 == 0 || tree.OutDegree(i) == 1; i++) { ; } oddDegreeVertices[i] = true; } } foreach (var edge in g.PerfectMatching(oddDegreeVertices)) { tree.AddEdge(edge); } } var weight = 0.0; var lastVert = 0; var cycleCounter = 0; var cycle = new Edge[g.VerticesCount]; bool PreVisitVertex(int vert) { if (vert == 0) { return(true); } var w = g.GetEdgeWeight(lastVert, vert); cycle[cycleCounter++] = new Edge(lastVert, vert, w); weight += w; lastVert = vert; return(true); } tree.GeneralSearchAll <EdgesStack>(PreVisitVertex, null, null, out _); var edgeWeight = g.GetEdgeWeight(lastVert, 0); cycle[g.VerticesCount - 1] = new Edge(lastVert, 0, edgeWeight); weight += edgeWeight; return(!weight.IsNaN() ? (weight, cycle) : (double.NaN, null)); }
public static bool ContainsEdge(this Graph g, Edge e) { return(!double.IsNaN(g.GetEdgeWeight(e.From, e.To))); }
private State SolveProblem(State state, int ii, int jj, double[,] tab) { var problemSize = state.tab.GetLength(0) - 1; var naNColumns = new bool[problemSize - 1]; var naNRows = new bool[problemSize - 1]; var zeroColumns = new bool[problemSize - 1]; var zeroRows = new bool[problemSize - 1]; var m = 0; var i = 0; int j; while (m <= problemSize) { if (m == ii) { i--; } else { var n = 0; j = 0; while (n <= problemSize) { if (n == jj) { j--; } else { tab[i, j] = state.tab[m, n]; if (m != problemSize && n != problemSize) { if (tab[i, j] == 0.0) { zeroRows[j] = true; zeroColumns[i] = true; } if (tab[i, j].IsNaN()) { naNRows[j] = true; naNColumns[i] = true; } } } n++; j++; } } m++; i++; } i = 0; while (i < problemSize - 1 && naNColumns[i]) { i++; } j = 0; while (j < problemSize - 1 && naNRows[j]) { j++; } if (tab[i, j] == 0.0) { zeroRows[j] = false; zeroColumns[i] = false; } tab[i, j] = double.NaN; var from = (int)state.tab[ii, problemSize]; var to = (int)state.tab[problemSize, jj]; state.edges[problemSize - 1] = new Edge(from, to, g.GetEdgeWeight(from, to)); for (var c = 0; c < problemSize - 1; c++) { if (!zeroColumns[c]) { ProcessColumn(tab, c); } } for (var r = 0; r < problemSize - 1; r++) { if (!zeroRows[r]) { ProcessRow(tab, r); } } return(new State(tab, state.edges)); }
} // 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
/// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera algorytmem przyrostowym /// </summary> /// <param name="g">Badany graf</param> /// <param name="select">Metoda wyboru wstawianego wierzchołka</param> /// <returns>Krotka (weight, cycle) składająca się z długości (sumy wag krawędzi) znalezionego cyklu i tablicy krawędzi tworzących ten cykl)</returns> /// <remarks> /// Znaczenia parametru select opisane jest w wyliczeniu <see cref="TSPIncludeVertexSelectionMethod"/>.<para/> /// Jeśli algorytm przyrostowy nie znajdzie w badanym grafie cyklu Hamiltona (co oczywiście nie znaczy, /// że taki cykl nie istnieje) to metoda zwraca krotkę (NaN,null).<para/> /// Przy wyborze wstawiania najbliższego i najdalszego wierzchołka metodę można stosować jedynie /// dla grafów nieskierowanych.<para/> /// Przy wyborze wstawiania kolejnego wierzchołka i wstawiania wierzchołka o /// najniższym koszcie wstawiania metodę można stosować dla grafów skierowanych i nieskierowanych.<para/> /// Metodę można stosować dla dla grafów z wagami krawędzi z przedziału <float.MinValue,float.MaxValue>. /// </remarks> /// <seealso cref="AproxTSPGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Edge[] cycle) IncludeTSP(this Graph g, TSPIncludeVertexSelectionMethod select = TSPIncludeVertexSelectionMethod.Sequential) { if (g.Directed && (select == TSPIncludeVertexSelectionMethod.Nearest || select == TSPIncludeVertexSelectionMethod.Furthest)) { throw new ArgumentException( "Directed graphs are not allowed for Nearest and Furthest selection methods"); } if (g.VerticesCount <= (g.Directed ? 1 : 2)) { return(double.NaN, null); } var solution = new LinkedList <Edge>(); var available = new HashSet <int>(); PriorityQueue <int, double> nearest = null; double[] furthest = null; solution.AddFirst(new Edge(0, 0, ClampDoubleToFloat(double.NaN))); for (var i = 1; i < g.VerticesCount; i++) { available.Add(i); } switch (select) { case TSPIncludeVertexSelectionMethod.Nearest: bool MinValueOrKey(KeyValuePair <int, double> kvp1, KeyValuePair <int, double> kvp2) { if (kvp1.Value != kvp2.Value) { return(kvp1.Value < kvp2.Value); } return(kvp1.Key < kvp2.Key); } nearest = new PriorityQueue <int, double>(MinValueOrKey, CMonDoSomething.Nothing); for (var i = 1; i < g.VerticesCount; i++) { nearest.Put(i, ClampDoubleToFloat(double.PositiveInfinity)); } foreach (var edge in g.OutEdges(0)) { nearest.ImprovePriority(edge.To, ClampDoubleToFloat(edge.Weight)); } break; case TSPIncludeVertexSelectionMethod.Furthest: furthest = new double[g.VerticesCount]; for (var i = 1; i < g.VerticesCount; i++) { furthest[i] = ClampDoubleToFloat(double.PositiveInfinity); } foreach (var edge in g.OutEdges(0)) { furthest[edge.To] = ClampDoubleToFloat(edge.Weight); } break; case TSPIncludeVertexSelectionMethod.Sequential: break; case TSPIncludeVertexSelectionMethod.MinimalCost: break; default: throw new ArgumentException("Invalid vertex selection method"); } for (var i = 1; i < g.VerticesCount; i++) { LinkedListNode <Edge> edgeNode = null; int currVert; switch (select) { case TSPIncludeVertexSelectionMethod.Sequential: currVert = i; break; case TSPIncludeVertexSelectionMethod.Nearest: currVert = nearest.Get(); foreach (var edge in g.OutEdges(currVert)) { if (available.Contains(edge.To)) { nearest.ImprovePriority(edge.To, ClampDoubleToFloat(edge.Weight)); } } break; case TSPIncludeVertexSelectionMethod.Furthest: var max = double.NegativeInfinity; currVert = available.First(); foreach (var vert in available) { if (max < furthest[vert]) { currVert = vert; max = furthest[vert]; } } foreach (var edge in g.OutEdges(currVert)) { var w = ClampDoubleToFloat(edge.Weight); if (furthest[edge.To] > w) { furthest[edge.To] = w; } } break; case TSPIncludeVertexSelectionMethod.MinimalCost: var min = double.PositiveInfinity; edgeNode = solution.First; currVert = available.First(); foreach (var vert in available) { for (var k = solution.First; k != null; k = k.Next) { var dif = ClampDoubleToFloat(g.GetEdgeWeight(k.Value.From, vert)) + ClampDoubleToFloat(g.GetEdgeWeight(vert, k.Value.To)) - ClampDoubleToFloat(k.Value.Weight); if (!(min > dif)) { continue; } edgeNode = k; currVert = vert; min = dif; } } break; default: throw new ArgumentException("Invalid vertex selection method"); } available.Remove(currVert); if (edgeNode == null) { var min = double.PositiveInfinity; edgeNode = solution.First; for (var j = solution.First; j != null; j = j.Next) { var diff = ClampDoubleToFloat(g.GetEdgeWeight(j.Value.From, currVert)) + ClampDoubleToFloat(g.GetEdgeWeight(currVert, j.Value.To)) - ClampDoubleToFloat(j.Value.Weight); if (!(min > diff)) { continue; } edgeNode = j; min = diff; } } solution.AddBefore(edgeNode, new Edge(edgeNode.Value.From, currVert, g.GetEdgeWeight(edgeNode.Value.From, currVert))); solution.AddBefore(edgeNode, new Edge(currVert, edgeNode.Value.To, g.GetEdgeWeight(currVert, edgeNode.Value.To))); solution.Remove(edgeNode); } var cycle = solution.ToArray(); var weight = 0.0; for (var i = 0; i < cycle.Length; i++) { weight += cycle[i].Weight; } return(!weight.IsNaN() ? (weight, cycle) : (double.NaN, null)); }
/// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera algorytmem 3-optymalnym /// </summary> /// <param name="g">Badany graf</param> /// <param name="init">Cykl początkowy</param> /// <returns>Krotka (weight, cycle) składająca się z długości (sumy wag krawędzi) znalezionego cyklu i tablicy krawędzi tworzących ten cykl)</returns> /// <remarks> /// Elementy (krawędzie) umieszczone są w tablicy cycle w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<para/> /// Jeśli algorytm 3-optymalny nie znajdzie w badanym grafie cyklu Hamiltona (co oczywiście nie znaczy, że taki cykl nie istnieje) to metoda zwraca krotkę (NaN,null).<para/> /// Metodę można stosować dla grafów skierowanych i nieskierowanych.<para/> /// Metodę można stosować dla dla grafów z wagami krawędzi z przedziału <float.MinValue,float.MaxValue>.<para/> /// Metoda sprawdza poprawność cyklu początkowego (dopuszcza sztuczne krawędzie), /// nie jest on poprawny lub nie został podany (ma wartość domyślną null), /// to jako cykl początkowy przyjmuje cykl przechodzący przez wierzchołki w kolejności ich numeracji /// (jeśli jakaś krawędź wchodząca w skład takiego "cyklu" nie istnieje przyjmowana /// jest sztuczna krawędź z wagą <see cref="float.MaxValue"/>).<para/> /// Element init[i] zawiera numer wierzchołka do którego przechodzimy z wierzchołka i-tego.<para/> /// Metoda wykonuje obliczenia równolegle w wielu wątkach. /// </remarks> /// <seealso cref="AproxTSPGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Edge[] cycle) ThreeOptTSPParallel(this Graph g, int[] init = null) { if (g.VerticesCount <= (g.Directed ? 1 : 2)) { return(double.NaN, null); } double weight; if (g.VerticesCount == 2) { weight = g.GetEdgeWeight(0, 1) + g.GetEdgeWeight(1, 0); if (weight.IsNaN()) { return(double.NaN, null); } var c = new Edge[2]; c[0] = g.OutEdges(0).First(); c[1] = g.OutEdges(1).First(); return(weight, c); } int[] tempCycle; var useInit = false; var weights = new double[g.VerticesCount]; var newJ = new int[g.VerticesCount]; var newK = new int[g.VerticesCount]; var differences = new double[g.VerticesCount]; void Loop(int i) { differences[i] = 0.0; if (tempCycle[i] == 0) { return; } for (var j = tempCycle[i]; tempCycle[j] != 0; j = tempCycle[j]) { var w = ClampDoubleToFloat(g.GetEdgeWeight(i, tempCycle[j])); for (var k = tempCycle[j]; k != 0; k = tempCycle[k]) { var difference = weights[i] + weights[j] + weights[k] - w - ClampDoubleToFloat(g.GetEdgeWeight(j, tempCycle[k])) - ClampDoubleToFloat(g.GetEdgeWeight(k, tempCycle[i])); if (!(differences[i] < difference)) { continue; } differences[i] = difference; newJ[i] = j; newK[i] = k; } } } if (init != null && init.Length == g.VerticesCount && init[0] > 0 && init[0] < g.VerticesCount) { var visited = new bool[g.VerticesCount]; int vertCount; var i = init[0]; for (vertCount = 2; vertCount < g.VerticesCount; vertCount++, i = init[i], visited[i] = true) { if (init[i] <= 0 || init[i] >= g.VerticesCount || init[i] == i || visited[i]) { break; } } if (vertCount == g.VerticesCount && init[i] == 0) { useInit = true; } } if (useInit) { tempCycle = (int[])init.Clone(); } else { tempCycle = new int[g.VerticesCount]; for (var i = 1; i < g.VerticesCount; i++) { tempCycle[i - 1] = i; } tempCycle[g.VerticesCount - 1] = 0; } while (true) { for (var i = 0; i < g.VerticesCount; i++) { weights[i] = ClampDoubleToFloat(g.GetEdgeWeight(i, tempCycle[i])); } Parallel.For(0, g.VerticesCount, Loop); var maxDifference = 0.0; var newI = -1; for (var i = 0; i < g.VerticesCount; i++) { if (!(maxDifference < differences[i])) { continue; } maxDifference = differences[i]; newI = i; } if (maxDifference == 0.0) { break; } var temp = tempCycle[newI]; tempCycle[newI] = tempCycle[newJ[newI]]; tempCycle[newJ[newI]] = tempCycle[newK[newI]]; tempCycle[newK[newI]] = temp; } var cycle = new Edge[g.VerticesCount]; weight = 0.0; { int i; int j; for (i = 0, j = 0; j < g.VerticesCount; j++, i = tempCycle[i]) { cycle[j] = new Edge(i, tempCycle[i], g.GetEdgeWeight(i, tempCycle[i])); weight += cycle[j].Weight; } } return(weight.IsNaN() ? (double.NaN, null) : (weight, cycle)); }
private static IEnumerable <Edge> PerfectMatching(this Graph g, IReadOnlyList <bool> oddVertices) { var array = new int[g.VerticesCount]; var oddVerticesCount = g.VerticesCount; for (var i = 0; i < g.VerticesCount; i++) { if (!oddVertices[i]) { oddVerticesCount--; } } if (oddVerticesCount % 2 != 0) { throw new ArgumentException("Invalid arguments in Matching"); } for (var n = -1; ;) { var m = n + 1; while (m < g.VerticesCount && !oddVertices[m]) { m++; } if (m == g.VerticesCount) { break; } n = m + 1; while (n < g.VerticesCount && !oddVertices[n]) { n++; } array[m] = n; array[n] = m; var xD = -1; var xDD = -1; var flag1 = false; var flag2 = false; var floatWeight = ClampDoubleToFloat(g.GetEdgeWeight(m, n)); for (var i = 0; i < m; i++) { if (!oddVertices[i]) { continue; } var num11 = ClampDoubleToFloat(g.GetEdgeWeight(i, m)) + ClampDoubleToFloat(g.GetEdgeWeight(array[i], n)) - ClampDoubleToFloat(g.GetEdgeWeight(i, array[i])); if (!(floatWeight > num11)) { continue; } floatWeight = num11; xDD = i; flag2 = true; } for (var i = 0; i < m; i++) { if (!oddVertices[i]) { continue; } for (var j = 0; j < m; j++) { if (!oddVertices[j] || i == j || i == array[j] || j == array[i]) { continue; } var num11 = ClampDoubleToFloat(g.GetEdgeWeight(i, m)) + ClampDoubleToFloat(g.GetEdgeWeight(j, n)) + ClampDoubleToFloat(g.GetEdgeWeight(array[i], array[j])) - ClampDoubleToFloat(g.GetEdgeWeight(i, array[i])) - ClampDoubleToFloat(g.GetEdgeWeight(j, array[j])); if (!(floatWeight > num11)) { continue; } floatWeight = num11; xDD = i; xD = j; flag1 = true; } } if (flag1) { var x = array[xDD]; var d = array[xD]; array[xDD] = m; array[m] = xDD; array[xD] = n; array[n] = xD; array[x] = d; array[d] = x; } else if (flag2) { array[n] = array[xDD]; array[array[xDD]] = n; array[m] = xDD; array[xDD] = m; } } var matching = new Edge[oddVerticesCount / 2]; for (int i = 0, j = 0; i < g.VerticesCount; i++) { if (i < array[i]) { matching[j++] = new Edge(i, array[i], ClampDoubleToFloat(g.GetEdgeWeight(i, array[i]))); } } return(matching); }
/// <summary> /// Wyznacza maksymalny przepływ o minimalnym koszcie /// </summary> /// <param name="g">Graf przepustowości</param> /// <param name="c">Graf kosztów</param> /// <param name="source">Wierzchołek źródłowy</param> /// <param name="target">Wierzchołek docelowy</param> /// <param name="parallel">Informacja czy algorytm ma korzystać z równoległej wersji algorytmu Forda-Bellmana</param> /// <param name="mf">Metoda wyznaczania wstępnego maksymalnego przepływu (bez uwzględniania kosztów)</param> /// <param name="af">Metoda powiększania przepływu</param> /// <param name="matrixToAVL">Czy optymalizować sposób reprezentacji grafu rezydualnego</param> /// <returns> /// Krotka (value, cost, flow) składająca się z /// wartości maksymalnego przepływu, jego kosztu i grafu opisującego ten przepływ /// </returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Można wybrać metodę wyznaczania wstępnego maksymalnego przepływu (parametr mf).<para/> /// Domyślna wartość mf (null) oznacza konstrukcję wstępnego przepływu z wykorzystaniem sztucznej krawędzi.<para/> /// Można wybrać metodę powiększania przepływu (parametr pf).<para/> /// Znaczenie tego parametru zależy od wybranej metody wyznaczania wstępnego maksymalnego przepływu /// (parametr mf), dla niektórych wartości parametru mf /// (np. <see cref="MaxFlowGraphExtender.FordFulkersonDinicMaxFlow"/>) /// jawne wskazanie metody powiększania przepływu jest konieczne /// (pozostawienie domyślnej wartości parametru pf (null) /// spowoduje zgłoszenie wyjątku <see cref="ArgumentException"/>).<para/> /// Metoda uruchomiona dla grafu nieskierowanego lub grafu /// z ujemnymi przepustowościami krawędzi zgłasza wyjątek <see cref="ArgumentException"/>.<para/> /// Gdy grafy przepustowości i kosztów mają różną strukturę lub parametry /// source i target są równe metoda również zgłasza wyjątek <see cref="ArgumentException"/>.<para/> /// Gdy w grafie przepustowości istnieją krawędzie w obu kierunkach /// pomiędzy parą wierzchołków metoda również zgłasza wyjątek <see cref="ArgumentException"/>. /// </remarks> /// <seealso cref="MinCostFlowGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double value, double cost, Graph flow) MinCostFlow(this Graph g, Graph c, int source, int target, bool parallel = false, MaxFlow mf = null, AugmentFlow af = null, bool matrixToAVL = true) { if (!g.Directed) { throw new ArgumentException("Undirected graphs are not allowed"); } if (source == target) { throw new ArgumentException("Source and target must be different"); } if (g.VerticesCount != c.VerticesCount) { throw new ArgumentException("Inconsistent capacity and cost graphs"); } var fordBellmanShortestPaths = parallel ? ShortestPathsGraphExtender.FordBellmanShortestPathsParallel : new FordBellmanShortestPaths(ShortestPathsGraphExtender.FordBellmanShortestPaths); var gOut = new HashSet <int>(); var cOut = new HashSet <int>(); for (var i = 0; i < g.VerticesCount; i++) { gOut.Clear(); cOut.Clear(); foreach (var edge in g.OutEdges(i)) { if (edge.Weight < 0.0) { throw new ArgumentException("Negative capacity edges are not allowed"); } if (!g.GetEdgeWeight(edge.To, edge.From).IsNaN()) { throw new ArgumentException( "Edges in both directions between pair of vertices are not allowed"); } gOut.Add(edge.To); } foreach (var edge in c.OutEdges(i)) { cOut.Add(edge.To); } if (!gOut.SetEquals(cOut)) { throw new ArgumentException("Inconsistent capacity and cost graphs"); } } var tempCost = double.NaN; var tempFlow = double.NaN; var maxFlow = double.NaN; Graph flow; if (mf != null) { (maxFlow, flow) = mf(g, source, target, af, matrixToAVL); } else { if (!(tempFlow = g.GetEdgeWeight(source, target)).IsNaN()) { g.DelEdge(source, target); } if (!(tempCost = c.GetEdgeWeight(source, target)).IsNaN()) { c.DelEdge(source, target); } flow = g.IsolatedVerticesGraph(); var maxPossibleFlow = g.OutEdges(source).Sum(e => e.Weight); g.AddEdge(source, target, maxPossibleFlow + 1.0); flow.AddEdge(source, target, maxPossibleFlow + 1.0); var maxPossibleCost = 0.0; for (var i = 0; i < c.VerticesCount; i++) { foreach (var edge4 in c.OutEdges(i)) { maxPossibleCost += Math.Abs(edge4.Weight); flow.AddEdge(edge4.From, edge4.To, 0.0); } } c.AddEdge(source, target, maxPossibleCost + 1.0); } var residualFlow = flow.IsolatedVerticesGraph(); var residualCost = flow.IsolatedVerticesGraph(); for (var i = 0; i < flow.VerticesCount; i++) { foreach (var edge in flow.OutEdges(i)) { var something = Math.Min(g.GetEdgeWeight(edge.From, edge.To), double.MaxValue) - edge.Weight; if (something > 0.0) { residualFlow.AddEdge(edge.From, edge.To, something); residualCost.AddEdge(edge.From, edge.To, c.GetEdgeWeight(edge.From, edge.To)); } if (edge.Weight > 0.0) { residualFlow.AddEdge(edge.To, edge.From, edge.Weight); residualCost.AddEdge(edge.To, edge.From, -c.GetEdgeWeight(edge.From, edge.To)); } } } var foundFlow = 0.0; while (!fordBellmanShortestPaths(residualCost, target, out var pi)) { var cycle = residualCost.FindNegativeCostCycle(pi).cycle; var cycleMaxFlow = double.PositiveInfinity; var flag = false; foreach (var edge in cycle) { if (edge.From == target && edge.To == source) { flag = true; } var weight = residualFlow.GetEdgeWeight(edge.From, edge.To); if (cycleMaxFlow > weight) { cycleMaxFlow = weight; } } if (flag) { foundFlow += cycleMaxFlow; } foreach (var edge in cycle) { if (flag = (flow.GetEdgeWeight(edge.To, edge.From) > 0.0)) { var weight = flow.ModifyEdgeWeight(edge.To, edge.From, -cycleMaxFlow); if (weight < 0.0) { flow.ModifyEdgeWeight(edge.To, edge.From, -weight); flow.ModifyEdgeWeight(edge.From, edge.To, -weight); } } else { flow.ModifyEdgeWeight(edge.From, edge.To, cycleMaxFlow); } if (residualFlow.ModifyEdgeWeight(edge.From, edge.To, -cycleMaxFlow) == 0.0) { residualFlow.DelEdge(edge); residualCost.DelEdge(edge); } if (residualFlow.ModifyEdgeWeight(edge.To, edge.From, cycleMaxFlow).IsNaN()) { residualFlow.AddEdge(edge.To, edge.From, cycleMaxFlow); var weight = (flag ? c.GetEdgeWeight(edge.To, edge.From) : c.GetEdgeWeight(edge.From, edge.To)); residualCost.AddEdge(edge.To, edge.From, flag ? weight : (-weight)); } } } if (mf == null) { g.DelEdge(source, target); c.DelEdge(source, target); flow.DelEdge(source, target); if (!tempFlow.IsNaN()) { foundFlow += tempFlow; g.AddEdge(source, target, tempFlow); c.AddEdge(source, target, tempCost); flow.AddEdge(source, target, tempFlow); } maxFlow = foundFlow; } var cost = 0.0; for (var k = 0; k < flow.VerticesCount; k++) { cost += flow.OutEdges(k).Sum(e => e.Weight * c.GetEdgeWeight(e.From, e.To)); } return(maxFlow, cost, flow); }
/// <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); }