2012-06-19 6 views
16

Как вы можете напечатать двоичное дерево на своей стороне, чтобы результат выглядел так?печать двоичного дерева на его стороне

__/a 
__/ \b 
    \ _/c 
    \_/ \d 
    \e 

(Красивее ASCII-арт приветствуются)

Вот код, который не совсем работает:

def print_tree(tree): 
    def emit(node,prefix): 
     if "sequence" in node: 
      print "%s%s"%(prefix[:-1],node["name"]) 
     else: 
      emit(node["left"],"%s_/ "%prefix.replace("/ "," /")[:-1].replace("_"," ")) 
      emit(node["right"],"%s \\ "%prefix.replace("\\ "," \\")[:-1]) 
    emit(tree,"")  

, который выводит это:

 _/hg19 
    _/ \rheMac2 
    _/ \mm9 
    /\_/bosTau4 
/\_/canFam2 
_/  \pteVam1 
\_/loxAfr3 
    \dasNov2 

Scope ползучесть: это было бы e xcellent, если вы можете передать функцию, которая вернет строку для печати любого узла; таким образом, я иногда могу печатать информацию о узлах без останова. Таким образом, независимо от того, что узел имеет что-либо для печати, управляется функцией, переданной в качестве параметра.

Вот некоторые тест-данные в формате JSON:

{ 
    "left": { 
     "left": { 
      "left": { 
       "left": { 
        "name": "hg19", 
        "sequence": 0 
       }, 
       "right": { 
        "name": "rheMac2", 
        "sequence": 1 
       } 
      }, 
      "right": { 
       "name": "mm9", 
       "sequence": 2 
      } 
     }, 
     "right": { 
      "left": { 
       "name": "bosTau4", 
       "sequence": 3 
      }, 
      "right": { 
       "left": { 
        "name": "canFam2", 
        "sequence": 4 
       }, 
       "right": { 
        "name": "pteVam1", 
        "sequence": 5 
       } 
      } 
     } 
    }, 
    "right": { 
     "left": { 
      "name": "loxAfr3", 
      "sequence": 6 
     }, 
     "right": { 
      "name": "dasNov2", 
      "sequence": 7 
     } 
    } 
} 
+3

Что вы пробовали? Я могу представить, что он включает в себя вычисление свойств дерева (глубина, ширина и т. Д.), Вычисление компоновки и создание искусства ASCII. –

+0

@SimeonVisser добавил некорректный код – Will

+1

Глядя на это, я задумался, что вы также должны отслеживать глубину дерева. У меня есть некоторый рудиментарный код на основе вашего сломанного кода, но он выглядит ужасно. Для каждой строки я пытаюсь выяснить, сколько дополнительного пространства у нее должно получиться, но восстановление для этой строки в настоящее время учитывает только самую низкую ветвь – Michael

ответ

7

Вот код, который реализует общий, рекурсивный подход, описанный в другом месте. Внутреннее представление дерева - это либо строка (лист), либо кортеж (пара) узлов. Внутренним представлением промежуточного «фрагмента» узла является кортеж (above, below, lines), где above и below - это количество строк выше и ниже корня, а lines - итератор над каждой частичной линией (без пробелов слева).

#!/usr/local/bin/python3.3 

from itertools import chain 
from random import randint 


def leaf(t): 
    return isinstance(t, str) 

def random(n): 
    def extend(t): 
     if leaf(t): 
      return (t+'l', t+'r') 
     else: 
      l, r = t 
      if randint(0, 1): return (l, extend(r)) 
      else: return (extend(l), r) 
    t = '' 
    for _ in range(n-1): t = extend(t) 
    return t 

def format(t): 
    def pad(prefix, spaces, previous): 
     return prefix + (' ' * spaces) + previous 
    def merge(l, r): 
     l_above, l_below, l_lines = l 
     r_above, r_below, r_lines = r 
     gap = r_below + l_above 
     gap_above = l_above 
     gap_below = gap - gap_above 
     def lines(): 
      for (i, line) in enumerate(chain(r_lines, l_lines)): 
       if i < r_above: 
        yield ' ' + line 
       elif i - r_above < gap_above: 
        dash = '_' if i - r_above == gap_above - 1 else ' ' 
        if i < r_above + r_below: 
         yield pad(dash + '/', 2 * (i - r_above), line) 
        else: 
         yield pad(dash + '/', 2 * gap_below - 1, line) 
       elif i - r_above - gap_above < gap_below: 
        if i < r_above + r_below: 
         yield pad(' \\', 2 * gap_above - 1, line) 
        else: 
         spaces = 2 * (r_above + gap_above + gap_below - i - 1) 
         yield pad(' \\', spaces, line) 
       else: 
        yield ' ' + line 
     return (r_above + gap_above, gap_below + l_below, lines()) 
    def descend(left, t): 
     if leaf(t): 
      if left: 
       return (1, 0, [t]) 
      else: 
       return (0, 1, [t]) 
     else: 
      l, r = t 
      return merge(descend(True, l), descend(False, r)) 
    def flatten(t): 
     above, below, lines = t 
     for (i, line) in enumerate(lines): 
      if i < above: yield (' ' * (above - i - 1)) + line 
      else: yield (' ' * (i - above)) + line 
    return '\n'.join(flatten(descend(True, t))) 


if __name__ == '__main__': 
    for n in range(1,20,3): 
     tree = random(n) 
     print(format(tree)) 

Вот пример вывода:

  _/rrrr 
     _/ \_/rrrlr 
    /\ \rrrll 
    _/ \_/rrlr 
    /\  \rrll 
/ \ _/rlrr 
/ \_/ \rlrl 
_/  \_/rllr 
\   \_/rlllr 
    \   \rllll 
    \  _/lrrr 
    \  _/ \lrrl 
    \ /\_/lrlr 
     \_/ \lrll 
     \ _/llrr 
     \_/ \llrl 
      \_/lllr 
      \_/llllr 
       \lllll 

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

   _/rrrrr 
      _/ \rrrrl 
      _/ \rrrl 
     _/ \_/rrlr 
     /\ \rrll 
    / \_/rlr 
    / \rll 
    /  /lrrr 
    /  _/ _/lrrlrr 
/ /\_/ \lrrlrl 
/ / \lrrll 
_/  _/  _/lrlrrr 
\ /\ _/ \lrlrrl 
    \ / \_/ \lrlrl 
    \_/  \lrll 
    \  _/llrrr 
     \ _/ \llrrl 
     \_/ \llrl 
     \lll 

Это «очевидный» рекурсивный алгоритм - дьявол находится в деталях. Легче всего писать без «_», что делает логику несколько более сложной.

Возможно, единственным «прозрением» является gap_above = l_above - это говорит о том, что правая «рука» имеет длину правой части левого поддерева (вам нужно будет прочитать это несколько раз). Это делает вещи относительно сбалансированными. См. Асимметричный пример выше.

Хороший способ понять вещи более подробно - это изменить подпрограмму pad, чтобы взять символ вместо ' ' и дать различный символ для каждого вызова. Затем вы можете точно определить, какая логика сгенерировала это пространство. Это то, что вы получите с помощью A. B, C и D для звонков на площадку сверху вниз, сверху (очевидно, нет никакого символа, когда объем пространства равен нулю):

   _/rrrr 
      /\rrrl 
      _/B _/rrlrr 
     /\_/ \rrlrl 
     /AA \rrll 
     _/BBB _/rlrrr 
    /\DD _/ \rlrrl 
    /AA \_/ \_/rlrlr 
    /AAAA \C \rlrll 
    /AAAAAA \_/rllr 
_/AAAAAAAA \rlll 
\DDDDDDDD _/lrrrr 
    \DDDDDD _/ \lrrrl 
    \DDDD/\lrrl 
    \DD _/B _/lrlrr 
    \_/ \_/ \lrlrl 
     \C \lrll 
     \_/llr 
      \lll 

Там больше объяснений here (хотя дерево очень немного отличается).

+0

Красивая! Пожалуйста, сделайте ссылку на сообщение в блоге. Одна цель заключается в том, чтобы иметь возможность контролировать строку, используемую для - в каждой ветви, и для них быть переменной длины. – Will

+0

сообщение на http://www.acooke.org/cute/Printingbi0.html - это очень похожий код, но без «_» и с дополнительными комментариями. вы можете решить, как добавить произвольную строку, сравнив их. –

2

сделать структуру представления, вовлекая массив строк и номер строки из «лепесток».

Rep (лист) является [0, магнезии (значение листа)]

Rep (неконечный), учитывая top = nonleaf.left и bottom = nonleaf.right:

Pad каждая строка Rep (вверху) с пробелами, если выше лепесток волчка , или с косой чертой в соответствующем положении, если ниже. Аналогично, проложите каждую строку rep (внизу) пробелами, если лепесток внизу внизу, или с обратной косой чертой в соответствующем положении, если указано выше. repr (nonleaf) - это [высота вершины, заполненные строки сверху, а затем заполненные строки снизу].

Пример:

rep(a): [0, ["a"]] 
rep(b): [0, ["b"]] 
rep(ab): [1, ["/"+"a", "\"+"b"]] 
rep(c): [0, ["c"]] 
rep(d): [0, ["d"]] 
rep(cd): [1, ["/"+"c", "\"+"d"]] 
rep(e): [0, ["e"]] 
rep(cde): [2, [" "+"/c", "/" + "\d", "\" + "e"]] 
rep(abcde): [2, [" "+"/a", "/"+"\b", "\ "+" /c", " \" + "/\d", " " + "\e"]] 

Обратите внимание, что в верхней части корпуса, ширина прокладки является количество линий ниже лепестка; в нижнем случае ширина прокладки соответствует лепестку. Таким образом, в случае (abcde) вершина имеет 2 строки и лепесток 1, поэтому заполнение равно (2 - 1 == 1) одного символа; внизу есть лепесток 2, поэтому отступы составляют 2 символа.

Если вы хотите, вы также можете добавить «_» на каждом нелибе в строке (petal-1) th (и дополнительное пространство для всех остальных линий).

Очевидно, что это не код («\» - это синтаксическая ошибка, ожидающая того, что это произойдет), но отсюда не слишком сложно реализовать.

0

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

/ 
\/ 
\/ 
    \/ 
    \ 

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

/
/\/ 
/\/ 
\ \/ 
\ \ 
    \ 

Основная идея - начать с листьев. Они тривиальны. Затем определите способ агрегирования двух поддеревьев, поскольку они имеют различное количество строк и другую позицию корневого узла поддерева.

+0

Его подход, но разве вы не замаскируете жесткий часть?;) – Will

+0

Ну, я дал вам основные идеи: от одного до корня, отслеживая размер и поддерево корневой позиции. Вы должны сами выяснить строгие операции. У меня нет готового кода. Вот как я решил это 10 лет назад при построении деревьев генеалогии, выведя исходный постскрипт. Даже если бы я смог выкопать этот код, это было бы бесполезно для вас. –

0

Вот хороший вбок дерево, которое только что помог мне в отладке проекта: http://www.acooke.org/cute/ASCIIDispl0.html

Результаты напоминают макет каталога плагина VIM NERDtree, если вы видели.

Вот мое повторное внедрение в качестве способа __str__ в бинарном дереве:

def __str__(self): 
    """Recursive __str__ method of an isomorphic node.""" 
    # Keep a list of lines 
    lines = list() 
    lines.append(self.name) 
    # Get left and right sub-trees 
    l = str(self.left).split('\n') 
    r = str(self.right).split('\n') 
    # Append first left, then right trees 
    for branch in l, r: 
     # Suppress Pipe on right branch 
     alt = '| ' if branch is l else ' ' 
     for line in branch: 
      # Special prefix for first line (child) 
      prefix = '+-' if line is branch[0] else alt 
      lines.append(prefix + line) 
    # Collapse lines 
    return '\n'.join(lines) 
Смежные вопросы