/// <summary> /// Znajduje rozwiązanie przybliżone problemu komiwojażera algorytmem zachłannym "kruskalopodobnym" /// </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 algorytm "kruskalopodobny" 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) KruskalTSP(this Graph g) { if (g.VerticesCount <= (g.Directed ? 1 : 2)) { return(double.NaN, null); } var graph = !(g is AdjacencyMatrixGraph) ? g.IsolatedVerticesGraph() : new AdjacencyListsGraph <SimpleAdjacencyList>(g.Directed, g.VerticesCount); var unionFind = new UnionFind(g.VerticesCount); var edgesMinPriorityQueue = new EdgesMinPriorityQueue(); for (var i = 0; i < g.VerticesCount; i++) { foreach (var edge in g.OutEdges(i)) { if (g.Directed || edge.From < edge.To) { edgesMinPriorityQueue.Put(edge); } } } while (graph.EdgesCount < g.VerticesCount && !edgesMinPriorityQueue.Empty) { var edge = edgesMinPriorityQueue.Get(); if (graph.OutDegree(edge.From) >= (g.Directed ? 1 : 2) || graph.InDegree(edge.To) >= (g.Directed ? 1 : 2)) { continue; } if (unionFind.Union(edge.From, edge.To) || graph.EdgesCount == g.VerticesCount - 1) { graph.AddEdge(edge); } } if (graph.EdgesCount < g.VerticesCount) { return(double.NaN, null); } var cycle = new Edge[g.VerticesCount]; var weight = 0.0; var prevVert = -1; var vert = 0; for (var i = 0; i < g.VerticesCount;) { foreach (var edge in graph.OutEdges(vert)) { if (edge.To == prevVert) { continue; } cycle[i++] = edge; weight += edge.Weight; prevVert = vert; vert = edge.To; break; } } return(weight, cycle); }
/// <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