2012-06-19 11 views
16

Jak wydrukować drzewo binarne na boku, aby wyglądał tak?wydrukuj drzewo binarne na swojej stronie.

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

(Prettier ASCII-art mile widziane)

Oto niektóre kodu, który nie dość pracy:

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,"")  

które wyjścia to:

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

Zakres pełzanie: byłoby to e xcellent, jeśli mógłbyś przekazać funkcję, która zwróci ciąg znaków do drukowania dowolnego węzła; w ten sposób czasami mogę drukować informacje o węzłach niezwiązanych. To, czy węzeł ma cokolwiek do drukowania, jest kontrolowane przez funkcję przekazaną jako parametr.

Oto kilka danych testowych w 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

Co próbowaliście? Mogę sobie wyobrazić, że wymaga to obliczenia właściwości drzewa (głębokość, szerokość itd.), Obliczeń układu i wygenerowania sztuki ASCII. –

+0

@SimeonVisser dodał trochę złamanego kodu – Will

+1

Patrząc na to, myślę, że powinieneś również śledzić głębokość drzewa. Mam pewien podstawowy kod na podstawie twojego zepsutego kodu, ale wygląda okropnie. Dla każdego wiersza próbuję dowiedzieć się, ile dodatkowego miejsca powinien on mieć, ale rekonstrukcja dla tego wiersza odpowiada obecnie za najniższy numer oddziału – Michael

Odpowiedz

7

Oto niektóre kodu, który implementuje generał rekurencyjnej metody opisanej w innym miejscu. Wewnętrzna reprezentacja drzewa to ciąg (liść) lub krotka (para) podnagłówków. Wewnętrzna reprezentacja pośredniego "fragmentu" węzła jest krotką (above, below, lines), gdzie above i below są liczbą linii powyżej i poniżej podstawy, a lines jest iteratorem nad każdą linią częściową (bez spacji po lewej stronie).

#!/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)) 

Oto kilka przykładów wyjścia:

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

I nieco bardziej asymetryczne, który pokazuje, być może, dlaczego ja nie linie pad z pomieszczeń w lewo, aż do końca (przez flatten). Gdyby dolna część była wyściełana po lewej stronie, część górnego ramienia przechodziła przez wyściełany obszar.

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

To "oczywisty" algorytm rekurencyjny - diabeł tkwi w szczegółach. Najłatwiej było pisać bez "_", co czyni logikę nieco bardziej złożoną.

Być może jedynym "wglądem" jest gap_above = l_above - to znaczy, że prawe "ramię" ma długość prawej strony lewego poddrzewa (musisz to przeczytać kilka razy). To sprawia, że ​​rzeczy są względnie zrównoważone. Zobacz powyższy asymetryczny przykład.

Dobrym sposobem na bardziej szczegółowe zrozumienie jest zmodyfikowanie rutyny pad w celu zabrania postaci zamiast ' ' i nadania innej postaci każdemu połączeniu. Wtedy widać dokładnie, która logika wygenerowała jaką przestrzeń. To jest to, co można dostać za pomocą A. B, C i D dla połączeń pad od góry do dołu, od góry (oczywiście nie ma znaków, gdy ilość miejsca jest zero):

   _/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 

Jest więcej wyjaśnienie here (chociaż drzewo jest bardzo nieznacznie różne).

+0

Piękny! Podaj link do posta na blogu. Jednym z celów polegających na rozciąganiu byłaby możliwość kontrolowania ciągu znaków użytego w każdym oddziale i umożliwienia im zmiany długości. – Will

+0

post na http://www.acooke.org/cute/Printingbi0.html - to bardzo podobny kod, ale bez "_" i więcej komentarzy. możesz dowiedzieć się, jak dodać dowolny ciąg przez porównanie dwóch. –

2

Zrób struktury reprezentacji, z udziałem tablicę ciągów i numer linii „płatek”.

REP (k) w [0, reprezentujący (wartość liść)]

REP (nonleaf), biorąc pod uwagę top = nonleaf.left i bottom = nonleaf.right:

Pad każda linia Rep (u góry) w przestrzeni, jeżeli wyżej płatek blatu lub z ukośnikiem w odpowiedniej pozycji, jeśli poniżej. Podobnie, umieść każdą linię powtórzenia (u dołu) spacji, jeśli znajduje się pod płatkiem dna, lub odwrotnie ukośnik w odpowiedniej pozycji, jeśli jest powyżej. repr (nonleaf) jest wtedy [wysokość górnego, wyściełane linie wierzchołka, a następnie wyściełane linie dna].

przykład:

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"]] 

Należy pamiętać, że w górnej części obudowy, szerokość wypełnienia jest liczba linii poniżej płatków; w przypadku dolnym szerokość wypełnienia odpowiada płatkowi. Tak więc, w przypadku (abcde), góra ma 2 linie i płatek 1, więc dopełnienie to (2 - 1 == 1) jeden znak; u dołu ma płatek 2, więc dopełnienie to 2 znaki.

Jeśli chcesz, możesz również dodać "_" na każdym nonleaf w (płatek-1) linii (i dodatkowe miejsce na wszystkie inne linie).

Oczywiście, nic z tego nie jest kodem ("\" jest błędem składni, który czeka), ale nie powinno być zbyt trudne do wykonania stąd.

0

Musisz podejść do tego rekursywnie i śledzić rozmiary poszczególnych poddrzew. W szczególności, gdzie jest root. Niezbalansowany drzewo może łatwo wyglądać następująco:

/ 
\/ 
\/ 
    \/ 
    \ 

Rozważmy teraz już zbudowali to drzewo, co trzeba, aby przekształcić to do następujących podczas dodawania poziomu nadrzędnego.

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

Kluczową ideą jest zacząć od liści. Są banalne. Następnie określ sposób agregowania dwóch poddrzew, biorąc pod uwagę, że mają różną liczbę linii i inną pozycję węzła głównego poddrzewa.

+0

Jest to podejście, ale czy nie jesteś typem glosowania nad twardym część?;) – Will

+0

Cóż, podałem ci kluczowe idee: liść do korzenia, śledzenie rozmiaru i położenia korzenia podtruny. Będziesz musiał sam dokładnie określić operacje ciągów. Nie mam gotowego kodu. Tak rozwiązałem to 10 lat temu, kiedy wykreślałem drzewa genealogiczne, wypisując surowy postscript. Nawet jeśli będę w stanie wykopać ten kod, będzie to dla ciebie bezużyteczne. –

0

Oto miły bokiem drzewo, które po prostu pomógł mi w debugowanie projektu: http://www.acooke.org/cute/ASCIIDispl0.html

Wyniki przypominają układ katalogów wtyczki VIM NERDtree jeśli widziałem to.

Oto moja ponowna realizacja jako __str__ sposobu w binarnym drzewie:

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)