Алгоритм для Рендеринга Горизонтального Дерева Двоичного выхода в форме текста/ASCII

Это - довольно нормальное двоичное дерево, за исключением того, что один из узлов может быть пустым.

Я хотел бы найти способ произвести его горизонтальным способом (то есть, корневой узел слева и расширяется направо).

У меня был некоторый опыт при разворачивании деревьев вертикально (корневой узел наверху, расширяясь вниз), но я не уверен, где запустить в этом случае.

Предпочтительно, это следовало бы этим нескольким правилам:

  • Если узел имеет только одного ребенка, он может быть пропущен как избыточный ("конечный узел", без детей, всегда отображается),
  • Все узлы той же глубины должны быть выровненные вертикально; все узлы должны быть направо от всех меньшим количеством глубоко узлов и слева от всех более глубоких узлов.
  • Узлы имеют строковое представление, которое включает их глубину.
  • Каждый "конечный узел" имеет свою собственную уникальную строку; то есть, количество строк является количеством конечных узлов в дереве, и когда конечный узел находится на строке, не может быть ничего иного на той строке после того конечного узла.
  • В результате последнего правила корневой узел мог бы быть более обеспечен или в верхнем левом или в левом нижнем угле; верхний левый предпочтен.

Например, это - допустимое дерево, с шестью конечными узлами (узел представлен именем и его глубиной):Править: посмотрите нижнюю часть вопроса для альтернативного, более легкого рендеринга

        
[a0]-----------[b3]------[c5]------[d8]
    \              \         \----------[e9]
     \              \----[f5]
      \-[g1]--------[h4]------[i6]
            \           \--------------------[j10]
             \-[k3]

Который представляет вертикальное, явное двоичное дерево:

0              a
              / \
1            g   *
            / \   \
2          *   *   *
          /     \   \
3        k       *   b
                /   / \
4              h   *   *
              / \   \   \
5            *   *   f   c
            /     \     / \
6          *       i   *   *
          /           /     \
7        *           *       *
        /           /         \
8      *           *           d
      /           /
9    *           e
    /
10 j

(ответвления свернуты для компактности; * представляя избыточные, узлы с одним ребенком; отметьте это *фактические узлы, храня одного ребенка каждый, только с именами, опущенными здесь для пользы презентации),

(также, для разъяснения я хотел бы генерировать первое, горизонтальное дерево; не это вертикальное дерево)

Я говорю, что агностик языка, потому что я просто ищу алгоритм; я говорю, что рубин, потому что я в конечном счете оказываюсь перед необходимостью реализовывать его в рубине так или иначе.

Предположите что каждый Node структура данных хранит только свой идентификатор, левый узел и правильный узел.

Ведущее устройство Tree класс отслеживает все узлы и имеет соответствующие алгоритмы для нахождения:

  • Энный предок узла
  • Энный потомок узла
  • Все потомки конечного узла узла и их количество
  • Поколение узла
  • Самый низкий общий предок двух данных узлов

Я уже знаю:

  • Количество конечных узлов

У кого-либо есть какие-либо идеи того, где я мог запустить? Я должен пойти для рекурсивного подхода? Повторяющийся? Некоторый Psuedo-код был бы довольно прохладен также и очень ценивший =)


прогресс

Согласно предложению walkytalky, я решил видеть то, на что оно будет похоже для отображения каждого "соответствующего" или значительного узла на сетку, при этом столбцы будут глубиной и строками, идентифицирующимися их конечными узлами. Вот то, что происходит (пропуск столбца 7, потому что нет никаких значительных узлов подробно 7):

depth: 0  1  2  3  4  5  6  8  9  10
       a        b     c     d
                               e
                      f
          g        h     i
                                  j
                k

Должно быть достаточно легко генерировать эту сетку, или с или с поисками в глубину в ширину. Возможно, наиболее тривиально путем простого хранения 2D массива и размещения каждого значительного узла, найденного в него, вставки строки для каждого "второго ребенка".

Теперь, знание этих фактов:

  • Последний узел подряд должен быть конечным узлом
  • Дети всегда направо, и на той же строке или ниже, их родительского узла.
  • Все неконечные узлы должны иметь точно двух детей
  • Поэтому все неконечные узлы имеют детей, которые являются первыми направо от их столбца, при этом первый ребенок находится на той же строке, при этом второй ребенок является n строками ниже их, где n является количеством узлов на правой стороне его.

Мы видим, что, учитывая любую допустимую сетку существует один однозначный способ "соединить точки", так сказать; существует одно однозначное представленное дерево.

Теперь, "соединение точек" больше не является вопросом о двоичной древовидной структуре... это - просто вопрос о художественном оформлении. Мы просто должны создать алгоритм для надлежащего размещения права -и \куда они могут пойти, возможно, только после простой сетки / лексикографических правил, вместо правил двоичной древовидной структуры.

В основном это означает, что проблемой рендеринга дерева является теперь намного более простая проблема рендеринга сетки с необычными художественными оформлениями.

Кто-либо может предложить какой-либо способ сформулировать эти правила? Или возможно совершенно другой метод в целом?


править

Я забеременел очень, намного более легкий заключительный рендеринг:

--d0----d1----d3----d4----d5----d6----d8----d9----d10-- => guide line (not rendered)

 [a0 ]-------[b3 ]-------[c5 ]-------[d8 ]
   |           |           \---------------[e9 ]
   |           \---------[f5 ]
   \---[g1 ]-------[h4 ]-------[i6 ]
         |           \---------------------------[j10]
         \---[k3 ]

--d0----d1----d3----d4----d5----d6----d8----d9----d10-- => guide line (not rendered)

Могло бы быть легче попытаться создать этого вместо того, который я отправил ранее. Для одного это сохраняет симпатичную форму сетки, и Вы не имеете к непостоянному с диагональными строками. Строки все отображаются вдоль строк явно видимого столбца. К сожалению, это нигде не рядом настолько же симпатично как первое.

8
задан Justin L. 17 June 2010 в 04:27
поделиться

5 ответов

Если имеется N конечных узлов, должно быть N-1 внутренних узлов с 2 дочерними узлами. (Может быть любое количество внутренних узлов с 1 дочерним элементом, которые нам придется подсчитать, чтобы получить глубину, но в противном случае игнорировать.) Таким образом, создание дерева эквивалентно размещению этих узлов в сетке, где:

  • количество строк в сетке: N
  • Я думаю количество столбцов находится между 1 + floor (log2 (N)) и 2 * N-1 , в зависимости от степени перекрытия; это, вероятно, не имеет большого значения для наших целей, хотя
  • каждая конечная точка появляется в другой строке
  • все узлы с одинаковой глубиной отображаются в одном столбце
  • все внутренние узлы появляются в той же строке, что и их крайние правые конечная точка-потомок

Итак, давайте посмотрим:

  • Обходим дерево в глубину, справа налево.
  • Для каждой конечной точки запишите ее глубину и метку.
  • Для каждого внутреннего 2-дочернего элемента запишите его глубину, метку и индексы крайних правых и крайних левых дочерних конечных точек.
  • Сортировка всей партии по глубине - это дает вам порядок столбцов, при этом количество отдельных глубин дает фактическое количество столбцов. (Я думаю, все остальные упорядочения должны выводиться автоматически из обхода, но здесь дело обстоит не так, потому что любая ветвь может иметь любую глубину.)
  • Разместите все узлы в сетке.
  • Пометьте пустые ячейки справа от каждого узла, не являющегося конечной точкой, как горизонтальные ветви.
  • Пометьте пустые ячейки вниз от каждого внутреннего узла до строки над его левым дочерним элементом как вертикальные ветви, а ячейку на уровне левого дочернего элемента как соединение.

  • Печать с соответствующим оформлением ASCII.

Обновление:

Как вы говорите, позиционирования достаточно, чтобы однозначно определить соединения, но вам все равно нужно проделать некоторую восходящую работу, чтобы получить это правильно, поэтому я, вероятно, все равно сделаю шаги «пометки» во время построения сетки.

Мне казалось, что печать была достаточно тривиальной, чтобы ее можно было замазать, но:

  • Итерируем вниз по каждому столбцу и определяем ширину столбца как размер фиксированных элементов + максимальная длина метки + пол (log10 (глубина) + 1) . (Фиксированные элементы могут быть [ и ] - , например. Мы можем заменить ] \ n в качестве суффикса для конечных точек.)
  • Для каждой строки
    • для каждого столбца
      • , если ячейка содержит узел или конечную точку
        • печать фиксированного префикса
        • печать этикетки
        • глубина печати
        • печать заполненных пробелов (максимальная длина этикетки - текущая длина этикетки)
        • печать соответствующего суффикса
        • , если узел является конечной точкой, перейти к следующей строке
      • если ячейка пуста, вывести заполнение пробелов шириной столбца
      • , если ячейка содержит вертикаль, вывести некоторое выбранное количество пробелов префикса, полосу и заполнить пробелами
      • , если ячейка содержит перекресток, вывести несколько выбранное префиксное количество пробелов, обратная косая черта и заполнение дефисами
      • , если ячейка содержит горизонтальную, напечатайте полную ширину столбца с дефисами

Преобразование этого в печать диагоналей может быть самым простым, если вы сначала сгенерируете прямую версию, а затем выполните некоторые замены в массиве символов - в противном случае вы можете получить случаи, когда вы визуализируете длинную вертикальную ветвь в столбце, отличном от того, в котором она возникла.

В какой-то момент я могу попытаться поместить это в код, но, вероятно, этого не произойдет сегодня - кое-что нужно сделать!

3
ответ дан 5 December 2019 в 20:13
поделиться

Похоже, интересная проблема; Я был бы счастлив попробовать, если бы у меня было больше времени.

Я бы, вероятно, выбрал следующий подход:

  1. Начните рендеринг «правильных» (или, в вашем случае, «верхних») узлов, пока я не дойду до конца. (например: визуализировать a, b, c и d)
  2. Вернитесь к последнему узлу с дочерним узлом (например, c) и сделайте то же самое рекурсивно

Вам нужно будет сохранить глобальную переменную, указывающую на строку, которую вы печатаете. Каждый рекурсивный вызов увеличивает эту переменную.

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

function print_tree(Node n) {
    print "\n" // begin on a fresh new line
    childs = new Array();
    do {
        if (n.hasLeftChild) {
            childs.push(n.leftChild)
        }
        print "---" + n.id    //this needs a lot of tweaking, but you get the idea
    } while(n = n.rightChild)
    childs.reverse()
    foreach(child in childs) {
        print_tree(child);
    }
}
2
ответ дан 5 December 2019 в 20:13
поделиться

Вам, вероятно, потребуется выполнить поиск в глубину, если нет поиск по всему дереву, чтобы правильно подобрать его размер для вывода по двум измерениям.

0
ответ дан 5 December 2019 в 20:13
поделиться

Если вы начнете с ширины метки для каждого уровня (не включая [] символов), то она будет равна самой большой метке для этой ширины (в этом примере ширина обычно равна 2, за исключением j10 , который равен 3, и уровни 2 и 7, которые равны 0).

Сделайте каждый уровень с ненулевой максимальной шириной метки с равным интервалом с одним символом - между каждым уровнем, чтобы вы могли вычислить местоположения начального уровня y .

Дайте каждому узлу номер строки.

Затем отрегулируйте расположение уровней на основе максимального количества линий между детьми на уровне.

Добавлено 2 на уровень 1 для a0 до g1
Добавлен 1 на уровень 2 для g1 до k3
Добавлен 1 к уровню 4 для b3 в []

Используйте символы \ и ` для диагоналей.

[a0]---------[b3]-------[c5]------[d8]
    \            \          `----------[e9]
     \            `-----[f5]
      `[g1]--------[h4]------[i6]
           \           `--------------------[j10]
            `[k3]
2
ответ дан 5 December 2019 в 20:13
поделиться

Ниже приведен полностью функциональный код C #, который делает именно то, что вы хотите. Как это происходит:

  • Дерево представлено в виде объектов из классов, которые наследуются от узла
  • Сначала вычислите количество листьев и создайте массив из этого количества строк
  • Затем для каждого уровня:
    • выяснить, в каких строках мы собираемся записать
    • для этих строк, вычислить максимум того, что уже есть в этих строках
    • записать все узлы в столбец max (номер из предыдущего шага, конец предыдущего уровень) +1; добавьте в начало - , чтобы добраться до этого столбца.
    • запишите диагональные строки от всех двоичных узлов до строки их правого дочернего элемента (в моей программе первый дочерний элемент левый, второй правый, у вас есть другой наоборот)
    • перейти на один уровень

Алгоритм гарантирует, что каждый уровень начинается только после предыдущих окончаний. Вероятно, это хороший выбор для коротких имен, но для более длинных имен это, вероятно, не должно применяться.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SO_ASCII_tree
{
    class Program
    {
        static void Main()
        {
            Node root = …;

            StringBuilder[] lines = Enumerable.Range(0, root.Leaves).Select(i => new StringBuilder()).ToArray();

            Node[] currentLevel = new Node[] { root };
            int level = 0;
            int min = 0;
            int max = 0;
            while (currentLevel.Any())
            {
                NamedNode[] namedNodes = currentLevel.OfType<NamedNode>().ToArray();
                if (namedNodes.Any())
                {
                    min = namedNodes.Select(node => lines[node.Line].Length).Max();
                    min = Math.Max(min, max);
                    if (min != 0)
                        min++;
                    foreach (NamedNode namedNode in namedNodes)
                        WriteAtPosition(lines[namedNode.Line], namedNode.Write(level), min, '-');
                    max = namedNodes.Select(node => lines[node.Line].Length).Max();
                    // change to max = min + 1; for long names
                }

                foreach (Node node in currentLevel)
                    node.SetChildLines();

                Binary[] binaries = namedNodes.OfType<Binary>().ToArray();
                foreach (Binary binary in binaries)
                    GoDown(lines, binary.Line, binary.Right.Line);

                currentLevel = currentLevel.SelectMany(node => node.Children).ToArray();
                level++;
            }

            foreach (StringBuilder line in lines)
                Console.WriteLine(line.ToString());
        }

        static void WriteAtPosition(StringBuilder line, string message, int position, char prepend = ' ')
        {
            if (line.Length > position)
                throw new ArgumentException();
            line.Append(prepend, position - line.Length);
            line.Append(message);
        }

        static void GoDown(StringBuilder[] lines, int from, int to)
        {
            int line = from + 1;
            int position = lines[from].Length;
            for (; line <= to; line++, position++)
                WriteAtPosition(lines[line], "\\", position);
        }
    }

    abstract class Node
    {
        public int Line
        { get; set; }

        public abstract int Leaves
        { get; }

        public abstract IEnumerable<Node> Children
        { get; }

        public virtual void SetChildLines()
        { }
    }

    abstract class NamedNode : Node
    {
        public string Name
        { get; set; }

        public string Write(int level)
        {
            return '[' + Name + level.ToString() + ']';
        }
    }

    class Binary : NamedNode
    {
        public Node Left
        { get; set; }
        public Node Right
        { get; set; }

        int? leaves;
        public override int Leaves
        {
            get
            {
                if (leaves == null)
                    leaves = Left.Leaves + Right.Leaves;
                return leaves.Value;
            }
        }

        public override IEnumerable<Node> Children
        {
            get
            {
                yield return Left;
                yield return Right;
            }
        }

        public override void SetChildLines()
        {
            Left.Line = Line;
            Right.Line = Line + Left.Leaves;
        }
    }

    class Unary : Node
    {
        public Node Child
        { get; set; }

        int? leaves;
        public override int Leaves
        {
            get
            {
                if (leaves == null)
                    leaves = Child.Leaves;
                return leaves.Value;
            }
        }

        public override IEnumerable<Node> Children
        {
            get
            {
                yield return Child;
            }
        }

        public override void SetChildLines()
        {
            Child.Line = Line;
        }
    }

    class Leaf : NamedNode
    {
        public override int Leaves
        {
            get
            {
                return 1;
            }
        }

        public override IEnumerable<Node> Children
        {
            get
            {
                yield break;
            }
        }
    }
}

РЕДАКТИРОВАТЬ : ваше примерное дерево отображается точно так же, как и ваше:

[a0]-----------[b3]------[c5]------[d8]
    \              \         \----------[e9]
     \              \----[f5]
      \-[g1]--------[h4]------[i6]
            \           \--------------------[j10]
             \-[k3]
1
ответ дан 5 December 2019 в 20:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: