public static bool BFSColoring(Graph g, int v, ref COLORS [] col) { Queue <int> q = new Queue <int>(); int vert; int partition_class = 0; int i; q.Enqueue(v); while (q.Count != 0) { i = 0; vert = q.Dequeue(); if (col[vert] == COLORS.NONE || col[vert] == ((partition_class % 2 == 0) ? COLORS.RED : COLORS.BLUE)) { if (col[vert] == COLORS.NONE) { col[vert] = (partition_class++ % 2 == 0) ? COLORS.RED : COLORS.BLUE; } foreach (Edge e in g.OutEdges(vert)) { if (col[e.To] == COLORS.NONE) { if (col[vert] == COLORS.RED) { col[e.To] = COLORS.BLUE; } else { col[e.To] = COLORS.RED; } q.Enqueue(e.To); } else if (col[e.To] == col[vert]) { Console.WriteLine("Vertex {0} (Color {1}) is connected with Vertex {2} (Color {3})", e.To, col[e.To], vert, col[vert]); return(false); } else { i++; } } if (i == g.OutDegree(v)) { partition_class--; } } foreach (Edge e in g.OutEdges(vert)) { if (col[e.To] == col[vert]) { return(false); } } partition_class++; } return(true); }
/// <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.<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); }
/// <summary> /// Zapisuje graf do pliku /// </summary> /// <param name="g">Wskazany graf</param> /// <param name="path">Nazwa pliku</param> /// <remarks> /// Graf zapisany jest w postaci XML.<para/> /// Nie jest stosowana serializacja, dzięki temu graf może być odtworzony w innej reprezentacji /// (jako obiekt innego typu - przy serializacji odtwarzany jest obiekt tego samego typu). /// </remarks> /// <seealso cref="Graph"/> /// <seealso cref="ASD.Graphs"/> internal static void Write(Graph g, string path) { var xmlWriterSettings = new XmlWriterSettings { Indent = true, IndentChars = " " }; using (var xmlWriter = XmlWriter.Create(path, xmlWriterSettings)) { xmlWriter.WriteStartDocument(); xmlWriter.WriteStartElement("Graph"); xmlWriter.WriteAttributeString("directed", $"{g.Directed}"); xmlWriter.WriteAttributeString("verticescount", $"{g.VerticesCount}"); xmlWriter.WriteStartElement("Edges"); for (var i = 0; i < g.VerticesCount; i++) { foreach (var edge in g.OutEdges(i)) { xmlWriter.WriteStartElement("Edge"); xmlWriter.WriteAttributeString("from", $"{edge.From}"); xmlWriter.WriteAttributeString("to", $"{edge.To}"); xmlWriter.WriteAttributeString("weight", $"{edge.Weight:R}"); xmlWriter.WriteEndElement(); } } xmlWriter.WriteEndElement(); xmlWriter.WriteEndElement(); xmlWriter.WriteEndDocument(); } }
/// <summary> /// Znajduje rozwiązanie dokładne problemu komiwojażera metodą podziału i ograniczeń /// </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> /// Metoda przeznaczona jest dla grafów z nieujemnymi wagami krawędzi.<br/> /// Uruchomiona dla grafu zawierającego krawędź o wadze ujemnej zgłasza wyjątek <see cref="System.ArgumentException"/>.<br/> /// <br/> /// Elementy (krawędzie) umieszczone są w tablicy <i>cycle</i> w kolejności swojego następstwa w znalezionym cyklu Hamiltona.<br/> /// <br/> /// Jeśli w badanym grafie nie istnieje cykl Hamiltona metoda zwraca wartość <b>NaN</b>, /// parametr wyjściowy <i>cycle</i> ma wówczas wartość <b>null</b>.<br/> /// <br/> /// Metodę można stosować dla grafów skierowanych i nieskierowanych. /// </remarks> public double TSP(Graph g, out Edge[] cycle) { int n = g.VerticesCount; double[,] CostMatrix = new double[n, n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { CostMatrix[i, j] = -1; } } for (int i = 0; i < n; i++) { foreach (Edge e in g.OutEdges(i)) { CostMatrix[i, e.To] = e.Weight; } } BNB(CostMatrix, 0, new List <Edge>(), n); cycle = solution.ToArray(); return(best); }
private static int?ConstructCycle(Graph g, out Edge[] edges) { if (g == null) { edges = null; return(null); } List <Edge> cycle = new List <Edge>(); bool[] visited = new bool[g.VerticesCount]; int last = 0; int weight = 0; while (cycle.Count != g.EdgesCount) { foreach (Edge e in g.OutEdges(last)) { if (!visited[e.To]) { cycle.Add(e); weight += e.Weight; visited[e.To] = true; last = e.To; break; } } } edges = cycle.ToArray(); return(weight); }
/// <summary> /// Wyznacza sumę grafów /// </summary> /// <param name="g">Pierwszy sumowany graf</param> /// <param name="h">Drugi sumowany graf</param> /// <returns></returns> /// <exception cref="ArgumentException">Gdy argumentem jest graf skierowany</exception> /// <remarks> /// Suma grafów to graf składający się ze wszystkich wierchołków i krawędzi sumowanych grafów /// (wierzchołki pochodzące z drugiego grafu są odpowiednio przenumerowane).<para/> /// Można sumować grafy reprezentowane w różny sposób, /// suma ma taką reprezentację jak pierwszy z sumowanych grafów). /// </remarks> /// <seealso cref="GraphHelperExtender"/> /// <seealso cref="ASD.Graphs"/> public static Graph Union(this Graph g, Graph h) { if (g.Directed != h.Directed) { throw new ArgumentException("Union of directed and undirected graph are not allowed"); } var union = g.IsolatedVerticesGraph(g.Directed, g.VerticesCount + h.VerticesCount); for (var i = 0; i < g.VerticesCount; i++) { foreach (var e in g.OutEdges(i)) { union.AddEdge(e); } } for (var i = 0; i < h.VerticesCount; i++) { foreach (var edge in h.OutEdges(i)) { union.AddEdge(i + g.VerticesCount, edge.To + g.VerticesCount, edge.Weight); } } return(union); }
private void CreateDotFile(Graph g, IReadOnlyList <string> verticesDescriptions, string fileName) { using (var streamWriter = new StreamWriter(fileName + ".dot")) { var format = " [label=\"{0" + WeightsFormat + "}\"]"; streamWriter.WriteLine(g.Directed ? "digraph" : "graph"); streamWriter.WriteLine("{"); for (var i = 0; i < g.VerticesCount; i++) { streamWriter.WriteLine("\tnode [shape=circle, label=\"{0}\"] {1};", verticesDescriptions[i], i); } for (var i = 0; i < g.VerticesCount; i++) { foreach (var edge in g.OutEdges(i)) { if (!g.Directed && edge.From > edge.To) { continue; } streamWriter.Write("\t"); streamWriter.Write("{0}", edge.From); streamWriter.Write(g.Directed ? " -> " : " -- "); streamWriter.Write("{0}", edge.To); if (ShowWeights) { IFormatProvider invariantCulture = CultureInfo.InvariantCulture; streamWriter.Write(string.Format(invariantCulture, format, edge.Weight)); } streamWriter.WriteLine(); } } streamWriter.WriteLine("}"); } }
/// <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 int CliqueNumberRecursive(Graph g, ref bool[] isVertexInClique, int cliqueSize, int startingVertex = 0) { bool[] biggestClique = isVertexInClique; int biggestCliqueSize = cliqueSize; if (cliqueSize > 0) { // Instead of analizying all the vertices // we only take into account neighbors of the last vertex in the clique // This is especially benefitial for sparse graphs (the last one in the test set) foreach (Edge e in g.OutEdges(startingVertex - 1)) { if (e.To <= e.From) { continue; } CheckNewCliqueVertex(g, e.To, cliqueSize, ref isVertexInClique, ref biggestClique, ref biggestCliqueSize); } } else { // In the initial step we analyze every vertex // because any vertex is a valid clique for (int v = startingVertex; v < g.VerticesCount; v++) { CheckNewCliqueVertex(g, v, cliqueSize, ref isVertexInClique, ref biggestClique, ref biggestCliqueSize); } } isVertexInClique = biggestClique; return(biggestCliqueSize); }
/// <summary>Dodawanie wierzchołka do grafu</summary> /// <param name="g">Graf, do którego dodajemy wierzchołek</param> /// <returns>Graf z dodanym wierzchołkiem</returns> /// <remarks> /// 0.5 pkt. /// Metoda zwraca graf, będący kopią grafu wejściowego z dodanym wierzchołkiem. /// Graf wejściowy pozostaje niezmieniony. /// W utworzonym grafie są takie same krawędzie jak w wejściowym. /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga: W grafach nieskierowanych nie probować dodawawać po 2 razy tej samej krawędzi /// </remarks> public static Graph AddVertex(this Graph g) { int originalVerticesCount = g.VerticesCount; Graph newGraph = g.IsolatedVerticesGraph(g.Directed, originalVerticesCount + 1); for (int v = 0; v < originalVerticesCount; v++) { foreach (Edge e in g.OutEdges(v)) { // W nieskierowanych dodajemy tylko krawędzie od wierzchołka o niższym indeksie // do wierzchołka o wyższym indeksie, żeby uniknąć podwójnego dodawania krawędzi if (!g.Directed) { if (e.From > e.To) { continue; } } newGraph.AddEdge(e); } } return(newGraph); }
/// <summary> /// Wyznacza minimalne drzewo rozpinające grafu algorytmem Kruskala /// </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) Kruskal(this Graph g) { if (g.Directed) { throw new ArgumentException("Directed graphs are not allowed"); } var unionFind = new UnionFind(g.VerticesCount); var edgesMinPriorityQueue = new EdgesMinPriorityQueue(); var tree = g is AdjacencyMatrixGraph ? new AdjacencyListsGraph <AVLAdjacencyList>(false, g.VerticesCount) : g.IsolatedVerticesGraph(); var weight = 0.0; for (var i = 0; i < g.VerticesCount; i++) { foreach (var edge in g.OutEdges(i)) { if (edge.From < edge.To) { edgesMinPriorityQueue.Put(edge); } } } while (tree.EdgesCount < g.VerticesCount - 1 && !edgesMinPriorityQueue.Empty) { var edge = edgesMinPriorityQueue.Get(); if (!unionFind.Union(edge.From, edge.To)) { continue; } tree.AddEdge(edge); weight += edge.Weight; } return(weight, tree); }
} // TSP_Kruskal private static Graph GetTree(Graph g) { Graph helper = g.IsolatedVerticesGraph(true, g.VerticesCount); var queue = new EdgesMinPriorityQueue(); for (int i = 0; i < g.VerticesCount; i++) { foreach (var e in g.OutEdges(i)) { helper.AddEdge(e); queue.Put(e); } } Graph tree = g.IsolatedVerticesGraph(true, g.VerticesCount); UnionFind uf = new UnionFind(g.VerticesCount); while (tree.EdgesCount != tree.VerticesCount - 1) { if (queue.Empty) { return(null); } Edge e = queue.Get(); if (uf.Find(e.To) != uf.Find(e.From) && tree.OutDegree(e.From) < 1 && tree.InDegree(e.To) < 1) { tree.AddEdge(e); uf.Union(e.From, e.To); } } return(tree); }
/// <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) { int verticesCount = g.VerticesCount; double edgeWeight = double.NaN; Graph newGraph = g.IsolatedVerticesGraph(); for (int v = 0; v < verticesCount; v++) { bool[] shouldEdgeBeAdded = new bool[verticesCount]; if (!g.Directed) { // Nie dodajemy krawędzi "do tyłu" w grafach nieskierowanych for (int i = 0; i <= v; i++) { shouldEdgeBeAdded[i] = false; } for (int i = v + 1; i < verticesCount; i++) { shouldEdgeBeAdded[i] = true; } } else { for (int i = 0; i < verticesCount; i++) { shouldEdgeBeAdded[i] = true; } // Nie dodajemy pętli shouldEdgeBeAdded[v] = false; } foreach (Edge e in g.OutEdges(v)) { if (edgeWeight.IsNaN()) { edgeWeight = e.Weight; } if (edgeWeight != e.Weight) { throw new ArgumentException("Graf jest grafem ważonym."); } shouldEdgeBeAdded[e.To] = false; } for (int i = 0; i < verticesCount; i++) { if (shouldEdgeBeAdded[i]) { newGraph.AddEdge(v, i); } } } return(newGraph); }
private protected void Merge(Graph g) { for (var i = 0; i < g.VerticesCount; i++) { foreach (var e in g.OutEdges(i)) { AddEdge(e); } } }
/// <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)); }
// koloruje graf algorytmem zachlannym (byc moze niepotymalnie) public static int GreedyColor(this Graph g, out int[] colors) { // kazdemu wierzcholkowi // przydzielamy najmniejszy kolor nie kolidujacy z juz pokolorowanymi sasiadami // (wpisujemy go do tablicy colors) // zwracamy liczbe uzytych kolorow int n = g.VerticesCount; colors = new int[n]; if (n == 0) { return(0); } int maxColorUsed = 1; colors[0] = 0; bool[] colorsUsedByNeighbors = new bool[n]; for (int v = 1; v < n; ++v) { for (int i = 0; i < n; i++) { colorsUsedByNeighbors[i] = false; } foreach (Edge e in g.OutEdges(v)) { if (e.To >= v) { continue; } colorsUsedByNeighbors[colors[e.To]] = true; } int lowestColorForV = -1; for (int i = 0; i < n; i++) { if (!colorsUsedByNeighbors[i]) { lowestColorForV = i; break; } } colors[v] = lowestColorForV; if (lowestColorForV > maxColorUsed) { maxColorUsed = lowestColorForV; } } return(maxColorUsed + 1); }
/// <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>Usuwanie wierzchołka z grafu</summary>\ /// <param name="g">Graf, z którego usuwamy wierzchołek</param> /// <param name="del">Usuwany wierzchołek</param> /// <returns>Graf z usunietym wierzchołkiem</returns> /// <remarks> /// 1.0 pkt. /// Metoda zwraca graf, będący kopią grafu wejściowego z usuniętym wierzchołkiem. /// Graf wejściowy pozostaje niezmieniony. /// W utworzonym grafie są takie same krawędzie jak w wejściowym /// (oczywiście z wyjątkiem incydentnych z usuniętym wierzchołkiem, numeracja wierzchołków zostaje zaktualizowana) /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga: W grafach nieskierowanych nie probować dodawawać po 2 razy tej samej krawędzi /// </remarks> public static Graph DeleteVertex(this Graph g, int del) { // Czy w ogole istnieje taki wierzcholek: if (del > g.VerticesCount) { return(g); } // Utworzenie grafu bez krawedzi na bazie wierzcholkow grafu g bez usuwanego: Graph ret = g.IsolatedVerticesGraph(g.Directed, g.VerticesCount - 1); HashSet <Edge> elist = new HashSet <Edge>(); // Uzupelnienie wynikowego grafu o krawedzie z uwzglednieniem przesuniecia: for (int i = 0; i < g.VerticesCount; i++) { // Omijamy krawedzie z usuwanego wierzcholka: if (i == del) { continue; } foreach (Edge e in g.OutEdges(i)) { // Omijamy krawedzie z usuwanego wierzcholka: if (e.To == del) { continue; } if (!elist.Contains(new Edge(e.To, e.From, e.Weight))) { if (i < del && e.To < del) { ret.AddEdge(i, e.To, e.Weight); } else if (i < del && e.To > del) { ret.AddEdge(i, e.To - 1, e.Weight); } else if (i > del && e.To < del) { ret.AddEdge(i - 1, e.To, e.Weight); } else { ret.AddEdge(i - 1, e.To - 1, e.Weight); } elist.Add(e); } } } return(ret); // zmienic ! }
/// <summary> /// Wyznacza minimalne drzewo rozpinające grafu algorytmem Boruvki /// </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.<para/> /// Jest to nieco zmodyfikowana wersja algorytmu Boruvki /// (nie ma operacji "sciągania" spójnych składowych w jeden wierzchołek). /// </remarks> /// <seealso cref="MSTGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static (double weight, Graph mst) Boruvka(this Graph g) { if (g.Directed) { throw new ArgumentException("Directed graphs are not allowed"); } var unionFind = new UnionFind(g.VerticesCount); var edgesMinPriorityQueue = new EdgesMinPriorityQueue(); var tree = g is AdjacencyMatrixGraph ? new AdjacencyListsGraph <AVLAdjacencyList>(false, g.VerticesCount) : g.IsolatedVerticesGraph(); var weight = 0.0; var change = true; while (change) { change = false; for (var i = 0; i < g.VerticesCount; i++) { Edge?edge = null; foreach (var e in g.OutEdges(i)) { if (unionFind.Find(i) != unionFind.Find(e.To) && (edge == null || e.Weight < edge.Value.Weight)) { edge = e; } } if (edge != null) { edgesMinPriorityQueue.Put(edge.Value); } } while (!edgesMinPriorityQueue.Empty) { var edge = edgesMinPriorityQueue.Get(); if (!unionFind.Union(edge.From, edge.To)) { continue; } tree.AddEdge(edge); weight += edge.Weight; if (tree.EdgesCount == g.VerticesCount - 1) { return(weight, tree); } change = true; } } return(weight, tree); }
/// <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> /// 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 Reverse(this Graph g) { if (g.Directed == false) { throw new System.ArgumentException("Graph should be directed"); } Graph ret = g.IsolatedVerticesGraph(); for (int i = 0; i < g.VerticesCount; i++) { foreach (Edge e in g.OutEdges(i)) { ret.AddEdge(e.To, e.From, e.Weight); } } return(ret); }
private bool AreEqual(Graph g1, Graph g2) { if (g1.VerticesCount != g2.VerticesCount) { return(false); } for (int i = 0; i < g1.VerticesCount; ++i) { var g1e = new HashSet <Edge>(g1.OutEdges(i)); var g2e = new HashSet <Edge>(g2.OutEdges(i)); if (!g1e.SetEquals(g2e)) { 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); }
/// <summary> /// Wyznacza odwrotność grafu /// </summary> /// <param name="g">Badany graf</param> /// <returns>Odwrotność grafu</returns> /// <exception cref="ArgumentException">Gdy uruchomiona dla grafu nieskierowanego</exception> /// <remarks> /// Odwrotność grafu to graf skierowany o wszystkich krawędziach /// przeciwnie skierowanych niż w grafie pierwotnym.<para/> /// Metoda uruchomiona dla grafu nieskierowanego zgłasza wyjątek <see cref="ArgumentException"/>. /// </remarks> /// <seealso cref="SCCGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static Graph Reverse(this Graph g) { if (!g.Directed) { throw new ArgumentException("Undirected graphs are not allowed"); } var graph = g.IsolatedVerticesGraph(); for (var i = 0; i < g.VerticesCount; i++) { foreach (var edge in g.OutEdges(i)) { graph.AddEdge(edge.To, edge.From, edge.Weight); } } return(graph); }
/// <summary>Dodawanie wierzchołka do grafu</summary> /// <param name="g">Graf, do którego dodajemy wierzchołek</param> /// <returns>Graf z dodanym wierzchołkiem</returns> /// <remarks> /// 0.5 pkt. /// Metoda zwraca graf, będący kopią grafu wejściowego z dodanym wierzchołkiem. /// Graf wejściowy pozostaje niezmieniony. /// W utworzonym grafie są takie same krawędzie jak w wejściowym. /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga: W grafach nieskierowanych nie probować dodawawać po 2 razy tej samej krawędzi /// </remarks> public static Graph AddVertex(this Graph g) { Graph ret = g.IsolatedVerticesGraph(g.Directed, g.VerticesCount + 1); HashSet <Edge> elist = new HashSet <Edge>(); for (int i = 0; i < g.VerticesCount; i++) { foreach (Edge e in g.OutEdges(i)) { if (!elist.Contains(e)) { ret.AddEdge(e); elist.Add(e); } } } return(ret); }
/// <summary>Usuwanie wierzchołka z grafu</summary> /// <param name="g">Graf, z którego usuwamy wierzchołek</param> /// <param name="del">Usuwany wierzchołek</param> /// <returns>Graf z usunietym wierzchołkiem</returns> /// <remarks> /// 1.0 pkt. /// Metoda zwraca graf, będący kopią grafu wejściowego z usuniętym wierzchołkiem. /// Graf wejściowy pozostaje niezmieniony. /// W utworzonym grafie są takie same krawędzie jak w wejściowym /// (oczywiście z wyjątkiem incydentnych z usuniętym wierzchołkiem, numeracja wierzchołków zostaje zaktualizowana) /// Utworzony graf ma taką samą reprezentację jak graf wejściowy. /// Uwaga: W grafach nieskierowanych nie probować dodawawać po 2 razy tej samej krawędzi /// </remarks> public static Graph DeleteVertex(this Graph g, int del) { int originalVerticesCount = g.VerticesCount; Graph newGraph = g.IsolatedVerticesGraph(g.Directed, originalVerticesCount - 1); for (int v = 0; v < originalVerticesCount; v++) { if (v == del) { continue; } foreach (Edge e in g.OutEdges(v)) { int fromVertex = e.From; int toVertex = e.To; if (toVertex == del) { continue; } if (fromVertex > del) { fromVertex -= 1; } if (toVertex > del) { toVertex -= 1; } // Podobnie jak przy dodawaniu, krawędź w grafach nieskierowanych dodajemy tylko raz if (!g.Directed && fromVertex > toVertex) { continue; } newGraph.AddEdge(fromVertex, toVertex); } } return(newGraph); }
/// <summary> /// Wyznacza jądro grafu /// </summary> /// <param name="g">Badany graf</param> /// <returns>Jądro grafu</returns> /// <remarks> /// Jądro grafu to graf skierowany, którego wierzchołkami są silnie spójne składowe pierwotnego grafu.<br/> /// Cała silnie spójna składowa jest "ściśnięta" do jednego wierzchiłka.<br/> /// Wierzchołki jądra są połączone krawędzią gdy w grafie pierwotnym połączone są krawędzią dowolne /// z wierzchołków odpowiednich składowych (ale nie wprowadzamy pętli !). Wagi krawędzi przyjmujemy równe 1.<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 Kernel(this Graph g) { if (g.Directed == false) { throw new System.ArgumentException("Graph should be directed"); } int[] scc; int n = g.StronglyConnectedComponents(out scc); Graph ret = g.IsolatedVerticesGraph(true, n); for (int i = 0; i < scc.Length; i++) { foreach (Edge e in g.OutEdges(i)) { if (scc[e.To] != scc[i]) { ret.AddEdge(scc[i], scc[e.To]); } } } return(g); }
// rekurencyjna metoda znajdujaca najlepsze pokolorowanie // v - wierzcholek do pokolorowania // colors - tablica kolorow // k - maksymalny kolor u¿yty w colors internal void Color(int v, int[] colors, int k) { if (v == n) { if (bestColorsNumber > k + 1) { for (int i = 0; i < n; i++) { bestColors[i] = colors[i]; } bestColorsNumber = k + 1; } return; } bool[] colorsUsedByNeighbors = new bool[n]; foreach (Edge e in g.OutEdges(v)) { if (e.To >= v) { continue; } colorsUsedByNeighbors[colors[e.To]] = true; } for (int i = 0; i < bestColorsNumber; i++) { if (colorsUsedByNeighbors[i]) { continue; } colors[v] = i; Color(v + 1, colors, Math.Max(i, k)); } }
/// <summary> /// /// </summary> /// <param name="g">Badany graf</param> /// <param name="from">Wierzchołek startowy</param> /// <param name="preVisit">Metoda wywoływana przy pierwszym wejściu do wierzchołka</param> /// <param name="postVisit">Metoda wywoływana przy ostatecznym opuszczaniu wierzchołka</param> /// <param name="visitedVertices">Tablica odwiedzonych wierzchołków</param> /// <returns>Informacja czy zbadano wszystkie wierzchołki osiągalne z wierzchołka startowego</returns> /// <exception cref="ArgumentException"></exception> /// <remarks> /// Metoda odwiedza jedynie te wierzchołki, dla których elementy tablicy visitedVertices mają wartość false /// (i zmienia wpisy w visitedVertices na true).<para/> /// Tablica visitedVertices musi mieć rozmiar zgodny z liczbą wierzchołków grafu g.<para/> /// Domyślna wartość null parametru visitedVertices oznacza tablicę wypełnioną /// wartościami false (zostanie ona utworzona wewnątrz metody).<para/> /// Metoda oczywiście odwiedza wierzchołki jednokrotnie, zwrot /// "ostateczne opuszczenie wierzchołka" oznacza moment, /// w którym mechamizm rekursji opuszcza wierzchołek.<para/> /// Parametrem metod preVisit i postVisit jest numer odwiedzanego wierzchołka.<para/> /// Wyniki zwracane przez metody preVisit i postVisit oznaczają /// żądanie kontynuowania (true) lub przerwania (false) obliczeń.<para/> /// Parametry preVisit i postVisit mogą mieć wartość null, co oznacza metodę pustą /// (nie wykonującą żadnych działań, zwracającą true).<para/> /// Jeśli zostały zbadane wszystkie wierzchołki osiągalne z wierzchołka startowego metoda zwraca true, /// jeśli obliczenia zostały przerwane przez metody związane z parametrami preVisit lub postVisit /// metoda zwraca false.<para/> /// Wynik true nie oznacza, że metoda odwiedziła wszystkie wierzchołki grafu /// (oznacza jedynie, że odwiedziła wszystkie wierzchołki osiągalne z wierzchołka startowego).<para/> /// Uwaga:<para/> /// Dla dużych grafów metoda może spowodować przepełnienie stosu /// (z powodu zbyt dużej liczby zagnieżdżonych wywołań rekurencyjnych).<para/> /// W takim przypadku można zastosować metodę /// <see cref="GeneralSearchGraphExtender.GeneralSearchAll{TEdgesContainer}"/> z wykorzystaniem /// kontenera <see cref="EdgesStack"/> (jawnego stosu).<para/> /// </remarks> /// <seealso cref="DFSGraphExtender"/> /// <seealso cref="ASD.Graphs"/> public static bool DFSearchFrom(this Graph g, int from, Predicate <int> preVisit, Predicate <int> postVisit, bool[] visitedVertices = null) { if (visitedVertices == null) { visitedVertices = new bool[g.VerticesCount]; } else if (visitedVertices.Length != g.VerticesCount) { throw new ArgumentException("Invalid visitedVertices length"); } if (visitedVertices[from]) { throw new ArgumentException("Start vertex is already visited"); } if (preVisit == null) { preVisit = i => true; } if (postVisit == null) { postVisit = i => true; } visitedVertices[from] = true; if (!preVisit(from)) { return(false); } return(!g.OutEdges(from).Any(edge => !visitedVertices[edge.To] && !DFSearchFrom(g, edge.To, preVisit, postVisit, visitedVertices)) && postVisit(from)); }