/// <summary> /// Wyznacza minimalne drzewo rozpinające grafu algorytmem Prima /// </summary> /// <param name="g">Badany graf</param> /// <returns> /// Krotka (weight, mst) składająca się z wagi minimalnego drzewa rozpinającego i grafu opisującego to drzewo /// </returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Jeśli graf g jest typu <see cref="AdjacencyMatrixGraph"/> to wyznaczone drzewo rozpinające mst jest typu /// <see cref="AdjacencyListsGraph{AVLAdjacencyList}"/>, w przeciwnym /// przypadku drzewo rozpinające mst jest takiego samego typu jak graf g.<para/> /// Dla grafu skierowanego metoda zgłasza wyjątek <see cref="ArgumentException"/>.<para/> /// Wyznaczone drzewo reprezentowane jest jako graf bez cykli, to umożliwia jednolitą obsługę sytuacji /// gdy analizowany graf jest niespójny, wyznaczany jest wówczas las rozpinający. /// </remarks> /// <seealso cref="MSTGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Graph mst) Prim(this Graph g) { if (g.Directed) { throw new ArgumentException("Directed graphs are not allowed"); } var tree = g is AdjacencyMatrixGraph ? new AdjacencyListsGraph <AVLAdjacencyList>(false, g.VerticesCount) : g.IsolatedVerticesGraph(); var weight = 0.0; bool VisitEdge(Edge edge) { if (tree.InDegree(edge.To) != 0) { return(true); } tree.AddEdge(edge); weight += edge.Weight; return(tree.EdgesCount != g.VerticesCount - 1); } g.GeneralSearchAll <EdgesMinPriorityQueue>(null, null, VisitEdge, out _); return(weight, tree); }
/// <summary> /// Wyznacza silnie spójne składowe przy pomocy algorytmu Kosaraju /// </summary> /// <param name="g">Badany graf</param> /// <returns> /// Krotka (count, scc) składająca się z liczby silnie spójnych składowych i tablicy opisującej te składowe /// </returns> /// <exception cref="ArgumentException">Gdy uruchomiona dla grafu nieskierowanego</exception> /// <remarks> /// Metoda uruchomiona dla grafu nieskierowanego zgłasza wyjątek <see cref="ArgumentException"/>.<para/> /// Tablica scc zawiera informacje do, której silnie spójnej składowej należy dany wierzchołek. /// </remarks> /// <seealso cref="SCCGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (int count, int[] scc) Kosaraju(this Graph g) { if (!g.Directed) { throw new ArgumentException("Undirected graphs are not allowed"); } var array = new int[g.VerticesCount]; var vertCount = g.VerticesCount; var count = 0; var scc = new int[g.VerticesCount]; bool PostVisitVertex(int vert) { count++; array[vertCount - count] = vert; return(true); } bool PreVisitVertex(int vert) { scc[vert] = count - 1; return(true); } g.GeneralSearchAll <EdgesStack>(null, PostVisitVertex, null, out _); Reverse(g).GeneralSearchAll <EdgesStack>(PreVisitVertex, null, null, out count, array); return(count, scc); }
/// <summary> /// Wyznacza odwrotność grafu /// </summary> /// <param name="g">Badany graf</param> /// <returns>Odwrotność grafu</returns> /// <remarks> /// Odwrotność grafu to graf skierowany o wszystkich krawędziach przeciwnie skierowanych niż w grafie pierwotnym.<br/> /// <br/> /// Metoda uruchomiona dla grafu nieskierowanego zgłasza wyjątek <see cref="System.ArgumentException"/>. /// <br/> /// Graf wejściowy pozostaje niezmieniony. /// </remarks> public static Graph CustomReverse(this Graph g) { Graph reversed = g.IsolatedVerticesGraph(); g.GeneralSearchAll <EdgesQueue>(null, null, e => { reversed.AddEdge(e.To, e.From, e.Weight); return(true); }, out int cc); return(reversed); }
/// <summary> /// Bada czy graf nieskierowany jest acykliczny /// </summary> /// <param name="g">Badany graf</param> /// <returns>Informacja czy graf jest acykliczny</returns> /// <remarks> /// Metoda uruchomiona dla grafu skierowanego zgłasza wyjątek <see cref="System.ArgumentException"/>. /// <br/> /// Graf wejściowy pozostaje niezmieniony. /// </remarks> public static bool IsUndirectedAcyclic(this Graph g) { if (g.Directed == true) { throw new System.ArgumentException("Should be undirected"); } int cc; g.GeneralSearchAll <EdgesStack>(null, null, out cc, null); return(cc == (g.VerticesCount - g.EdgesCount)); }
/// <summary> /// Wyznacza silnie spójne składowe /// </summary> /// <param name="g">Badany graf</param> /// <param name="scc">Silnie spójne składowe (parametr wyjściowy)</param> /// <returns>Liczba silnie spójnych składowych</returns> /// <remarks> /// scc[v] = numer silnie spójnej składowej do której należy wierzchołek v<br/> /// (numerujemy od 0)<br/> /// <br/> /// Metoda uruchomiona dla grafu nieskierowanego zgłasza wyjątek <see cref="System.ArgumentException"/>. /// <br/> /// Graf wejściowy pozostaje niezmieniony. /// </remarks> public static int StronglyConnectedComponents(this Graph g, out int[] scc) { if (!g.Directed) { throw new System.ArgumentException("Graf jest nieskierowany"); } // Algorytm Kosaraju int[] initialOrder = new int[g.VerticesCount]; int currentOrderValue = 0; g.GeneralSearchAll <EdgesStack>(null, v => { initialOrder[currentOrderValue++] = v; return(true); }, null, out int cc); int[] vertexInComponent = new int[g.VerticesCount]; Graph reversed = g.CustomReverse(); // można skorzystać z bibliotecznego g.Reverse, ale zapewne o to chodziło w zadaniu, żeby zrobić własne bool[] visited = new bool[g.VerticesCount]; int leftToVisit = g.VerticesCount; int currentComponent = 0; while (leftToVisit > 0) { int startingVertex = 0; for (int i = g.VerticesCount - 1; i >= 0; i--) { int v = initialOrder[i]; if (!visited[v]) { startingVertex = v; break; } } reversed.GeneralSearchFrom <EdgesStack>(startingVertex, v => { leftToVisit--; vertexInComponent[v] = currentComponent; return(true); }, null, null, visited); currentComponent++; } scc = vertexInComponent; return(currentComponent); }
/// <summary> /// Sortowanie topologiczne wierzchołków grafu /// </summary> /// <param name="g">Badany graf</param> /// <param name="original2topological">Tablica opisująca przekształcenie numeracji pierwotnej w topologiczną</param> /// <param name="topological2original">Tablica opisująca przekształcenie numeracji topologicznej w pierworną</param> /// <returns>Informacja czy posortowanie topologiczne jest możliwe</returns> /// <exception cref="ArgumentException">Gdy uruchomiona dla grafu nieskierowanego</exception> /// <remarks> /// Wartość original2topological[i] to numer w porządku topologicznym wierzchołka o numerze pierwotnym i.<para/> /// Wartość topological2original[i] to numer w porządku pierwotnym wierzchołka o numerze topologicznym i.<para/> /// Jeśli posortowanie topologiczne grafu g nie jest możliwe (graf nie jest acykliczny) /// to parametry original2topological i topological2original są równe null.<para/> /// Metoda uruchomiona dla grafu nieskierowanego zgłasza wyjątek <see cref="ArgumentException"/>. /// </remarks> /// <seealso cref="GraphHelperExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool TopologicalSort(this Graph g, out int[] original2topological, out int[] topological2original) { if (!g.Directed) { throw new ArgumentException("Undirected graph are not allowed"); } var o2t = new int[g.VerticesCount]; var t2o = new int[g.VerticesCount]; var visitedVertices = new bool[g.VerticesCount]; var verticesCount = g.VerticesCount; topological2original = null; original2topological = null; for (var i = 0; i < g.VerticesCount; i++) { o2t[i] = -1; } bool PreVisitVertex(int vert) { visitedVertices[vert] = true; return(true); } bool PostVisitVertex(int vert) { verticesCount--; o2t[vert] = verticesCount; t2o[verticesCount] = vert; return(true); } bool VisitEdge(Edge edge) => !visitedVertices[edge.To] || o2t[edge.To] != -1; if (!g.GeneralSearchAll <EdgesStack>(PreVisitVertex, PostVisitVertex, VisitEdge, out _)) { return(false); } original2topological = o2t; topological2original = t2o; return(true); }
/// <summary> /// Bada czy graf nieskierowany jest acykliczny /// </summary> /// <param name="g">Badany graf</param> /// <returns>Informacja czy graf jest acykliczny</returns> /// <remarks> /// Metoda uruchomiona dla grafu skierowanego zgłasza wyjątek <see cref="System.ArgumentException"/>. /// <br/> /// Graf wejściowy pozostaje niezmieniony. /// </remarks> public static bool IsUndirectedAcyclic(this Graph g) { if (g.Directed) { throw new System.ArgumentException("Graf jest skierowany"); } bool[] isVertexInCurrentPath = new bool[g.VerticesCount]; EdgesStack edgesInCurrentPath = new EdgesStack(); bool isAcyclic = g.GeneralSearchAll <EdgesStack>(v => { isVertexInCurrentPath[v] = true; return(true); }, v => { isVertexInCurrentPath[v] = false; return(true); }, e => { if (!edgesInCurrentPath.Empty && edgesInCurrentPath.Peek().From == e.To) { // Krawędź "powrotna" aktualnej ścieżki edgesInCurrentPath.Get(); return(true); } if (isVertexInCurrentPath[e.To]) { return(false); } edgesInCurrentPath.Put(e); return(true); }, out int cc); return(isAcyclic); }
/// <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); }
} // 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