Пример #1
0
        /// <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 !
        }
Пример #2
0
        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}");
                }
            }
        }
Пример #3
0
        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);
                    }
                }
            }
        }
Пример #4
0
            /// <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);
        }
Пример #6
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        /// <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));
        }
Пример #10
0
        /// <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);
        }
Пример #11
0
        /// <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);
        }
Пример #12
0
        }          // 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);
        }
Пример #13
0
        /// <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 !
        }
Пример #14
0
        /// <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);
        }
Пример #15
0
        /// <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);
        }
Пример #16
0
        // 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;
        }
Пример #17
0
            /// <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);
            }
Пример #18
0
        /// <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
Пример #19
0
        /// <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);
        }
Пример #20
0
        /// <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 &lt;float.MinValue,float.MaxValue&gt;.<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));
        }
Пример #21
0
 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));
            }
Пример #23
0
        }  // 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
Пример #24
0
        /// <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 &lt;float.MinValue,float.MaxValue&gt;.
        /// </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));
        }
Пример #25
0
        /// <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 &lt;float.MinValue,float.MaxValue&gt;.<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));
        }
Пример #26
0
        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);
        }
Пример #28
0
        /// <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);
        }