C# и немного алгоритмики: binary trees (реализация, примеры)

в 15:35, , рубрики: .net, Алгоритмы

Всем доброго дня! Давайте немного вспомним азы программирования на C# и повторим, что такое бинарные деревья (binary trees), чем они отличаются от остальных деревьев, что же такое «обход» и каким он бывает.

C# и немного алгоритмики: binary trees (реализация, примеры) - 1

Данная статья пригодится студентам, начинающим программистам, а также «профи» в качестве справочного материала. Приведены примеры, фрагменты алгоритмов и полный проект на C#. Всех заинтересовавшихся прошу под кат!

Для начала повторим азы. Дерево – связный граф без циклов. Корень – самая верхняя вершина дерева. Лист – вершина, не имеющая потомков. Обход дерева – систематический просмотр всех вершин, при котором каждая вершина встречается один раз.

В рассматриваемых алгоритмах используется бинарное дерево – то есть дерево, в котором каждая вершина имеет два потомка (правые и левые поддеревья – left и right). Добавление реализуется по следующей схеме: при добавлении каждой новой вершины, если значение меньше корня, то оно записывается в левое поддерево, в противном случае – в правое (древесная сортировка).

Реализуем же все перечисленные алгоритмы на языке C#!

Тех, кто хочет немного обновить в памяти данный материал, либо еще только учится программировать базовые алгоритмы, без которых не обойдется ни один программист, прошу под кат!) Все фрагменты кода подробно прокомментированы, а проект на языке C# (VS2012) опубликован на GitHub.

Опишем класс Tree на языке C#:

    public class Tree
    {
        // подкласс "элемент дерева"
        public class TreeNode
        {
            public int Value; // численное значение
            public int Count = 0; // сколько раз было добавлено данное численное значение
            public TreeNode Left; // левое поддерево
            public TreeNode Right; // правое поддерево
        }
        public TreeNode Node; // экземпляр класса "элемент дерева"
    }

И реализуем методы:

        // добавление значения в дерево (рекурсивно)
 private void AddRecursion(ref TreeNode node, int val)
        // печать дерева (рекурсивно) 
 private void PrintRecursion(TreeNode node, int spaces, ref string s)
        // обход дерева в ширину (итерационно, используется очередь)
 private void Across(TreeNode node, ref string s, bool detailed)
        // прямой обход (CLR - center, left, right)
 private void CLR(TreeNode node, ref string s, bool detailed)
        // обратный обход (LCR - left, center, right) 
 private void LCR(TreeNode node, ref string s, bool detailed)
        // концевой обход (RCL - left, right, center)
 private void RCL(TreeNode node, ref string s, bool detailed)

Обход в глубину

Сперава разберемся с его видами:

  • Прямой обход (CLR – center, left, right). Сначала берется значение корня, затем обходится левое поддерево, затем правое;
  • Концевой обход (RCL – right, center, left). Сначала обходится правое поддерево, затем берется значение корня, затем левое;
  • Обратный обход (LCR – left, center, right). Сначала обходится левое поддерево, затем берется значение корня, затем правое поддерево.

Программная реализация всех трех перечисленных алгоритмов схожа, используется рекурсивный вызов методов. Сложность алгоритма O(N*log2(N)), где N – число вершин.

Прямой обход:

// прямой обход (CLR - center, left, right)
private void CLR(TreeNode node, ref string s, bool detailed)
{
    /*
     Аргументы метода:
     1. TreeNode node - текущий "элемент дерева" (ref  передача по ссылке)       
     2. ref string s - строка, в которой накапливается результат (ref - передача по ссылке)
    */
    if (node != null)
    {
        if (detailed)
            s += "    получили значение " + node.Value.ToString() + Environment.NewLine;
        else
            s += node.Value.ToString() + " "; // запомнить текущее значение
        if (detailed) s += "    обходим левое поддерево" + Environment.NewLine;
            CLR(node.Left, ref s, detailed); // обойти левое поддерево
        if (detailed) s += "    обходим правое поддерево" + Environment.NewLine;
            CLR(node.Right, ref s, detailed); // обойти правое поддерево
    }
    else if (detailed) s += "    значение отсутствует - null" + Environment.NewLine;
}

Обратный обход:

// обратный обход (LCR - left, center, right) 
private void LCR(TreeNode node, ref string s, bool detailed)
{
    /*
     Аргументы метода:
     1. TreeNode node - текущий "элемент дерева" (ref - передача по ссылке)       
     2. ref string s - строка, в которой накапливается результат (ref - передача по ссылке)
    */
    if (node != null)
    {
        if (detailed) s += "    обходим левое поддерево" + Environment.NewLine;
        LCR(node.Left, ref s, detailed); // обойти левое поддерево
        if (detailed)
            s += "    получили значение " + node.Value.ToString() + Environment.NewLine;
        else
            s += node.Value.ToString() + " "; // запомнить текущее значение
        if (detailed) s += "    обходим правое поддерево" + Environment.NewLine;
        LCR(node.Right, ref s, detailed); // обойти правое поддерево
    }
    else if (detailed) s += "    значение отсутствует - null" + Environment.NewLine;
}

Концевой обход:

// концевой обход (RCL -right, center, left)
private void RCL(TreeNode node, ref string s, bool detailed)
{
    /*
     Аргументы метода:
     1. TreeNode node - текущий "элемент дерева" (ref  передача по ссылке)       
     2. ref string s - строка, в которой накапливается результат (ref - передача по ссылке)
    */
    if (node != null)
    {
        if (detailed) s += "    обходим правое поддерево" + Environment.NewLine;
        RCL(node.Right, ref s, detailed); // обойти правое поддерево
        if (detailed)
            s += "    получили значение " + node.Value.ToString() + Environment.NewLine;
        else
            s += node.Value.ToString() + " "; // запомнить текущее значение
        if (detailed) s += "    обходим левое поддерево" + Environment.NewLine;
        RCL(node.Left, ref s, detailed); // обойти левое поддерево
    }
    else if (detailed) s += "    значение отсутствует - null" + Environment.NewLine;
}

?

Обход в ширину

Поддеревья посещаются уровень за уровнем, каждый уровень обходится слева направо. Сложность алгоритма O(V+E), где V – множество вершин, а E-множество дуг.

// обход дерева в ширину (итерационно, используется очередь)
private void Across(TreeNode node, ref string s, bool detailed)
{
    /*
     Аргументы метода:
     1. TreeNode node - текущий "элемент дерева" (ref  передача по ссылке)       
     2. ref string s - строка, в которой накапливается результат (ref - передача по ссылке)
    */
    var queue = new Queue<TreeNode>(); // создать новую очередь
    if (detailed) s += "    заносим в очередь значение " + node.Value.ToString() + Environment.NewLine; queue.Enqueue(node); // поместить в очередь первый уровень
    while (queue.Count!=0) // пока очередь не пуста
    {    
        //если у текущей ветви есть листья, их тоже добавить в очередь
        if (queue.Peek().Left != null)
        {
            if (detailed) s += "    заносим в очередь значение " + queue.Peek().Left.Value.ToString() + " из левого поддерева" + Environment.NewLine;
            queue.Enqueue(queue.Peek().Left);
        }
        if (queue.Peek().Right != null)
        {
            if (detailed) s += "    заносим в очередь значение " + queue.Peek().Right.Value.ToString() + " из правого поддерева" + Environment.NewLine;
            queue.Enqueue(queue.Peek().Right);
        }
        //извлечь из очереди информационное поле последнего элемента
        if (detailed) s += "    извлекаем значение из очереди: " + queue.Peek().Value.ToString() + Environment.NewLine;
        else s += queue.Peek().Value.ToString() + " "; // убрать последний элемент очереди
        queue.Dequeue();
    }
}?

С использованием данных методов я по-быстрому написал такую вот небольшую программку:

C# и немного алгоритмики: binary trees (реализация, примеры) - 2

C# и немного алгоритмики: binary trees (реализация, примеры) - 3

Разберем несколько примеров

Пример 1

C# и немного алгоритмики: binary trees (реализация, примеры) - 4

Пример: дерево с большим числом поддеревьев, «правых» и «левых».
В глубину CLR: 5 4 3 1 9 8 6 7 15 10
В глубину LCR: 1 3 4 5 6 7 8 9 10 15
В глубину RCL: 15 10 9 8 7 6 5 4 3 1
В ширину: 5 4 9 3 8 15 1 6 10 7

Пример2

C# и немного алгоритмики: binary trees (реализация, примеры) - 5

Пример: только отрицательные значения в дереве.
В глубину CLR: -2 -4 -15 -5 -1
В глубину LCR: -15 -5 -4 -2 -1
В глубину RCL: -1 -2 -4 -5 -15
В ширину: -2 -4 -1 -15 -5

Пример 3

C# и немного алгоритмики: binary trees (реализация, примеры) - 6

Пример: отрицательные и положительные значения в дереве
В глубину CLR: 5 4 3 -30 0 1 2 15 8 30
В глубину LCR: -30 0 1 2 3 4 5 8 15 30
В глубину RCL: 30 15 8 5 4 3 2 1 0 -30
В ширину: 5 4 15 3 8 30 -30 0 1 2

Пример 4

C# и немного алгоритмики: binary trees (реализация, примеры) - 7

Пример: только правые поддеревья
В глубину CLR: 4 5 6 7 8
В глубину LCR: 4 5 6 7 8
В глубину RCL: 8 7 6 5 4
В ширину: 4 5 6 7 8

Пример 5

C# и немного алгоритмики: binary trees (реализация, примеры) - 8

Пример: только левые поддеревья
В глубину CLR: 10 9 8 7 6
В глубину LCR: 6 7 8 9 10
В глубину RCL: 10 9 8 7 6
В ширину: 10 9 8 7 6

Пример 6

C# и немного алгоритмики: binary trees (реализация, примеры) - 9

Пример: наиболее емкая наглядность обходов
В глубину CLR: 10 9 11
В глубину LCR: 9 10 11
В глубину RCL: 11 10 9
В ширину: 10 9 11

Исходники проекта

Полные исходники программы для работы с бинарными деревьями выкладываю на GitHub. Можете свободно пользоваться ими для своих проектов или для более локальных задач, к примеру, выполнения лабораторных работ в университете. Присутствуют возможности загрузки исходных данных из текстового файла, сохранения результатов в файл, а также распечатки подробных действий реализации алгоритма (то есть логирование).

Спасибо за внимание! Если будут вопросы — рад на них ответить.

Автор: Dageron

Источник


  1. Феликс:

    А взглянуть на экране на бинарное дерево можно?
    https://soft.softodrom.ru/ap/Trees-Portable-p19987

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js