The article substantiates the essential properties of the planning process (as a process for which information support is provided), information and computing support for the planning process and the information support system (as a system implementing information and computing support for the planning process), quantitative and qualitative criteria are given.
Key words: planning process, information and computing support, information support system.
Myakotin Alexander Viktorovich, doctor of technical sciences, professor, [email protected], Russia, St. Petersburg, Military Academy of Communications,
Burlakov Andrey Anatolyevich, candidate of military sciences, docent, burlakov38@gmail. com, Russia, St. Petersburg, Military Academy of Communications,
Morgunov Alexey Yakovlevich, candidate of military sciences, docent, [email protected], Russia, St. Petersburg, Military Academy of Communications,
Pitenko Valery Aleksandrovich, senior lecturer, [email protected], Russia, St. Petersburg, Military Academy of Communications., Russia, St. Petersburg, Military Academy of Communications,
Muravyev Alexander Ivanovich, lecturer, [email protected], Russia, St. Petersburg, Military Academy of Communications
УДК 519.688
DOI: 10.24412/2071-6168-2023-2-128-138
ВИЗУАЛИЗАЦИЯ НА ГРАФАХ А.О. Гюллинг, Н.В. Воронцова
Статья посвящена рассмотрению разработанного алгоритма и программы решения задачи визуализации на графах получения остовного дерева.
Ключевые слова: графы, визуализация, остовное дерево, язык программирования C#, платформа .NET Framework.
Задачи оптимизации, удобно представляемые и решаемые на графах, многочисленны. Из них можно выделить две, которые стали наиболее распространенными и даже классическими:
- Задачи оптимальных покрывающих деревьев (или же минимального остовного дерева)
- Задачи нахождения кратчайшего пути в графе (задача коммивояжёра)
Задача о минимальном остовном дереве (минимальном покрывающем дереве) более проста, но тем не менее всё так же значима [1]. Алгоритмы её решения могут использоваться для минимизации затрат в задачах календарного планирования, где вес ребра представляет время или трудоемкость выполнения производственной задачи, а сами задачи становятся вершинами. Также она может использоваться для строительства минимально затратной дорожной сети, или же в логистике крупных авиалиний.
В рамках теории графов задачу можно сформулировать следующим образом:
в связном взвешенном неориентированном графе найти подграф со всеми вершинами начального графа, соединенными минимальным количеством рёбер, сумма весов которых будет также минимальна (рис. 1).
Языком программирования стал C#, а платформой - .NET Framework. Это наиболее распространенная платформа для создания клиентских приложений на ОС Windows. Более того, интерфейсная технология WPF, являющаяся подсистемой .NET Framework, обладает отличным графическим инструментарием, который понадобится в ходе разработки. Её интерфейсные элементы управления очень гибки в надстройке [6].
Для нахождения минимального остовного дерева существуют два основных алгоритма. Это алгоритм Прима и алгоритм Краскала. Они одинаковы по алгоритмической сложности. В первом из них отбираются вершины, во втором - в первую очередь отбираются и сортируются рёбра.
Алгоритм Прима был выбран в качестве оптимального для нахождения минимального остовного дерева из-за простоты реализации, а также гибкости в разработке. Его эффективность зависит от применяемых программных способов хранения отобранных рёбер и вершин [2].
Наиболее оптимальный элемент управления WPF для реализации записи ребер в матрицу смежности - это TextBox. Таким образом, основной формат входных параметров программы будет реализован через динамическое создание матрицы из элементов TextBox, с добавлением соответствующего события TextChanged для отслеживания вводимых данных. Незаполненные пользователем ячейки матрицы смежности будут считаться отсутствием ребра, т.е. их вес будет равняться нулю. Это обеспечит дополнительное удобство при вводе данных.
Следует упомянуть и формат вводимых данных. Очевидно, что в значения матрицы смежности можно записывать только числа. В программе реализована полноценная динамическая блокировка ввода любого символа, отличного от цифры.
Также в программе реализовано отслеживание ячейки матрицы, на которую в данный момент времени наведён курсор мыши. Строка и столбец искомой ячейки динамически обновляются в специальном элементе управления, называемом StatusBar [5].
Когда ввод данных был реализован, необходимо разработать базовый функционал для отображения графа в программном окне. Элементы WPF легко настраиваемы, поэтому рисовать графические примитивы можно практически на любом элементе управления. В данном случае, оптимальным элементом управления для графического полотна был выбран контейнер Grid, который наиболее удобен для добавления в себя любых элементов, поскольку не структуризирует их.
Таким образом, необходимо написать класс, который займется преобразованием матрицы смежности в графовый вид, т.е. рисованием рёбер и вершин, а в последствие - и итоговых результатов алгоритмов. Этот класс получил название GraphVisible. В этом же классе необходимо реализовать и базовый функционал для изменения положения графа в пространстве. Ввиду удобства, вместе с ним будет создан элементарный класс Vertex, хранящий координаты вершин графа в x; y. Поскольку программа неразделима с алгоритмической частью, класс GraphVisible обязан работать в тесном тандеме с алгоритмическим классом GraphCore, в котором, и будут реализованы все алгоритмы, связанные с непосредственной обработкой данных графа [5].
Вместе с графом GraphCore также будет написан дочерний класс AlgorithmGraph. Он станет одним из приватных полей класса GraphCore и будет структурировать и хранить в себе все данные об итерациях того или иного алгоритма в основном классе. Схема разрабатываемого программного приложения показана на рис. 1.
Рис. 1. Упрощенная схема программы
Итак, необходимо выполнить поставленную задачу - визуализировать граф по его матрице смежности, также реализовав базовый способ управления графом в пространстве. Этим способом является перетаскивание выбранной вершины при помощи кнопки мыши. Соответственно, после вывода графа на экран, пользователь сможет отредактировать его положение вручную так, как посчитает нужным.
Следует так же отметить, что, несмотря на возможность управлять графом на выходе, также необходимо создать базовую укладку графа, при которой часть вершин не будет пересекаться. Для выполнения этой задачи, в классе GraphVisible был написан собственный алгоритм, смещающий все вершины графа в пространстве, на основе нескольких изменяемых коэффициентов. Алгоритм делит все вершины графа на некоторое количество строк, в зависимости от общего числа вершин, и смещает вершины построчно либо в левую сторону, либо в правую, при этом постоянно увеличивая общий коэффициент смещения. Такой подход позволяет уменьшить количество пересечений.
Код алгоритма выглядит следующим образом:
private void Vertex_Coord() {//Заполнение массива координат для укладки вершин
int count = 0, count_2 = 0, count_3 = 0, length_str = 2;
double x = 15.0, y = 15.0;
double app_x = 0, app_y = 0, app_sum_y = 3, size_coef = 1, count_length = 8; if (Graph_Size > 5) { app_sum_y = 8;
size_coef = 3; }
while (count < Graph_Size - count_length) {//Вычисление количества строк из вершин в зависимости от их количества length_str += 1;
count += 4;
}
count = 0;
for (int i = 0; i < Graph_Size; i++) {//Вычисление самих координат для каждой
вершины графа
if (count == 0) {
count = length_str;
app_x += 15; app_y = 0;
y += (60 * size_coef);
if (count_2 == 0) {
count_2 = 1;
x = (75 / size_coef) + app_x; } else { count_2 = 0;
x = (75 / size_coef) - app_x - 20; }
}
app_y += app_sum_y; x += (50 * size_coef); count--;
if (count_3 == 0) {//Занесение координат в класс, хранящий вершины
count_3 = 1;
VertexCoord[i].X = x;
VertexCoord[i].Y = y - app_y;
} else {
count_3 = 0;
VertexCoord[i].X = x;
VertexCoord[i].Y = y + app_y; }
} }
Результат работы этого алгоритма - избежание большого количества пересечений, которое бы возникло в случае квадратной ячеистой укладки.
Редактирование графа вручную для достижения нулевого количества пересечений рёбер стало возможным благодаря использованию событий WPF, отлавливающих нажатие, удержание и отпускание пользователем кнопки мыши, основной метод, для работы которых приведен ниже:
/*Добавление событий для перетаскивания вершин с помощью кнопки мыши*/ vertex_name.MouseDown += new MouseButtonEventHandler(Vertex_MouseDown); vertex_name.MouseMove += new MouseEventHandler(Vertex_MouseMove); vertex_name.MouseUp += new MouseButtonEventHandler(Vertex_MouseUp);
private void Mouse_Move(Point newPos) { /*Функция переноса графа во время манипуляций с мышью*/ var key = VertexSearch(Point_Global); Vertex_List[key].X1 += newPos.X - Point_Global.X; Vertex_List[key].X2 += newPos.X - Point_Global.X; Vertex_List[key].Y1 += newPos.Y - Point_Global.Y; Vertex_List[key].Y2 += newPos.Y - Point_Global.Y; Label_List[key].Margin = new Thickness(Vertex_List[key].X1 - 8, Vertex_List[key].Y1 - 12, 0, 0);
/*Перемещение ребер вслед за изменением координат вершин*/ if (Ribs_Index_List[key].Count != 0) {//Проверка на то, существуют ли ребра у перетаскиваемой вершины for (int i = 0; i < Ribs_Index_List[key].Count; i++) {
var k = Ribs_Index_List[key][i]Item1; var i_matrix = Ribs_Index_List[key][i].Item2; var j_matrix = Ribs_Index_List[key][i].Item3; Ribs_List[k].X1 = Vertex_List[i_matrix].X1; Ribs_List[k].X2 = Vertex_List[j_matrix].X1; Ribs_List[k].Y1 = Vertex_List[i_matrix].Y1; Ribs_List[k].Y2 = Vertex_List[j_matrix].Y1; Label_Weigher_List[k] = Average_point(Label_Weigher_List[k],
Ribs_List[k]); }
} }
Главной задачей в реализации перетаскивания вершин графа стала удобная форма представления рёбер в программном коде. Для этого была создана сложная структура, показанная на листинге и имеющая название Ribs_Index_List - это двумерный список из типа данных Tuple, который позволяет хранить сразу несколько значений одновременно. В этот список заносятся все рёбра графа [3]. Они нумеруются по связываемым вершинам, и затем легко вызываются в нужный момент, как было показано выше в листинге.
Реализовав базовый инструментарий для отображения графа в программе, необходимо перейти к реализации алгоритма.
Алгоритм Прима заключается в построении подграфа, веса рёбер которого будут минимальны. Его стартовая точка начинается с произвольной вершины, рёбра которой, ведущие к другим вершинам, перебираются на предмет наличия минимального веса. Как только в результате перебора находится ребро с минимальным весом, в базовый подграф, состоящий из первой случайно выбранной вершины, добавляется вторая - она связана с первой вершиной по искомому минимальному ребру. Подграф разрастается до тех пор, пока не станет включать в себя все вершины из начального графа. При этом рёбра, которые связывают вершины, уже находящиеся в подграфе, в итеративном переборе ребёр больше не участвуют. При анализе алгоритма Прима следует учесть очень важный аспект, который можно назвать решающим для понимания его работы - в его итерациях перебираются рёбра не какой-либо отдельной, крайней вершины из рассматриваемых, а абсолютно все рёбра строящегося подграфа, связанные с вершинами, еще не вошедшими в него.
Если разбить работу рассматриваемого алгоритма на шаги, то он работает следующим образом:
1. Происходит создание подграфа, в котором не существует вершин на момент старта алгоритма;
2. Выбирается произвольная вершина графа, которая добавляется в подграф из пункта 1, и становится, таким образом, его первой вершиной;
3. Перебираются все рёбра, исходящие из вершин искомого подграфа в другие вершины, которые еще не находятся в подграфе, на предмет наличия ребра с минимально возможным весом;
4. Когда ребро с минимальным весом находится, то вершина, которая связана этим ребром с вершиной подграфа, также добавляется в подграф;
5. Повтор пунктов 3 и 4 до тех пор, пока количество вершин в подграфе не станет равно количеству вершин в основном графе.
После анализа самого алгоритма, необходимо учесть то, что в программе должен быть представлен не только конечный результат его работы, но и возможность просмотра всех его итераций с необходимыми для этого пояснениями. Для этого был написан, а затем и использован, уже упомянутый выше дочерний класс AlgorithmGraph, в котором и будут структурированы все данные о каждой итерации. Загружаться в класс они будут непосредственно во время исполнения алгоритма в основном классе GraphCore.
Следующий листинг с комментариями демонстрирует весь код алгоритма минимального остовного дерева:
public List<List<int>> Get_SpanningTree() {
/*Выполнение алгоритма Прима для получения матрицы минимального
остовного дерева*/
int count = 1, min = 0, min2 = 0;
var spanning_matrix = new List<List<int>>();
var matrix_iter = new int [Size, Size];
var vertex_chek = new List<bool>();
var var = new List<int>();
vertex_chek.Add(true);
for (int i = 0; i < Size; i++) {
spanning_matrix.Add(new List<int>());
for (int j = 0; j < Size; j++) { spanning_matrix[i].Add(Matrix[i, j]);
matrix_iter[i, j] = Matrix[i, j]; }
vertex_chek.Add(false);
var.Add(0); }
spanning_matrix[0][0] = int.MaxValue;
while (count != spanning_matrix.Count) {
for (int i = 0; i < count; i++) {//Перебор всех вершин во
множестве минимального остовного подграфа
for (int j = 0; j < spanning_matrix.Count; j++)
{//Перебор их соседних вершин
if (spanning_matrix[var[i]][j] > 0) {//Проверка
на то, больше ли нуля вес ребра (сделано чтобы уже выделенные -1 ребра не попадали в выборку)
if (vertex_chek[j] == false) {//Проверка
на то, была ли вершина уже добавлена в минимальный остовный подграф matrix_iter[var[i], j] = -
2;//Пометка перебираемых рёбер для итерационного класса matrix_iter[j, var[i]] = -
2;//Пометка перебираемых рёбер для итерационного класса if (spanning_matrix[var[i]][j] <
spanning_matrix[min][min2]) {//Проверка на то, является ли ребро минимальным min = var[i]; min2 = j;
var[count] = min2;
matrix_iter[min, min2] = -3;//Пометка выбранного минимального ребра для итерационного класса
matrix_iter[min2, min] = -3;//Пометка выбранного минимального ребра для итерационного класса GraphDescr.Add_Iter(matrix_iter, Matrix[min, min2]);//Промежуточная итерация spanning_matrix[min][min2] = -1; spanning_matrix[min2][min] = -1; SetIterMatrix(spanning_matrix, ref matrix_iter); vertex_chek[min2] = true; min = 0; min2 = 0;
count++; }
GraphDescr.Add_Iter(spanning_matrix);//Финальная итерация готового алгоритма
return spanning_matrix; }
Для реализации оптимизационного алгоритма, в качестве основного типа данных будет использован тип данных List, являющийся списком. Способ хранения элементов списка в памяти компьютера позволяет ему без каких-либо проблем удалять из себя, или же добавлять в себя любой элемент, не нарушая при этом последовательности других элементов [4].
Матрица spanning_matrix, реализованная двумерным целочисленным списком, станет результатом работы алгоритма Прима. В этой системной матрице будут храниться рёбра, входящие в итоговое минимальное остовное дерево. Они помечены отрицательным весом, равным -1. Во время рисования графа, матрица spanning_matrix будет соотноситься с обычной матрицей смежности графа, и поэтому помеченные отрицательным весом рёбра сохранят свой реальный вес.
Когда все алгоритмы реализованы, необходимо разработать рандомизатор. Иными словами -собственный алгоритм, заполняющий матрицу смежности произвольными числами. Можно было бы пойти по простому пути, и заполнить все рёбра матрицы смежности целиком, то есть (n*n - n)/2 ячеек для неориентированного графа. Однако, алгоритм, рассматриваемый в программе, способнен работать и с не полностью связанными графами. Такую же возможность необходимо дать и пользователю. Мини-
132
мальное остовное дерево требует лишь связности графа - возможностью попасть из любой его вершины в любую другую.
Таким образом, в программе был реализован следующий алгоритм рандома для генерирования матрицы смежности, в случае выбора алгоритма Прима:
int r, r_ind, vecl, vec2, spawn_count; if (Matrix_Inp. Count < 8) { spawn_count = 2; } else {
spawn_count = 1; }
var vert = new List<int>(Matrix_Inp.Count); for (int i = 0; i < Matrix_Inp.Count; i++) {
vert.Add(i);
}
r_ind = rand.Next(0, vert.Count);//Рандом первой вершины
vecl = vert[r_ind];
vert.RemoveAt(r_ind);
while (vert.Count != 0) {
r_ind = rand.Next(0, vert.Count);
vec2 = vert[r_ind];
vert.RemoveAt(r_ind);
r = rand.Next(1, 100);
Matrix_Inp[vec1][vec2].Text = Convert.ToString(r);
Matrix_Inp[vec2][vec1].Text = Convert.ToString(r);
for (int i = 0; i < spawn_count; i++) {//Производство
дополнительных ребер
r_ind = rand.Next(0, Matrix_Inp.Count);
r = rand.Next(1, 100);
if (vecl != r_ind) {
Matrix_Inp[vec1][r_ind].Text = Convert.ToString(r);
Matrix_Inp[r_ind][vec1].Text = Convert.ToString(r);
}
}
vecl = vec2;//Зацикливание в связке со второй вершиной
}
} else { }
}
Этот алгоритм гарантированно генерирует путь, являющийся эейлеровым. При этом он в случайном порядке наращивает дополнительные ребра вокруг этого пути. Таким образом, почти всегда ран-домизатор порождает матрицу смежности графа, являющегося не полностью связным, но, тем не менее, достаточным для реализации в нём алгоритма Прима.
Единственный критерий, который необходим для его выполнения - связность графа. Для того, чтобы проверить введенную пользователем матрицу смежности на связность, необходимо разработать алгоритм, реализующий поиск в глубину графа - это рекурсивный алгоритм, точка старта которого -одна из вершин графа, от которой он попадает в другие вершины при помощи следующего рекурсивного вызова. Таким образом, используя рёбра той или иной вершины, алгоритм перебирает все n вершин графа, и если ему не удается попасть в одну из них к моменту окончания поиска - граф не является связным. Так выглядит реализация алгоритма поиска в глубину: public bool Graph_Connec_Chek() {
/*Проверка графа на связность для исполнения алгоритма минимального остовного дерева и коммивояжера*/ var list_vert = new List<int>(); list_vert.Add(0);
Graph_Connec_Chek_Rec(ref list_vert, 0);
if (list_vert.Count == Size) {
return true;
return false;
40
private void Graph_Connec_Chek_Rec(ref List<int> list_vert, int i) {
/*Рекурсивный алгоритм проверки на связность*/
for (int j = 0; j < Size; j++) {
if (Matrix[i, j] > 0) {
if (!list_vert.Contains(j)) {
list_vert.Add(j);
if (list_vert.Count != Size) {
Graph_Connec_Chek_Rec(ref list_vert, j); }
} } } }
Ранее говорилось, что однозначных признаков наличия гамильтонова цикла не существует. Однако, есть простая теорема Дирака, заявляющая о достаточности существования гамильтонова цикла в графе.
Теорема Дирака звучит так: если количество вершин в графе больше трёх, и наименьшая степень вершины в этом графе больше половины от количества всех вершин, граф однозначно может содержать гамильтонов цикл.
Её реализация в данной программе очень проста:
public bool Graph_Commi_Dirak_Chek() {
/*Проверка достаточности наличия гамильтоновых циклов в графе по признаку Дирака*/ int counter;
for (int i = 0; i < Size; i++) { counter = 0;
for (int j = 0; j < Size; j++) { if (Matrix[i,j] > 0) {
counter++; }
}
if (counter <= Size / 2) {
return false; }
}
return true; }
Теперь программа правильно интерпретирует любые входящие в неё данные, и запрещает вычисление алгоритмов по недопустимым для них матрицам смежности.
Необходимо удостовериться в корректности работы всех реализованных в ней оптимизационных графовых алгоритмов.
Следует отметить, что важной особенностью тестирования будет являться и проверка на отказоустойчивость программного продукта путем ввода критических или же некорректных входных данных.
В первую очередь, тестирование необходимо начать с запуска программы и ввода входных
данных.
Запуск программы и ввод входных данных. На рис. 2 можно увидеть пока что пустую графическую часть интерфейса справа и базовые настройки приложения слева. Это тип исполняемого алгоритма в выпадающем списке, способ ввода матрицы, а также выбор количества вершин искомого графа.
Уже на данном этапе можно наблюдать поэтапность настройки всех необходимых параметров - программа не даёт пользователю раньше времени предпринять какие-либо необдуманные меры, способные привести к некорректной работе приложения. Ввод любых значений в текстовое поле, кроме числовых, приводит к невозможности создания матрицы смежности.
Теперь будет произведен корректный ввод количества вершин. Выбор алгоритма остаётся прежним, «Рисование просто графа» - необходимо убедиться в корректности ввода данных и их последующей визуализации. На рис. 3 можно увидеть, что теперь матрица смежности - таблица из текстовых полей - действительно появилась, и готова к вводу числовых данных.
Ввод любых символов с клавиатуры, отличимых от цифр, не представляется возможным - программа блокирует их еще до появления на экране. Рандомизатор также работает успешно - все числа сгенерировались для заполнения матрицы.
33 йббУ
Файл Информация Настройка алгоритм
о алгоритма:
® Ввод матрицы вручную О Рандомизатор
Настройка размеров графа
Тизд!
Строка: не выбрана | Столбец: не выбран
3 Ш&/
Файл Информация Настройка алгоритмов
Тип исполняемого алгоритма: О Ввод матрицы вручную Настройка размеров графа
Рис. 2. Первичные настройки в приложении
® Рандомизатор
Повтор рандома
| Обнулит
трица смежности
¡46 46 60 0 69 46 54 44
0 47 0 44 50 0
60 0 34 0 33 90 0
0 47 34 80 10 47 34
69 0 0 80 0 0 61
46 44 33 10 0 0 0
54 50 90 47 0 0 65 75
0 0 0 0 0 0 65 0
44 0 0 34 61 0 75
Строка: 4 | Столбец: 5
Рис. 3. Ввод матрицы смежности в программу
Идентификация каждой ячейки в строке состояния работает также корректно - ячейка в четвертой строке и пятом столбце действительно содержит в себе на данный момент курсор мыши.
После нажатия кнопки «Построить граф», левая часть окна программы преображается - выводится информация о запущенном алгоритме, список его итераций (на данный момент ни один алгоритм не был запущен, поэтому итераций не существует, модуль заблокирован), а также происходит визуализация данных из матрицы смежности в виде неориентированного графа, веса ребер и вершины которого помечены корректными значениями (рис. 4).
Перетаскивание вершин для их «распутывания» работает в реальном времени - ребра выбранной вершины подсвечиваются красным для их однозначной визуальной идентификации. На рис. 5 тот же граф обработан вручную и имеет совершенно другой вид. В строке состояния номер вершины и её степень также указаны корректно - вершина с номером 2 действительно имеет 4 ребра.
Тестирование алгоритма. Минимальное остовное дерево (рис. 6). Матрица смежности была заполнена при помощи рандомизатора, но из неё были удалены в случайном порядке некоторые данные, приведшие граф к состоянию несвязности. Программа не дала запустить алгоритм, сообщив о том, что граф не является связным - это доказывает корректность работы рекурсивного поиска в глубину графа, реализованного в программе как раз для проверки вводимых пользователем данных.
Итчр»н* ем5рл<.л.
Рис. 4. Визуализация данных из матрицы смежности
Рис. 5. «Распутывание» нарисованного графа вручную
Рис. 6. Сообщение о некорректно введенной матрице смежности
На рис. 7 производится ввод данных матрицы смежности в онлайн-сервис graphonline.ru, затем в пункте «Алгоритмы» был выбран поиск минимального остовного дерева и получен результат. Тот же результат был получен и при вводе идентичной матрицы смежности в разработанную программу. Выбор итераций доступен, минимальное остовное дерево подсвечено. Программа успешно выполнила свою задачу
& Graph Online Работ* «графами еняам»
О
Рис. 7. Проверка работоспособности алгоритма
Подводя итоги, можно утверждать, что в ходе проверки программного обеспечения была подтверждена его работоспособность, соответствие требованиям технического задания, и общая готовность к работе.
Список литературы
1. Акимов О.Е. Дискретная математика: логика, группы, графы. Лаборатория Базовых знаний, 2003. 376 с.
2. Фляйшнер Г. Эйлеровы графы и смежные вопросы. Мир, 2002. 335 с.
3. Левитин А.В. Алгоритмы. Введение в разработку и анализ. М.: Вильямс, 2006. С. 159-160.
4. Петров В.Н. Информационные системы. СПб.: Питер, 2002.
5. Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы. Введение в разработку и анализ. М.: Вильямс, 2005. 1296 с.
6. Мак-Доналд М., Лейзерсон Ч., Ривест Р. WPF: Windows Presentation Foundation в .NET 4.5 с примерами на C# 5.0 для профессионалов. М.: Вильямс, 2013. 261 с.
Гюллинг Андрей Олегович, магистрант, gyulling@mail. ru, Россия, Тула, Тульский государственный педагогический университет им. Л.Н. Толстого,
Воронцова Наталья Вадимовна, канд. техн. наук, доцент, [email protected], Россия, Тула, Тульский государственный педагогический университет им. Л.Н. Толстого
VISUALIZATION ON GRAPHS
A.O. Gyulling, N.V. Vorontsova
The article is devoted to the consideration of the developed algorithm and program for solving the visualization problem on graphs for obtaining a spanning tree.
Key words: graphs, visualization, spanning tree, C# programming language, .NET Framework.
Gyulling Andrey Olegovich, master, [email protected], Russia, Tula, Tula State Pedagogical University. L.N. Tolstoy,
Vorontsova Natalia Vadimovna, candidate of technical sciences, docent, [email protected], Russia, Tula, Tula State Pedagogical University. L.N. Tolstoy