Это - довольно нормальное двоичное дерево, за исключением того, что один из узлов может быть пустым.
Я хотел бы найти способ произвести его горизонтальным способом (то есть, корневой узел слева и расширяется направо).
У меня был некоторый опыт при разворачивании деревьев вертикально (корневой узел наверху, расширяясь вниз), но я не уверен, где запустить в этом случае.
Предпочтительно, это следовало бы этим нескольким правилам:
Например, это - допустимое дерево, с шестью конечными узлами (узел представлен именем и его глубиной):Править: посмотрите нижнюю часть вопроса для альтернативного, более легкого рендеринга
[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 массива и размещения каждого значительного узла, найденного в него, вставки строки для каждого "второго ребенка".
Теперь, знание этих фактов:
Мы видим, что, учитывая любую допустимую сетку существует один однозначный способ "соединить точки", так сказать; существует одно однозначное представленное дерево.
Теперь, "соединение точек" больше не является вопросом о двоичной древовидной структуре... это - просто вопрос о художественном оформлении. Мы просто должны создать алгоритм для надлежащего размещения права -
и \
куда они могут пойти, возможно, только после простой сетки / лексикографических правил, вместо правил двоичной древовидной структуры.
В основном это означает, что проблемой рендеринга дерева является теперь намного более простая проблема рендеринга сетки с необычными художественными оформлениями.
Кто-либо может предложить какой-либо способ сформулировать эти правила? Или возможно совершенно другой метод в целом?
править
Я забеременел очень, намного более легкий заключительный рендеринг:
--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)
Могло бы быть легче попытаться создать этого вместо того, который я отправил ранее. Для одного это сохраняет симпатичную форму сетки, и Вы не имеете к непостоянному с диагональными строками. Строки все отображаются вдоль строк явно видимого столбца. К сожалению, это нигде не рядом настолько же симпатично как первое.
Если имеется N
конечных узлов, должно быть N-1
внутренних узлов с 2 дочерними узлами. (Может быть любое количество внутренних узлов с 1 дочерним элементом, которые нам придется подсчитать, чтобы получить глубину, но в противном случае игнорировать.) Таким образом, создание дерева эквивалентно размещению этих узлов в сетке, где:
N
1 + floor (log2 (N))
и 2 * N-1
, в зависимости от степени перекрытия; это, вероятно, не имеет большого значения для наших целей, хотя Итак, давайте посмотрим:
Пометьте пустые ячейки вниз от каждого внутреннего узла до строки над его левым дочерним элементом как вертикальные ветви, а ячейку на уровне левого дочернего элемента как соединение.
Печать с соответствующим оформлением ASCII.
Обновление:
Как вы говорите, позиционирования достаточно, чтобы однозначно определить соединения, но вам все равно нужно проделать некоторую восходящую работу, чтобы получить это правильно, поэтому я, вероятно, все равно сделаю шаги «пометки» во время построения сетки.
Мне казалось, что печать была достаточно тривиальной, чтобы ее можно было замазать, но:
размер фиксированных элементов + максимальная длина метки + пол (log10 (глубина) + 1)
. (Фиксированные элементы могут быть [
и ] -
, например. Мы можем заменить ] \ n
в качестве суффикса для конечных точек.) Преобразование этого в печать диагоналей может быть самым простым, если вы сначала сгенерируете прямую версию, а затем выполните некоторые замены в массиве символов - в противном случае вы можете получить случаи, когда вы визуализируете длинную вертикальную ветвь в столбце, отличном от того, в котором она возникла.
В какой-то момент я могу попытаться поместить это в код, но, вероятно, этого не произойдет сегодня - кое-что нужно сделать!
Похоже, интересная проблема; Я был бы счастлив попробовать, если бы у меня было больше времени.
Я бы, вероятно, выбрал следующий подход:
Вам нужно будет сохранить глобальную переменную, указывающую на строку, которую вы печатаете. Каждый рекурсивный вызов увеличивает эту переменную.
редактировать: хорошо, я не мог устоять перед попыткой написать какой-нибудь непроверенный псевдокод, надеюсь, он сработает:
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, за исключением 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]
Ниже приведен полностью функциональный код C #, который делает именно то, что вы хотите. Как это происходит:
узла
-
, чтобы добраться до этого столбца. Алгоритм гарантирует, что каждый уровень начинается только после предыдущих окончаний. Вероятно, это хороший выбор для коротких имен, но для более длинных имен это, вероятно, не должно применяться.
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]