Алгоритм Краскала для нахождения минимального остовного дерева на C#

воскресенье, 25 июля 2010, Александр Краковецкий

Остовное дерево графа - это подграф, который содержит все вершины и является деревом. Граф может содержать несколько остовных деревьев.

Рассмотрим пример:

o---o
    |\ /|
    | X |
    |/ \|
    o---o

Этот граф может иметь 16 различных остовных деревьев:

o---o    o---o    o   o    o---o
    |   |    |        |   |        |
    |   |    |        |   |        |
    |   |    |        |   |        |
    o   o    o---o    o---o    o---o

    o---o    o   o    o   o    o   o
     \ /     |\ /      \ /      \ /|
      X      | X        X        X |
     / \     |/ \      / \      / \|
    o   o    o   o    o---o    o   o

    o   o    o---o    o   o    o---o
    |\  |       /     |  /|     \
    | \ |      /      | / |      \
    |  \|     /       |/  |       \
    o   o    o---o    o   o    o---o

    o---o    o   o    o   o    o---o
    |\       |  /      \  |       /|
    | \      | /        \ |      / |
    |  \     |/          \|     /  |
    o   o    o---o    o---o    o   o

Для построения остовного дерева могут быть использованы методы Краскала (Kruskal), Прима (Prim) или Борувки (Boruvka). Рассмотрим алгоритм Краскала более подробно.

Алгоритм Краскала

Алгоритм Краскала в псевдокоде:

Kruskal's algorithm:
    sort the edges of G in increasing order by length
    keep a subgraph S of G, initially empty
    for each edge e in sorted order
        if the endpoints of e are disconnected in S
        add e to S
    return S

Вначале текущее множество рёбер устанавливается пустым. Затем, пока это возможно, проводится следующая операция: из всех рёбер, добавление которых к уже имеющемуся множеству не вызовет появление в нём цикла, выбирается ребро минимального веса и добавляется к уже имеющемуся множеству. Когда таких рёбер больше нет, алгоритм завершён. Подграф данного графа, содержащий все его вершины и найденное множество рёбер, является его остовным лесом минимального веса.

До начала работы алгоритма необходимо отсортировать рёбра по весу, это требует O(E × log(E)) времени. После чего компоненты связности удобно хранить в виде системы непересекающихся множеств. Все операции в таком случае займут O(E × α(E, V)), где α — функция, обратная к функции Аккермана. Поскольку для любых практических задач α(E, V) < 5, то можно принять её за константу, таким образом общее время работы алгоритма Краскала можно принять за O(E).

(Joseph. B. Kruskal. On the Shortest Spanning Subtree of a Graph and the Traveling Salesman Problem. // Proc. AMS. 1956. Vol 7, No. 1. C. 48-50)

Реализация на C#

Класс Kruskal.cs (использует небольшой дополнительный класс Edge для обозначения ребра графа):

/*****************************************************************
 *  File    : Kruskal.cs
 *  Purpose : Kruskal's algorithm implementation to find the edges and cost of the
 *            minimal spanning tree.
 *  Author  : Oleksandr Krakovetskyi 
 *  Mail Id : alex.krakovetskiy@gmail.com
 *  Website : www.msug.vn.ua
 *
 *****************************************************************/

namespace GraphMethods
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// 
    /// Class represents graph edge.
    /// 
    public class Edge
    {
        public int U;
        public int V;
        public double Weight;
    }

    /// 
    /// Implementation of Kruskal algorithm.
    /// 
    public class Kruskal
    {
        private const int MAX = 100;
        private int _edgesCount;
        private int _verticlesCount;
        private List _edges;
        private int[,] tree;
        private int[] sets;

        public List Edges { get { return _edges; } }
        public int VerticlesCount { get { return _verticlesCount; } }
        public double Cost { get; private set; }

        public Kruskal(string input)
        {
            tree = new int[MAX, 3];
            sets = new int[MAX];

            string[] lines = input.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
            _verticlesCount = int.Parse(lines[0]);
            _edgesCount = int.Parse(lines[1]);
            _edges = new List();

            _edges.Add(null);

            for (int i = 2; i < lines.Count(); i++)
            {
                string[] line = lines[i].Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                
                _edges.Add(new Edge
                {
                    U = int.Parse(line[0]),
                    V = int.Parse(line[1]),
                    Weight = double.Parse(line[2])
                });
            }

            for (int i = 1; i <= _verticlesCount; i++) sets[i] = i;
        }

        private void ArrangeEdges(int k)
        {
            Edge temp;
            for (int i = 1; i < k; i++)
            {
                for (int j = 1; j <= k - i; j++)
                {
                    if (_edges[j].Weight > _edges[j + 1].Weight)
                    {
                        temp = _edges[j];
                        _edges[j] = _edges[j + 1];
                        _edges[j + 1] = temp;
                    }
                }
            }
        }
        
        private int Find(int vertex)
        {
            return (sets[vertex]);
        }

        private void Join(int v1, int v2)
        {
            if (v1 < v2)
                sets[v2] = v1;
            else
                sets[v1] = v2;
        }

        public void BuildSpanningTree()
        {
            int k = _verticlesCount;
            int i, t = 1;
            this.ArrangeEdges(k);
            this.Cost = 0;
            for (i = 1; i <= k; i++)
            {
                for (i = 1; i < k; i++)
                    if (this.Find(_edges[i].U) != this.Find(_edges[i].V))
                    {
                        tree[t, 1] = _edges[i].U;
                        tree[t, 2] = _edges[i].V;
                        this.Cost += _edges[i].Weight;
                        this.Join(_edges[t].U, _edges[t].V);
                        t++;
                    }
            }
        }

        public void DisplayInfo()
        {
            Console.WriteLine("The Edges of the Minimum Spanning Tree are:");
            for (int i = 1; i < _verticlesCount; i++)
                Console.WriteLine(tree[i,1] + " - " + tree[i,2]);
        }
    }
}

Проверка реализации алгоритма на конкретном примере

Пусть имеем следующий граф:

minimum spanning tree 3

У которого минимальное основное дерево имеет следующий вид:

minimum spanning tree 4

Вес минимального остовного дерева: 6

На вход алгоритма подадим такое описание графа:

4
6
1 2 1
2 3 3
3 4 2
4 1 5
1 3 4
2 4 6

где 4 - количество вершин, 6 - количество ребер, остальные строки - это номера вершин ребра и его вес.

Функция main консольного приложения имеет вид:

static void Main(string[] args)
        {
            Kruskal k = new Kruskal(@"4
6
1 2 1
2 3 3
3 4 2
4 1 5
1 3 4
2 4 6");

            k.BuildSpanningTree();
            Console.WriteLine("Cost: " + k.Cost);
            k.DisplayInfo();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }

После выполнения мы получим такой результат на экране:

Cost: 6

The Edges of the Minimum Spanning Tree are:

1 - 2

3 - 4

2 - 3

Press any key...

Можно убедится с рисунка выше, что это правильный ответ.


Ищите нас в интернетах!

Комментарии

Свежие вакансии