Назад Вперед Зміст

Проста візуалізація графа

Дерева та Графи

Графом називається набір точок (ці точки називаються вершинами), деякі з яких оголошуються суміжними (або сусідніми). Вважається, що суміжні вершини з'єднані між собою ребрами (або дугами). Ребро визначається парою вершин. Два ребра, у яких є спільна вершина, також називаються суміжними (або сусідніми).

Граф

Граф називається орієнтованим (або орграфом), якщо деякі ребра мають напрямок. Це означає, що в орграфі деяка вершина може бути з'єднана з іншою вершиною, але зворотного з'єднання немає. Геометрично граф часто зображують точками площини, причому сусідні вершини з'єднані дугами (для орграфа деякі дуги мають напрямок, що зазвичай позначають стрілкою).

Граф називається зваженим або навантаженим, якщо кожному ребру відповідає певне число.

Маршрут у графі – це послідовність суміжних вершин. Очевидно, що маршрут можна визначити і як послідовність суміжних ребер (у цьому випадку ребра набувають напрямку). Зауважимо, що в маршруті можуть повторюватися вершини, але не ребра. Маршрут називається циклом, якщо його перша вершина збігається з останньою.

Граф називається деревом, якщо він зв'язаний і не має циклів.

Властивості дерев

  1. Будь-яка пара вершин з'єднана єдиним маршрутом.
  2. Кількість ребер на одиницю менша за кількість вершин.
  3. Видалення хоча б одного ребра не порушує його структуру.
  4. Якщо до дерева додати хоча б одне ребро, з'явиться цикл.

Дерево називається деревом з коренем, якщо одна вершина виділена і розташована вище за інші.

Вершини, розташовані під однією вершиною, називаються її синами, а сама вершина – батьком.

Вершини, які не мають синів, називаються листям.

Вершини, відмінні від кореня та листя, називаються внутрішніми.

Дерево, коренем якого є одна з вершин даного дерева, називається піддеревом.

Шлях у графі (іноді кажуть простий шлях) – це маршрут без повторення вершин (а отже, і ребер).

Найкоротшим шляхом ми називатимемо шлях, якщо: ці вершини з'єднані мінімальною кількістю ребер (у випадку, якщо граф незважений); сума ребер, що з'єднують ці вершини, мінімальна (для зваженого графа).

Контур (остов, стягуюче дерево) – це цикл без повторення вершин, за винятком першої вершини, яка збігається з останньою. Матриця суміжності графа – це матриця, значення елементів якої характеризують суміжність вершин графа. При цьому значенню елемента матриці відповідає кількість ребер, які з'єднують відповідні вершини.

Шлях по вершинах і ребрах графа, у який будь-яке ребро графа входить не більше одного разу, називається ланцюгом. Ланцюг, початкова та кінцева вершини якого збігаються, називається циклом.

Кількість ребер, що виходять з вершини графа, називається степенем вершини.

Граф

Вершина графа, яка має непарний степінь, називається непарною, а парний степінь – парною.

Ейлер встановив, що:

  1. Число непарних вершин (вершин, до яких веде непарна кількість ребер) графа має бути парним або не може існувати граф, який мав би непарну кількість непарних вершин.
  2. Умова існування в графі ейлерового циклу. Якщо всі вершини графа парні, то можна, не відриваючи олівця від паперу, накреслити граф, при цьому можна починати з будь-якої вершини графа і завершити його в тій же вершині.
  3. Умова існування в графі ейлерового ланцюга. Граф із двома непарними вершинами можна накреслити одним росчерком, з'єднавши дві вершини.

Задача про кенігсберзькі мости (нім. Königsberger Brückenproblem)

Граф

Найвідоміша головоломка, придумана у XVIII столітті. Називається вона задача про сім кенігсберзьких мостів. У Кенігсберзі, починаючи з XIV століття, було побудовано 7 мостів: Медовий міст, Крамничний міст, Зелений міст, Робочий міст, Ковальський міст, Дерев'яний міст і Високий міст, які з'єднували острів і півострови в єдине місто.

Тоді й виникла головоломка: «Як пройти по всіх мостах, не проходячи жоден з них двічі?»

Відповідь: "пройти лише один раз по кожному мосту неможливо".

Властивості графів:

  1. У будь-якому графі є, принаймні, дві вершини, які мають однаковий степінь.
  2. Для будь-якого графа кількість вершин непарного степеня завжди буде парною.
  3. Сума степенів усіх вершин графа дорівнює подвоєній кількості його ребер.

Способи задання графів

Можливі кілька різних способів задання графа. Ми розглянемо лише один, який використовуватимемо у нашому проекті.

За допомогою матриці суміжності. Матрицею суміжності неорієнтованого розміченого графа з вершинами називається матриця

А = [Xij], i,j = 1, ..., n, рядки якої відповідають вершинам, а стовпці – ребрам, у якій:

Aij = 1, якщо існує ребро (Xi, Xj);

Aij = 0, якщо вершини Xi, Xj не пов'язані ребром.

Приклад 1. Для графа, зображеного на рисунку 1, складемо матрицю суміжності.

Граф (приклад 1)

Матриця суміжності для графа, зображеного на рис. 1

A

B

C

D

E

A

0

1

0

0

1

B

1

0

1

1

1

C

0

1

0

1

0

D

0

1

1

0

1

E

1

1

0

1

0

Матриця суміжності однозначно визначає структуру графа. Зауважимо, що петля в матриці суміжності може бути представлена відповідним діагональним елементом. Кратні ребра можна представити, дозволивши елементу матриці бути більшим за 1, але це не прийнято.

Приклад неорієнтованого графа (приклад 2)

Матриця суміжності для графа, зображеного на рис. 2

A

B

C

D

E

A

0

1

1

1

1

B

1

0

0

0

1

C

1

0

0

1

0

D

1

0

1

0

0

E

1

1

0

0

0

Проект «Найпростіша візуалізація графа»

Почнемо з найпростішого. На формі позначимо вершини майбутнього графа, задамо матрицю суміжності та побудуємо неорієнтований граф.

Створили проект C# і додали на форму об'єкт PictureBox. Для активації події onPaint() слід використовувати команду Invalidate(). Ця команда також очищає форму від намальованих фігур.

  1. Створили проект C# і додали на форму компоненти PictureBox, два dataGridView для вершин і для матриці суміжності, меню та кнопки
  2. Будуємо нуль-граф:
  3. Заповнюємо матрицю суміжності:
  4. Будуємо граф:
  5. Глобальні змінні:
namespace GraphViz
{
    public partial class Form1 : Form
    {
        #region Глобальні змінні

        public BackgroundWorker backgroundWorker1; // Фоновий воркер для обчислень
        public List points = new List(); // Список вершин графа
        Point F = new Point(0, 0); // Тимчасова точка (увага: '@' замінено на 0, оскільки це помилка)
        public Graphics G; // Екземпляр класу Graphics для малювання
        public Bitmap myBMP; // Бітова карта для малювання
        Pen Pen = new Pen(Color.Green, 3); // Зелений пензель товщиною 3 пікселі
        public static int X, Y, XX, YY; // Координати для роботи з графом
        public int[,] matr; // Матриця суміжності графа
        bool flag = false; // Прапорець для роботи з інтерфейсом

        #endregion
  • Код проекта:
    
    public Form1()
    {
        InitializeComponent();
        backgroundWorker1 = new BackgroundWorker(); // Ініціалізація фонового потоку
        myBMP = new Bitmap(pictureBox1.Width, pictureBox1.Height); // Створення растрового зображення
        G = Graphics.FromImage(myBMP); // Графічний контекст для малювання
        points.Clear(); // Очищення списку вершин
        dataGridView1.Columns.Clear(); // Очищення таблиці
        dataGridView1.Columns.Add("Vertices", "Вершини графа"); // Додавання стовпця
        dataGridView1.Columns[0].Width = 75; // Ширина стовпця
    }
    #region Малювання
    /// 
    /// Відображає вершини графа у вигляді зелених кіл
    /// 
    public void DrawPoints()
    {
        Brush vertexBrush = new SolidBrush(Color.Green); // Кисть для заливки вершин
        foreach (Point vertex in points)
        {
            // Малюємо коло з центром у точці вершини (з урахуванням перетворення координат)
            G.FillEllipse(vertexBrush, vertex.X - 8, -vertex.Y + 400 - 8, 16, 16);
        }
        pictureBox1.Image = myBMP; // Оновлюємо зображення
    }
    #endregion
    /// 
    /// Обчислює відстань між двома точками
    /// 
    /// Перша точка
    /// Друга точка
    /// Відстань між точками
    public double DistanceBetweenPoints(Point A, Point B)
    {
        return Math.Sqrt(Math.Pow((A.X - B.X), 2) + Math.Pow((A.Y - B.Y), 2));
    }
    
  • Код проекта.Створюємо та додаємо нові вершини мишею на pictureBox1:
    
    private void pictureBox1_Click(object sender, EventArgs e)
    {
        // Обчислюємо координати кліку з урахуванням розташування елементів форми
        int clickX = MousePosition.X - this.Location.X - pictureBox1.Location.X - 8;
        int clickY = -MousePosition.Y + this.Location.Y + 78 + 400;
        
        // Створюємо кисть для малювання вершин
        Brush vertexBrush = new SolidBrush(Color.Green);
        
        // Малюємо вершину у вигляді зеленого кола
        G.FillEllipse(vertexBrush, clickX - 8, -clickY + 400 - 8, 16, 16);
        
        // Створюємо та додаємо нову вершину до списку
        Point newVertex = new Point(clickX, clickY);
        points.Add(newVertex);
        
        // Додаємо запис про вершину у таблицю DataGridView
        dataGridView1.Rows.Add($"({clickX}, {clickY})");
        
        // Оновлюємо зображення
        pictureBox1.Image = myBMP;
    }
    
  • Код проекта.Рисуем ребра:
    
    /// 
    /// Малює ребра графа на основі матриці суміжності
    /// 
    public void DrawEdges()
    {
        // Налаштування пера для малювання ребер
        Pen edgePen = new Pen(Color.DarkViolet, 2);
        
        // Ініціалізація матриці суміжності
        matr = new int[points.Count, points.Count];
        
        // Заповнення матриці суміжності з DataGridView
        for (int i = 0; i < points.Count; i++)
        {
            for (int j = i; j < points.Count; j++)
            {
                matr[i, j] = Convert.ToInt32(dgw.Rows[i].Cells[j].Value);
            }
        }
        
        // Малювання ребер
        for (int i = 0; i < points.Count; i++)
        {
            for (int j = i; j < points.Count; j++)
            {
                if (i != j && matr[i, j] == 1)
                {
                    // Перетворення координат вершин
                    int startX = points[i].X - 5;
                    int startY = -points[i].Y + 400 - 5;
                    int endX = points[j].X - 5;
                    int endY = -points[j].Y + 400 - 5;
                    
                    // Малювання лінії між вершинами
                    G.DrawLine(edgePen, startX, startY, endX, endY);
                }
            }
        }
        
        // Оновлення зображення
        pictureBox1.Image = myBMP;
    }
    
  • Код проекта.Переизначаємо віртуальний метод OnPaint():
    
    protected override void OnPaint(PaintEventArgs e)
    {
        // Очищення полотна білим кольором
        G.Clear(Color.White);
        
        // Налаштування пера для осей координат
        Pen axisPen = new Pen(Color.DarkGreen, 2);
        
        // Визначення точок для осей координат
        Point bottomLeft = new Point(1, 400);
        Point bottomRight = new Point(400, 400);
        Point topLeft = new Point(1, 1);
        Point topRight = new Point(400, 1);
        
        // Малювання осей координат
        G.DrawLine(axisPen, bottomLeft, bottomRight); // Горизонтальна вісь
        G.DrawLine(axisPen, topLeft, topRight);       // Друга горизонтальна лінія
        G.DrawLine(axisPen, bottomLeft, topLeft);     // Вертикальна вісь
        G.DrawLine(axisPen, topRight, bottomRight);   // Друга вертикальна лінія    
        // Налаштування пера для сітки
        Pen gridPen = new Pen(Color.FromArgb(50, 0, 0, 0), 1);    
        // Малювання сітки з кроком 10 пікселів
        for (int i = 0; i <= 400; i += 10)
        {
            G.DrawLine(gridPen, i, 0, i, 400);   // Вертикальні лінії сітки
            G.DrawLine(gridPen, 0, i, 400, i);   // Горизонтальні лінії сітки
        }    
        // Оновлення зображення та малювання елементів
        pictureBox1.Image = myBMP;
        DrawPoints(); // Малювання вершин    
        // Малювання ребер, якщо прапорець встановлений
        if (flag)
        {
            DrawEdges();
        }    
        base.OnPaint(e);
    }
    
  • Код проекта.Обработчики кнопок:
    
    /// 
    /// Обробник кліку кнопки для створення матриці суміжності
    /// 
    private void button4_Click(object sender, EventArgs e)
    {
        // Очищення існуючих стовпців
        dgw.Columns.Clear();
    
        // Додавання стовпців за кількістю вершин
        for (int i = 0; i < points.Count; i++)
        {
            var column = new DataGridViewTextBoxColumn
            {
                DataPropertyName = $"Column{i}",
                Name = i.ToString(),
                HeaderText = i.ToString(),
                Width = 40  // Оптимальна ширина стовпця
            };
            dgw.Columns.Add(column);
        }
    
        // Додавання рядків та ініціалізація значень
        dgw.Rows.Add(points.Count);
        
        // Заповнення матриці нулями
        for (int i = 0; i < points.Count; i++)
        {
            dgw.Rows[i].HeaderCell.Value = i.ToString();  // Нумерація рядків
            for (int j = 0; j < points.Count; j++)
            {
                dgw.Rows[i].Cells[j].Value = 0;  // Ініціалізація значень
            }
        }
    }
    
    /// 
    /// Обробник кліку кнопки для відображення ребер графа
    /// 
    private void button1_Click(object sender, EventArgs e)
    {
        this.flag = true;  // Встановлення прапорця для малювання ребер
        Invalidate();      // Оновлення відображення
    }
    

    Назад Вперед Зміст