/// <summary> /// Метод запуска дополнительного потока по аугментальной цепи /// </summary> /// <param name="graph">Граф (сеть)</param> /// <param name="augmentalPath">Аугментальный путь в виде массива ID вершин</param> /// <param name="augmentalFlow">Величина увеличения потока в этой цепи</param> public void StartAugmentalFlow(EduGraph graph, int[] augmentalPath, int augmentalFlow) { // Идём со второй вершины, и смотрим на предшествующую ей дугу for (int curVertexIndex = 1; curVertexIndex < augmentalPath.Length; curVertexIndex++) { // ID текущей вершины int curVertexID = augmentalPath[curVertexIndex]; // ID предыдущей вершины int prevVertexID = augmentalPath[curVertexIndex - 1]; // Выясняем, по какой дуге мы прошли - прямой, или обратной bool isForwardEdge; // прямая ли дуга if (graph[prevVertexID, curVertexID] != null) { isForwardEdge = true; } else { isForwardEdge = false; } // Пускаем поток по этой дуге if (isForwardEdge) { graph[prevVertexID, curVertexID].Flow += augmentalFlow; } else { graph[curVertexID, prevVertexID].Flow -= augmentalFlow; } } }
/// <summary> /// Метод получения аугментальной цепи между заданными вершинами сети. /// Возвращаемый массив содержит искомую цепь в виде последовательности ID вершин /// </summary> /// <param name="graph">Сеть (граф)</param> /// <param name="startVertex">Начальная вершина цепи</param> /// <param name="finishVertex">Конечная вершина цепи</param> /// <param name="visitedVertices">Массив, содержащий пометки обо всех посещённых вершинах</param> /// <returns></returns> public int[] GetAugmentalPath(EduGraph graph, int startVertex, int finishVertex, bool[] visitedVertices) { Stack <int> stackPath = new Stack <int>(); BuildAugmentalPath(graph, startVertex, finishVertex, new bool[graph.VerticesInfo.Count], visitedVertices, stackPath); int[] augmentalPath = GetAugmentalPathFromStack(stackPath); return(augmentalPath); }
/// <summary> /// Метод инициализации элемента управления EduGraphVisualizer. /// Элемент управления должен быть инициализирован перед своим использованием. /// Также метод позволяет сменить визуализируемый граф /// </summary> /// <param name="visualizingGraph">Граф, который необходимо визуализировать</param> /// <param name="interactiveMode">Флаг, включающий интерактивный режим элемента управления</param> public void Initialize(EduGraph visualizingGraph, bool interactiveMode = false) { // Инициализируем поля контрола selectedVertex = null; selectedEdge = null; verticesMarkingColors = new Dictionary <VertexInfo, Color>(); edgesMarkingColors = new Dictionary <EdgeInfo, Color>(); InteractiveMode = interactiveMode; // Сохраняем информацию о графе Graph = visualizingGraph; // Создаём под изображение битмап, чтобы оно не стиралось после перерисовки Image = new Bitmap(Width, Height); // Рисуем граф и запрашиваем перерисовку контрола DrawGraph(); }
/// <summary> /// Поиск максимального потока /// </summary> /// <param name="graph">Граф (сеть)</param> /// <param name="startVertex">Начальная вершина цепи</param> /// <param name="finishVertex">Конечная вершина цепи</param> /// <param name="divisionOfVertices">Разбиение множества вершин на два - для построения минимального разреза</param> /// <param name="egVis">Элемент управления визуализации графа. В случае null визуализация отсутствует</param> /// <returns></returns> public int FindMaximalFlow(EduGraph graph, int startVertex, int finishVertex, out List <EdgeInfo> minimalCut, EduGraphVisualizer egVis = null) { int maximalFlow = 0; // искомая величина максимального потока bool[] divisionOfVertices = new bool[graph.VerticesCount]; // разбиение множества вершин на два подмножества (true/false) - для нахождения разреза int[] augmentalPath; // Пока у нас есть аугментальная цепь while ((augmentalPath = GetAugmentalPath(graph, startVertex, finishVertex, divisionOfVertices)) != null) { int augmentalFlow = GetAugmentalFlow(graph, augmentalPath, egVis); // Визуализация => засыпаем на время if (egVis != null) { Thread.Sleep(750); } StartAugmentalFlow(graph, augmentalPath, augmentalFlow); // Визуализация => Рисуем изменения if (egVis != null) { egVis.DrawGraph(); egVis.Update(); } // Визуализация => засыпаем на время, чистим метки if (egVis != null) { Thread.Sleep(1500); graph.ClearVerticesOutsideLabels(); egVis.ClearEdgesMarking(); egVis.ClearVerticesMarking(); egVis.DrawGraph(); egVis.Update(); Thread.Sleep(1000); } // Пересоздаём массив посещённых вершин к следующему разу. Если вдруг цепь не найдётся - он сохранится divisionOfVertices = new bool[graph.VerticesCount]; } // Суммируем исходящий из истока поток - это и будет максимальным потоком foreach (var vertexEdge in graph.Matrix[startVertex].Values) { maximalFlow += vertexEdge != null ? vertexEdge.Flow : 0; } // Находим минимальный разрез minimalCut = BuildMinimalCut(graph, divisionOfVertices); return(maximalFlow); }
/// <summary> /// Метод поиска аугментальной цепи между заданными вершинами сети. /// Результат возвращается в stackPath в обратном порядке /// </summary> /// <param name="graph">Сеть (граф)</param> /// <param name="curVertexID">Начало аугментальной цепи</param> /// <param name="findingVertexID">Конец аугментальной цепи</param> /// <param name="verticesInPath">Вспомогательный массив для пометки вершин, включённых в цепь</param> /// <param name="visitedVertices">Массив для пометки когда-либо посещённых вершин (для построения первого множества для разреза)</param> /// <param name="stackPath">Стек, в который добавляются вершины по мере формирования пути. Содержит результат в обратном порядке</param> private void BuildAugmentalPath(EduGraph graph, int curVertexID, int findingVertexID, bool[] verticesInPath, bool[] visitedVertices, Stack <int> stackPath) { // Помечаем вершину как посещённую visitedVertices[curVertexID - 1] = true; // Берём текущую вершину в стоящийся путь verticesInPath[curVertexID - 1] = true; stackPath.Push(curVertexID); // Если текущая вершина искомая - выходим if (curVertexID == findingVertexID) { return; } // Ищем следующую вершину - проверяем прямые рёбра for (int nextVertexID = 1; nextVertexID <= graph.VerticesInfo.Count; nextVertexID++) { // Проверяем прямое ребро: есть, не взято, допускает увеличение потока if (graph[curVertexID, nextVertexID] != null && !verticesInPath[nextVertexID - 1] && graph[curVertexID, nextVertexID].GetAugmentalFlow() > 0) { BuildAugmentalPath(graph, nextVertexID, findingVertexID, verticesInPath, visitedVertices, stackPath); } // Проверим, не закончен ли поиск if (stackPath.Peek() == findingVertexID) { return; } // Проверяем обратное ребро: есть, не взято, допускает уменьшение потока if (graph[nextVertexID, curVertexID] != null && !verticesInPath[nextVertexID - 1] && graph[nextVertexID, curVertexID].Flow > 0) { BuildAugmentalPath(graph, nextVertexID, findingVertexID, verticesInPath, visitedVertices, stackPath); } // Проверим, не закончен ли поиск if (stackPath.Peek() == findingVertexID) { return; } } // Убираем текущую вершину из строящегося пути, если она не искомая (если сверху искомая - мы нашли путь) if (stackPath.Peek() != findingVertexID) { verticesInPath[curVertexID - 1] = false; stackPath.Pop(); } }
/// <summary> /// Метод получения аугментальной цепи между заданными вершинами сети. /// Возвращаемый массив содержит искомую цепь в виде последовательности ID вершин /// </summary> /// <param name="graph">Сеть (граф)</param> /// <param name="startVertex">Начальная вершина цепи</param> /// <param name="finishVertex">Конечная вершина цепи</param> /// <param name="visitedVertices">Массив, содержащий пометки обо всех посещённых вершинах</param> /// <returns></returns> private int[] GetAugmentalPath(EduGraph graph, int startVertex, int finishVertex, bool[] visitedVertices) { Stack <int> stackPath = new Stack <int>(); BuildAugmentalPath(graph, startVertex, finishVertex, new bool[graph.VerticesInfo.Count], visitedVertices, stackPath); int[] augmentalPath; // Если путь есть, инвертируем его из стека. Иначе - null if (stackPath.Count != 0) { augmentalPath = new int[stackPath.Count]; for (int i = stackPath.Count - 1; i >= 0; i--) { augmentalPath[i] = stackPath.Pop(); } } else { augmentalPath = null; } return(augmentalPath); }
/// <summary> /// Построение минимального разреза из массива разбиения множества вершин /// </summary> /// <param name="graph">Граф (сеть)</param> /// <param name="divisionOfVertices">Массив разбиения множества вершин</param> /// <returns></returns> public List <EdgeInfo> BuildMinimalCut(EduGraph graph, bool[] divisionOfVertices) { List <EdgeInfo> res = new List <EdgeInfo>(); int verticesCount = graph.VerticesCount; // Составляем списки из индексов вершин обоих подмножеств List <int> firstDivision = new List <int>(); List <int> secondDivision = new List <int>(); for (int i = 0; i < verticesCount; i++) { if (divisionOfVertices[i]) { firstDivision.Add(i + 1); } else { secondDivision.Add(i + 1); } } // Добавляем в разрез дуги, соединяющие разбиение foreach (var firstDivVertexID in firstDivision) { foreach (var secondDivVertexID in secondDivision) { if (graph[firstDivVertexID, secondDivVertexID] != null) { res.Add(graph[firstDivVertexID, secondDivVertexID]); } else if (graph[secondDivVertexID, firstDivVertexID] != null) { res.Add(graph[secondDivVertexID, firstDivVertexID]); } } } return(res); }
/// <summary> /// Конструктор формы /// </summary> /// <param name="graphStruct">Структура, содержащая информацию о графе-примере</param> public TaskForm(GraphStruct graphStruct) { InitializeComponent(); // Создаём по данным графа объект EduGraph Graph = new EduGraph(graphStruct.adjacencyMatrix, graphStruct.verticesCoordinates); // Инициализируем статическую визуализацию eGraphViz.Initialize(Graph, isEdgesMarksVisible: false, isArrowsVisible: false); // Задаём общие подсказки для элементов управления ttCommonTip.SetToolTip(bClearAll, "Сбросить текущее решение и начать заново"); ttCommonTip.SetToolTip(bLecture, "Открыть текст лекции"); ttCommonTip.SetToolTip(bDoStep, "Приступить к следующей итерации решения"); // Задаём текст подсказки tbTip.Text = @"- Добро пожаловать! - В данном окне вы можете просмотреть демонстрацию алгоритма поиска максимального потока. - В разделе ""Настройки"" вы можете выбрать номера вершин, между которыми хотите пустить максимальный поток. - В разделе ""Решение"" находятся кнопки, позволяющие управлять демонстрацией. Как только максимальный поток будет найден, синим цветом на графе будет выделен минимальный разрез. Максимальный поток в данном случае будет текущим потоком в графе. -В разделе ""Помощь"" вы можете открыть текст лекции. "; // Выделяем общую кнопку, чтобы после изменения текста tbTip он не был выделен bDoStep.Select(); }
/// <summary> /// Метод нахождения величины дополнительного потока, который можно /// пустить по аугментальной цепи /// </summary> /// <param name="graph">Граф (сеть)</param> /// <param name="augmentalPath">Аугментальный путь в виде последовательности ID вершин</param> /// <param name="egVis">Элемент управления визуализации графа. В случае null визуализация отсутствует</param> /// <returns></returns> public int GetAugmentalFlow(EduGraph graph, int[] augmentalPath, EduGraphVisualizer egVis = null) { int augmentalFlow = int.MaxValue; // искомое значение аугментального потока int augmentalPathLength = augmentalPath.Length; for (int curVertexIndex = 0; curVertexIndex < augmentalPathLength; curVertexIndex++) { // Визуализация => засыпаем на время if (egVis != null) { Thread.Sleep(400); } // ID текущей вершины int curVertexID = augmentalPath[curVertexIndex]; // Визуализация => отмечаем текущую вершину if (egVis != null) { egVis.MarkVertex(graph[curVertexID], Color.Red); egVis.Update(); } // Если вершина первая - просто ставим метку и идём к следующей вершине if (curVertexIndex == 0) { // Визуализация => ставим метку if (egVis != null) { // Засыпаем на время Thread.Sleep(400); graph[curVertexID].OutsideLabel = $"(+{curVertexID};inf)"; egVis.DrawGraph(); egVis.Update(); } continue; } // ID предыдущей вершины int prevVertexID = augmentalPath[curVertexIndex - 1]; // Выясняем, по какому ребру мы прошли - прямому, или обратному bool isForwardEdge; // прямое ли ребро if (graph[prevVertexID, curVertexID] != null) { isForwardEdge = true; } else { isForwardEdge = false; } // Визуализация => отмечаем дугу, по которой пришли if (egVis != null) { if (isForwardEdge) { egVis.MarkEdge(graph[prevVertexID, curVertexID], Color.Red); } else { egVis.MarkEdge(graph[curVertexID, prevVertexID], Color.Red); } egVis.Update(); } // Выясняем, какой поток можно пропустить по пути, которым мы прошли if (isForwardEdge) { augmentalFlow = Math.Min(augmentalFlow, graph[prevVertexID, curVertexID].GetAugmentalFlow()); } else { augmentalFlow = Math.Min(augmentalFlow, graph[curVertexID, prevVertexID].Flow); } // Визуализация => ставим метку на эту вершину if (egVis != null) { // Определяем знак в метке, который ставится перед предыдущей вершиной char sign = isForwardEdge ? '+' : '-'; // Засыпаем на время Thread.Sleep(400); // Ставим на вершину метку graph[curVertexID].OutsideLabel = $"({sign}{prevVertexID};{augmentalFlow})"; egVis.DrawGraph(); egVis.Update(); } } return(augmentalFlow); }
public Test() { InitializeComponent(); // Данные тестового графа 1 //int[,] adjacencyMatrix = { // {0, 15, 0, 25, 0, 0, 0 }, // {0, 0, 16, 7, 0, 0, 0 }, // {0, 0, 0, 0, 4, 0, 9 }, // {0, 0, 0, 0, 18, 3, 0 }, // {0, 0, 0, 0, 0, 6, 25 }, // {0, 2, 3, 0, 0, 0, 0 }, // {0, 0, 0, 0, 0, 0, 0 } //}; //PointF[] verticesCoordinates = { // new PointF(50, 200), new PointF(200, 50), new PointF(350, 50), new PointF(200, 350), // new PointF(350, 350), new PointF(275, 200), new PointF(500, 200)}; //graphID = 1; // Данные тестового графа 2 //int[,] adjacencyMatrix = { // {0, 7, 0, 0, 5, 0, 3, 0 }, // {0, 0, 2, 0, 0, 0, 0, 3 }, // {0, 0, 0, 6, 0, 0, 0, 0 }, // {0, 0, 0, 0, 0, 0, 0, 0 }, // {0, 0, 2, 0, 0, 6, 0, 0 }, // {0, 0, 0, 4, 0, 0, 0, 1 }, // {0, 0, 1, 0, 2, 0, 0, 6 }, // {0, 0, 0, 5, 0, 0, 0, 0 } //}; //PointF[] verticesCoordinates = { // new PointF(50, 200), new PointF(250, 50), new PointF(450, 50), new PointF(650, 200), // new PointF(180, 200), new PointF(500, 200), new PointF(250, 400), new PointF(450, 400)}; //graphID = 2; // Данные тестового графа 3 int[,] adjacencyMatrix = { { 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 1, 0, 1, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 1, 0, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, { 1, 0, 1, 0, 1, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }; PointF[] verticesCoordinates = { new PointF(200, 50), new PointF(350, 50), new PointF(200, 150), new PointF(350, 150), new PointF(200, 250), new PointF(350, 250), new PointF(200, 350), new PointF(350, 350), new PointF(50, 175), new PointF(500, 175) }; graphID = 3; EduGraph testGraph = new EduGraph(adjacencyMatrix, verticesCoordinates); graph = testGraph; //MessageBox.Show("Ура!"); egViz.Initialize(graph, true); // инициализируем интерактивный контрол визуализации // Подписка на события egViz.VertexSelectedEvent += TestVertexMarking; egViz.EdgeSelectedEvent += TestEdgeMarking; }