/// <summary> /// Wyznacza ścieżki o maksymalnej przepustowości /// </summary> /// <param name="g">Badany graf</param> /// <param name="s">Wierzchołek źródłowy</param> /// <param name="d">Znalezione ścieżki (parametr wyjściowy)</param> /// <returns><b>true</b></returns> /// <remarks> /// Metoda przydaje się w algorytmach wyznaczania maksymalnego przepływu, wagi krawędzi oznaczają tu przepustowość krawędzi.<br/> /// Przepustowość ścieżki to najmniejsza z przepustowości krawędzi wchodzących w jej skład.<br/> /// <br/> /// Elementy tablicy <i>d</i> zawierają przepustowości ścieżek od źródła do wierzchołka określonego przez indeks elementu.<br/> /// Jeśli ścieżka od źródła do danego wierzchołka nie istnieje, to przepustowość ma wartość <b>null</b>. /// <br/> /// Metoda zawsze zwraca <b>true</b> (można ją stosować do każdego grafu). /// </remarks> public static bool MaxFlowPathsLab05(this Graph g, int s, out PathsInfo[] d) { PathsInfo[] paths = new PathsInfo[g.VerticesCount]; Predicate <Edge> visitEdge = (Edge e) => { int?last = paths[e.From].Dist; if (last == null) { paths[e.To].Last = e; paths[e.To].Dist = e.Weight; return(true); } if (paths[e.To].Dist == null || paths[e.To].Dist.Value <= Math.Min(e.Weight, last.Value)) { paths[e.To].Last = e; paths[e.To].Dist = Math.Min(e.Weight, last.Value); } return(true); }; g.GeneralSearchFrom <EdgesMaxPriorityQueue>(s, null, visitEdge); d = paths; return(true); }
/// <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>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) { int verticesCount = g.VerticesCount; double edgeWeight = double.NaN; Graph newGraph = g.IsolatedVerticesGraph(); for (int v = 0; v < verticesCount; v++) { bool[] shouldEdgeBeAdded = new bool[verticesCount]; for (int i = 0; i < verticesCount; i++) { shouldEdgeBeAdded[i] = false; } Predicate <Edge> updateEdgesToBeAdded = e => { if (edgeWeight.IsNaN()) { edgeWeight = e.Weight; } if (edgeWeight != e.Weight) { throw new ArgumentException("Graf jest grafem ważonym."); } // W nieskierowanych nie dodajemy krawędzi "do tyłu" if (g.Directed || e.From < e.To) { shouldEdgeBeAdded[e.To] = true; } return(true); }; g.GeneralSearchFrom <EdgesStack>(v, null, null, updateEdgesToBeAdded); // Wykluczamy ewentualną pętlę shouldEdgeBeAdded[v] = false; for (int i = 0; i < verticesCount; i++) { if (shouldEdgeBeAdded[i]) { newGraph.AddEdge(v, i); } } } return(newGraph); }
/// <summary> /// Przeszukuje cały graf /// </summary> /// <param name="g">Badany graf</param> /// <param name="preVisitVertex">Metoda wywoływana przy pierwszym wejściu do wierzchołka</param> /// <param name="postVisitVertex">Metoda wywoływana przy ostatecznym opuszczaniu wierzchołka</param> /// <param name="visitEdge">Metoda wywoływana dla odwiedzanych krawędzi</param> /// <param name="cc">Liczba "spójnych składowych" grafu (parametr wyjściowy)</param> /// <param name="nr">Tablica kolejności "wierzchołków startowych"</param> /// <typeparam name="TEdgesContainer">Typ kolekcji przechowującej krawędzie</typeparam> /// <returns>Informacja czy zbadano wszystkie krawędzie grafu</returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Metoda odwiedza wszystkie wierzchołki grafu wykorzystując metodę /// <see cref="GeneralSearchFrom{TEdgesContainer}"/> (w razie potrzeby wywołuje ją wielokrotnie).<para/> /// Jako wierzchołek startowy dla metody <see cref="GeneralSearchFrom{TEdgesContainer}"/> /// wybierany jest pierwszy nieodwiedzony wierzchołek nr[i], /// gdzie tablica nr przeglądana jest w kierunku rosnących indeksów.<para/> /// Domyślna wartość parametru nr (null) oznacza, że tablica nr zostanie automatycznie /// wypełniona wartościami nr[i]=i (kolejność domyślna).<para/> /// Wartością parametru wyjściowego cc jest wykonana liczba wywołań metody /// <see cref="GeneralSearchFrom{TEdgesContainer}"/> (dla grafów nieskierowanych jest to /// liczba spójnych składowych grafu, dla grafów skierowanych oczywiście nie ma takiej zależności).<para/> /// Wartość parametru cc jest inkrementowana przed każdym wywołaniem metody /// <see cref="GeneralSearchFrom{TEdgesContainer}"/>.<para/> /// Parametrem metod preVisitVertex i postVisitVertex jest numer odwiedzanego wierzchołka.<para/> /// Metoda preVisitVertex jest wywoływana gdy przeszukiwanie grafu osiąga dany wierzchołek /// (jest on już oznaczony jako odwiedzony, ale wychodzące z /// niego krawędzie nie są jeszcze dodane do kolekcji.<para/> /// Metoda postVisitVertex jest wywoływana gdy z kolekcji została usunięta /// (i w pełni przetworzona) ostatnia krawędź wychodząca z danego wierzchołka.<para/> /// Korzystanie z metody postVisitVertex jest dozwolone jedynie /// gdy kontenerem przechowującym krawędzie jest <see cref="EdgesStack"/> czyli /// dla przeszukiwania w głąb (DFS) z jawnym stosem /// (w innych przypadkach metoda zgłasza wyjątek <see cref="ArgumentException"/>).<para/> /// Metoda visitEdge jest wywoływana dla każdej krawędzi rozważanej /// podczas przeszukiwania (nawet jeśli nie została wykorzystana), /// krawędź ta jest parametrem metody visitEdge.<para/> /// Wyniki zwracane przez metody preVisitVertex, postVisitVertex /// i visitEdge oznaczają żądanie kontynuowania (true) lub przerwania (false) obliczeń.<para/> /// Parametry preVisitVertex, postVisitVertex i visitEdge mogą mieć wartość null, /// co oznacza metodę pustą (nie wykonującą żadnych działań, zwracającą true).<para/> /// Jeśli zostały zbadane wszystkie krawędzie grafu metoda zwraca true, /// jeśli obliczenia zostały przerwane przez metodę związaną z parametrem preVisitVertex, /// postVisitVertex lub visitEdge metoda zwraca false. /// </remarks> /// <seealso cref="GeneralSearchGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool GeneralSearchAll <TEdgesContainer>(this Graph g, Predicate <int> preVisitVertex, Predicate <int> postVisitVertex, Predicate <Edge> visitEdge, out int cc, int[] nr = null) where TEdgesContainer : IEdgesContainer, new() { if (nr != null) { if (nr.Length != g.VerticesCount) { throw new ArgumentException("Invalid order table"); } var used = new bool[g.VerticesCount]; for (var i = 0; i < g.VerticesCount; i++) { if (nr[i] < 0 || nr[i] >= g.VerticesCount || used[nr[i]]) { throw new ArgumentException("Invalid order table"); } used[nr[i]] = true; } } else { nr = new int[g.VerticesCount]; for (var i = 0; i < g.VerticesCount; i++) { nr[i] = i; } } var visitedVertices = new bool[g.VerticesCount]; cc = 0; for (var i = 0; i < g.VerticesCount; i++) { if (visitedVertices[nr[i]]) { continue; } cc++; if (!g.GeneralSearchFrom <TEdgesContainer>(nr[i], preVisitVertex, postVisitVertex, visitEdge, visitedVertices)) { return(false); } } return(true); }
/// <summary>Badanie czy graf jest dwudzielny</summary> /// <param name="g">Graf wejściowy</param> /// <returns>Informacja czy graf wejściowy jest dwudzielny</returns> /// <remarks> /// 1.5 pkt. /// Graf wejściowy pozostaje niezmieniony. /// </remarks> public static bool IsBipartite(this Graph g) { int verticesCount = g.VerticesCount; for (int v = 0; v < verticesCount; v++) { int[] distanceFromV = new int[verticesCount]; for (int i = 0; i < verticesCount; i++) { distanceFromV[i] = -1; } distanceFromV[v] = 0; Predicate <Edge> updateDistanceFromV = e => { if (distanceFromV[e.To] > -1) { // Cykle muszą być długości parzystej w grafie dwudzielnym if ((distanceFromV[e.To] + distanceFromV[e.From] + 1) % 2 != 0) { return(false); } } distanceFromV[e.To] = distanceFromV[e.From] + 1; return(true); }; if (!g.GeneralSearchFrom <EdgesQueue>(v, null, null, updateDistanceFromV)) { return(false); } } return(true); }
/// <summary> /// Wyznacza ścieżkę powiekszającą o maksymalnej przepustowości spośród najkrótszych ścieżek /// </summary> /// <param name="g">Graf rezydualny</param> /// <param name="s">Wierzchołek źródłowy</param> /// <param name="t">Wierzchołek docelowy</param> /// <returns> /// Krotka (augmentingValue, augmentingFlow) składająca się z przepustowości wyznaczonej ścieżki /// i grafu opisującego tą ścieżkę /// </returns> /// <remarks> /// Jeśli ścieżka powiększająca nie istnieje, to zwracana jest krotka (0.0,null).<para/> /// Jeśli graf g jest typu <see cref="AdjacencyMatrixGraph"/> to wyznaczona ścieżka powiększająca p jest typu /// <see cref="AdjacencyListsGraph{HashTableAdjacencyList}"/>, /// w przeciwnym przypadku ścieżka p jest takiego samego typu jak graf g. /// </remarks> /// <seealso cref="MaxFlowGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double augmentingValue, Graph augmentingFlow) BFMaxPath(this Graph g, int s, int t) { var pi = new PathsInfo[g.VerticesCount]; var steps = new int[g.VerticesCount]; steps[s] = 1; pi[s].Dist = double.PositiveInfinity; bool VisitEdge(Edge e) { if (steps[e.From] == steps[t]) { return(false); } if (steps[e.To] == 0) { steps[e.To] = steps[e.From] + 1; } else if (steps[e.To] != steps[e.From] + 1) { return(true); } var d = Math.Min(pi[e.From].Dist, e.Weight); if (!(pi[e.To].Dist < d)) { return(true); } pi[e.To].Dist = d; pi[e.To].Last = e; return(true); } g.GeneralSearchFrom <EdgesQueue>(s, null, null, VisitEdge); return(pi[t].Dist, g.BuildGraph(s, t, pi)); }