比爾.米爾node
當我須要爲某個項目繪製一些樹時,我認爲繪製整齊樹木會有一個經典而簡單的算法。我發現的更有趣得多:樹佈局不只是一個NP徹底問題1,但樹繪圖算法背後有一個漫長而有趣的歷史。我將使用樹繪圖算法的歷史來逐一介紹核心概念,使用它們來構建一個完整的O(n)算法,以繪製一顆迷人的樹。python
這裏有什麼問題?git
圖1github
給定一棵樹T,咱們要作的就是繪製它,這樣觀衆就會發現它頗有吸引力。本文中介紹的每種算法的目標都是爲樹的每一個節點分配一個(x,y)座標,以便在算法運行後將其繪製到屏幕上或打印出來。算法
爲了存儲樹繪圖算法的結果,咱們將建立一個DrawTree數據結構來鏡像咱們正在繪製的樹; 咱們惟一假定的是每一個樹節點均可以迭代其子節點。清單1中能夠找到DrawTree的基本實現。數據結構
class DrawTree(object):app
def __init __(self,tree,depth = 0):函數
self.x = -1 工具
self.y = depth 佈局
self.tree = tree
self.children = [DrawTree(t,depth + 1)for t in tree]
隨着咱們方法的複雜性增長,DrawTree的複雜性也會增長。如今,它只將-1指定給每一個節點的x座標,將節點的深度指定給其y座標,並存儲對當前樹的根的引用。而後經過遞歸地爲每一個節點建立一個DrawTree來創建該節點的子節點列表。經過這種方式,咱們構建了一個DrawTree,它會封裝要繪製的樹並將繪圖特定的信息添加到每一個節點。
隨着咱們在本文中貫徹執行更好的算法,咱們將利用咱們每一個人的經驗幫助咱們產生有助於咱們構建下一個的原則。雖然生成一個「有吸引力」的樹圖是品味的問題,但這些原則將有助於指導咱們改進程序的輸出。
一開始,有Knuth
咱們將要製做的特定類型的繪圖是根部位於頂部,其子位於其下的位置等等。這種類型的圖表,以及由此產生的這類問題,主要歸功於Donald Knuth 2,咱們將從中提出咱們的前兩個原則:
原則1:樹的邊不該該相互交叉。
原則2:相同深度的全部節點應繪製在同一水平線上。這有助於明確樹的結構。
圖2
Knuth算法具備簡單和快速的優勢,但它只適用於二叉樹,它能夠產生一些至關畸形的圖形。它是一個簡單的序樹的遍歷,與被用做x變量,則在每一個節點增長一個全局計數器。清單2中的代碼演示了這種技術。
i = 0
def knuth_layout(tree, depth):
if tree.left_child:
knuth_layout(tree.left_child, depth+1)
tree.x = i
tree.y = depth
i += 1
if tree.right_child:
knuth_layout(tree.right_child, depth+1)
從圖2能夠看出,該算法生成的樹知足原則1,但不是特別有吸引力。你也能夠看到Knuth圖將會很是快速地擴展,由於即便樹可能顯着變窄,它們也不會重用x座標。爲了不這種浪費空間,咱們將介紹第三個原則:
原則3:樹木應儘量狹窄。
在咱們繼續研究一些更高級的算法以前,中止並贊成咱們將在本文中使用的術語多是一個好主意。首先,咱們將在描述數據節點之間的關係時使用家族樹的隱喻。一個節點能夠有下面的孩子,左邊或右邊的兄弟姐妹和上面的父親。
咱們已經討論過樹遍歷了,咱們也將討論前序遍歷和後序遍歷。好久之前,您可能在「數據結構」測試中看到了這三個術語,但除非您一直在玩樹最近,他們可能變得有點朦朧。
遍歷類型簡單地決定了咱們在給定節點上執行處理時須要作什麼。中序遍歷,如上面的Knuth算法,只適用於二叉樹,並意味着咱們處理左孩子,而後再處理當前節點,最後右子。後序遍歷意味着咱們處理當前節點,那麼它的全部孩子,後序遍歷簡直是相反的。
最後,您可能已經在以前看到過大O符號的概念,以表示算法運行時間的大小順序。在這篇文章中,咱們將快速放鬆地使用它做爲一個簡單的工具來區分可接受的運行時間和不可接受的運行時間。若是已經在它的主迴路的算法頻繁遍歷其中一個節點的全部孩子,咱們要調用它O(n^2)
,或二次。除此以外,咱們將稱之爲O(n)或線性。若是您想了解更多詳細信息,本文結尾部分引用的論文更多地介紹了這些算法的運行時特性。
圖3
Charles Wetherell和Alfred Shannon 3於1979年,Knuth在提出樹佈局問題8年後,引入了一整套創新技術。首先,他們展現瞭如何生成知足前三項原則的最小寬度樹。簡單地維護每一行上的下一個可用插槽,之後序遍歷樹,爲該插槽分配一個節點,並增長插槽計數器,如清單3所示。
nexts = [0] * maximum_depth_of_tree
def minimum_ws(tree, depth=0):
tree.x = nexts[depth]
tree.y = depth
nexts[depth] += 1
for c in tree.children:
minimum_ws(tree, c)
儘管它符合咱們全部的原則,但也許你會贊成產出是醜陋的。即便是像圖3那樣的簡單例子,也很難快速肯定節點之間的關係,整個結構彷佛都是一塊兒鬆散的。如今是咱們介紹另外一個有助於改善Knuth樹和最小寬度樹的原則的時候了:
原則4:父母應該集中在孩子身上。
圖4
到目前爲止,咱們已經可以用很是簡單的算法來繪製樹,由於咱們並不須要考慮本地情境; 咱們依靠全局計數器來避免節點彼此重疊。爲了知足父母應該以孩子爲中心的原則,咱們須要考慮每一個節點的本地情境,所以須要一些新的策略。
Wetherell和Shannon介紹的第一個策略是從底部開始構建樹,而後對樹進行後序遍歷,而不是像清單2那樣從頂部開始,或者像清單3那樣從中間開始。一旦你看到這樣樹,居中父母是一個簡單的操做:簡單地把它的孩子的x座標分紅兩半。
可是,咱們必須記住,在構建時要留意樹的左側。圖4顯示了樹的右側已經被推出到右側以容納左側的狀況。爲了改善這種分離,Wetherell和Shannon維護了列表2中引入的下一個可用點的陣列,可是若是將父項居中會致使樹的右側與左側重疊,則僅使用下一個可用點。
在咱們開始查看更多代碼以前,讓咱們仔細看看咱們自下而上構建樹的後果。若是它是一片葉子,咱們會給每一個節點下一個可用的x座標,若是它是一個分支,則將它放在子節點上方。可是,若是將分支居中會致使分支與樹的另外一部分發生衝突,咱們須要將分支移到正確的位置以免衝突。
當咱們將分支移到右邊時,咱們必須移動它的全部子項,不然咱們將失去咱們一直努力維護的中心父節點。很容易想出一個簡單的函數來將分支及其子樹右移一些空間:
def move_right(branch, n):
branch.x += n
for c in branch.children:
move_right(c, n)
它有效,但提出了一個問題。若是咱們使用這個函數向右移動一個子樹,咱們將在遞歸內部(放置節點)進行遞歸(移動樹),這意味着咱們將有一個低效率的算法,它可能會在時間O (N ^ 2)。
爲了解決這個問題,咱們會給每一個節點一個額外的成員mod
。當咱們到達須要用n
空格向右移動的分支時,咱們將添加n
到其x
座標和其mod
值,而且愉快地繼續放置算法。由於咱們正在從下往上移動,因此咱們沒必要擔憂咱們的樹木的底部會發生衝突(咱們已經代表它們不是),咱們將等到稍後將它們移動到右邊。
一旦第一次樹遍歷發生,咱們運行第二次樹遍歷將分支移動到右側,須要將其移到右側。因爲咱們將訪問每一個節點一次並僅對其執行算術運算,所以咱們能夠確定,這個遍歷將是O(n),就像第一個同樣,而且它們一塊兒也將是O(n)。
清單5中的代碼演示了父節點的居中和使用mod值來提升代碼的效率。
from collections import defaultdict
class DrawTree(object):
def __init__(self, tree, depth=0):
self.x = -1
self.y = depth
self.tree = tree
self.children = [DrawTree(t, depth+1) for t in tree]
self.mod = 0
def layout(tree):
setup(tree)
addmods(tree)
return tree
def setup(tree, depth=0, nexts=None, offset=None):
if nexts is None: nexts = defaultdict(lambda: 0)
if offset is None: offset = defaultdict(lambda: 0)
for c in tree.children:
setup(c, depth+1, nexts, offset)
tree.y = depth
if not len(tree.children):
place = nexts[depth]
tree.x = place
elif len(tree.children) == 1:
place = tree.children[0].x - 1
else:
s = (tree.children[0].x + tree.children[1].x)
place = s / 2
offset[depth] = max(offset[depth], nexts[depth]-place)
if len(tree.children):
tree.x = place + offset[depth]
nexts[depth] += 2
tree.mod = offset[depth]
def addmods(tree, modsum=0):
tree.x = tree.x + modsum
modsum += tree.offset
for t in tree.children:
addmods(t, modsum)
儘管在不少狀況下它確實產生了很好的結果,但清單5能夠生成一些破損的樹,好比圖5中的樹(可悲的是,已經在時間的流逝中消失了)。解釋Wetherell-Shannon算法產生的樹的另外一個困難在於,當放置在樹中的不一樣點處時,相同的樹結構能夠被不一樣地繪製。爲了不這種狀況,咱們會從Edward Reingold和John Tilford的論文中偷取原理4:
原則5:不管樹怎樣都應該繪製成同一棵子樹。
儘管這可能會擴大咱們的圖紙,但這一原則將有助於使它們傳達更多信息。這也有助於簡化樹的自底向上遍歷,由於它的一個後果是,一旦咱們找出了子樹的x座標,咱們只須要將它做爲一個單元向左或向右移動便可。
這是清單6中實現的算法概述:
•對樹進行後序遍歷
•若是節點是葉子,則給它一個0的x座標
•不然,將其右邊的子樹儘量靠近左邊而不發生衝突
•使用與先前的算法在O(n)時間內移動樹
•將節點放在其子節點的中間位置
•執行樹的第二步,將
累加的mod值添加到x座標
這個算法很簡單,但要執行它,咱們須要引入一些複雜性。
圖6
樹的輪廓是樹的一側的最大或最小座標的列表。在圖6中,有一棵左樹和一棵右樹,每一個節點的x座標重疊。若是咱們沿着左邊的樹的左邊追蹤每一個層的最小x座標,咱們就獲得[1,1,0],咱們稱之爲樹的左邊輪廓。若是咱們沿着右邊走,從每一層取最右邊的x座標,咱們獲得[1,1,2],這是樹的右邊輪廓。
爲了找到右邊樹的左邊輪廓,咱們再次取每層的最左邊節點的x座標,給咱們[1,0,1]。這一次,輪廓有一個有趣的特性,並不是全部節點都以父子關係鏈接; 第二層的0不是第三層的1的父層。
若是咱們按照清單6加入這兩棵樹,咱們能夠找到左樹的右輪廓和右樹的左輪廓。而後咱們能夠很容易地找到咱們須要的最小量,將右邊的樹推向右邊,這樣它就不會與左邊的樹重疊。清單7給出了一個簡單的方法。
from operator import lt, gt
def push_right(left, right):
wl = contour(left, lt)
wr = contour(right, gt)
return max(x-y for x,y in zip(wl, wr)) + 1
def contour(tree, comp, level=0, cont=None):
if not cont:
cont = [tree.x]
elif len(cont) < level+1:
cont.append(tree.x)
elif comp(cont[level], tree.x):
cont[level] = tree.x
for child in tree.children:
contour(child, comp, level+1, cont)
return cont
若是咱們在圖6的樹上運行清單7中的程序push_right()
,咱們將獲得[1,1,2]做爲左樹的右輪廓,[1,0,1]做爲右樹的左輪廓。而後咱們比較這些列表以找到它們之間的最大空間,併爲填充添加一個空格。在圖6的狀況下,將右側樹向右推2個空格將防止它與左側樹重疊。
使用清單7中的代碼,咱們找到了正確的值以代表咱們構建正確的樹,但爲此咱們必須掃描兩個子樹中的每一個節點,以得到所需的輪廓。因爲它極可能是O(n ^ 2)操做,所以Reingold和Tilford引入了一個混淆稱爲線程的概念,這根本不像用於並行執行的線程。
圖7
線程是一種經過在輪廓上的節點之間建立連接(若是其中一個不是另外一個的子節點)來減小掃描其輪廓的子樹所花費的時間的方法。在圖7中,虛線表示線程,而實線表示父子關係。
咱們還能夠利用這樣一個事實,即若是一棵樹比另外一棵樹深,咱們只須要降低到更短的樹。任何比這更深的東西都不會影響兩棵樹之間必要的分離,由於它們之間不會有任何衝突。
使用線程而且只須要遍歷咱們須要的深度,咱們就能夠獲得樹的輪廓,並使用清單8中的過程以線性時間設置線程。
def nextright(tree):
if tree.thread: return tree.thread
if tree.children: return tree.children[-1]
else: return None
def nextleft(tree):
if tree.thread: return tree.thread
if tree.children: return tree.children[0]
else: return None
def contour(left, right, max_offset=0, left_outer=None, right_outer=None):
if not left_outer:
left_outer = left
if not right_outer:
right_outer = right
if left.x - right.x > max_offset:
max_offset = left.x - right.x
lo = nextleft(left)
li = nextright(left)
ri = nextleft(right)
ro = nextright(right)
if li and ri:
return contour(li, ri, max_offset, lo, ro)
return max_offset
很容易看到,該過程僅訪問正在掃描的子樹的每一個級別上的兩個節點。這篇論文有一個很好的證據代表這是在線性時間內發生的; 若是你有興趣,我建議你閱讀它。
清單8給出的輪廓過程整潔快速,但它不適用於咱們以前討論的mod技術,由於節點的實際x值是節點的x值加上從自己到根的路徑上全部修改符的總和。爲了處理這種狀況,咱們須要給輪廓算法增長一些複雜度。
咱們須要作的第一件事是保留兩個額外的變量,即左子樹上的修飾符的總和和右子樹上的修飾符的總和。這些和是計算輪廓上每一個節點的實際位置所必需的,這樣咱們能夠檢查它是否與相反一側的節點發生衝突。參見清單9。
def contour(left, right, max_offset=None, loffset=0, roffset=0, left_outer=None, right_outer=None):
delta = left.x + loffset - (right.x + roffset)
if not max_offset or delta > max_offset:
max_offset = delta
if not left_outer:
left_outer = left
if not right_outer:
right_outer = right
lo = nextleft(left_outer)
li = nextright(left)
ri = nextleft(right)
ro = nextright(right_outer)
if li and ri:
loffset += left.mod
roffset += right.mod
return contour(li, ri, max_offset,
loffset, roffset, lo, ro)
return (li, ri, max_offset, loffset, roffset, left_outer, right_outer)
咱們須要作的另外一件事是在退出時返回函數的當前狀態,以便咱們能夠在線程節點上設置適當的偏移量。掌握這些信息後,咱們準備查看使用清單8中的代碼的函數,將兩棵樹儘量緊密地放在一塊兒:
def fix_subtrees(left, right):
li, ri, diff, loffset, roffset, lo, ro \
= contour(left, right)
diff += 1
diff += (right.x + diff + left.x) % 2
right.mod = diff
right.x += diff
if right.children:
roffset += diff
if ri and not li:
lo.thread = ri
lo.mod = roffset - loffset
elif li and not ri:
ro.thread = li
ro.mod = loffset - roffset
return (left.x + right.x) / 2
在咱們運行輪廓過程以後,咱們將左右樹之間的最大差別加1,以使它們不會相互衝突,若是它們之間的中點是奇數,則再添加1。這讓咱們保留了一個便利的測試屬性 - 全部節點都具備整數x座標,並且不會下降精度。
而後咱們將右邊的樹移動到右邊。請記住,咱們都將diff添加到x座標並將其保存到mod值的緣由是mod值僅適用於當前節點下面的節點。若是右子樹有多個節點,咱們將diff添加到roffset中,由於右節點的全部子節點都將移動到右邊。
若是樹的左側比右側更深,反之亦然,咱們須要設置一個線程。咱們只需檢查一側的節點指針是否比另外一側的節點指針前進得更遠,若是已經存在,則將線程從較淺的樹的外部設置到較深的樹的外部。
爲了正確處理咱們以前談到的mod值,咱們須要在線程節點上設置一個特殊的mod值。因爲咱們已經更新了右側偏移值以反映右側樹的向右移動,所以咱們須要在此處執行的操做是將線程節點的mod值設置爲更深樹的偏移量與其自身之間的差值。
如今咱們已經有了代碼來查找樹的輪廓並儘量地將兩棵樹放在一塊兒,咱們能夠輕鬆實現上述算法。我提供其他的代碼而沒有評論:
def layout(tree):
return addmods(setup(dt))
def addmods(tree, mod=0):
tree.x += mod
for c in tree.children:
addmods(c, mod+tree.mod)
return tree
def setup(tree, depth=0):
if len(tree.children) == 0:
tree.x = 0
tree.y = depth
return tree
if len(tree.children) == 1:
tree.x = setup(tree.children[0], depth+1).x
return tree
left = setup(tree.children[0], depth+1)
right = setup(tree.children[1], depth+1)
tree.x = fix_subtrees(left, right)
return tree
如今咱們終於獲得了一個繪製二叉樹的算法,它知足了咱們的原則,在通常狀況下看起來很好,而且在線性時間內運行,因此考慮如何將它擴展到具備任意數量子級的樹上是很天然的。若是你跟着我走了這麼遠,你可能認爲咱們應該採用咱們剛剛定義的美妙算法,並將其應用於節點的全部子節點。
先前算法在n元樹上工做的擴展可能以下所示:
該算法工做,速度快,但存在一個簡單的問題。它將節點的全部子樹放置在儘量遠的地方。若是右邊的一個節點與左邊的一個節點發生衝突,那麼它們之間的樹將所有填充到右邊,如圖7所示。讓咱們採用樹圖的最後一個原則來解決這個問題:
原則6:父節點的子節點應均勻分佈。
圖8
爲了對稱地繪製一個n元樹,而且很快,咱們將須要迄今爲止開發的全部技巧加上一些新的技巧。感謝Christoph Buchheim等人5最近發表的一篇論文,咱們已經掌握了全部的工具,而且仍然可以以線性時間繪製咱們的樹。
要修改上面的算法以符合原則6,咱們須要一種方法來隔離兩棵相互衝突的大樹之間的樹。最簡單的方法是,每當兩棵樹發生衝突時,將可用空間除以樹的數量,而後移動每棵樹使其與其兄弟姐妹分開。例如,在圖7中,右邊和左邊的大樹之間有一段距離n,它們之間有三棵樹。若是咱們簡單地將中間的第一棵樹n/3與左邊的樹分開,下一個n/3遠離那棵樹,等等,咱們就會有一棵知足原則6的樹。
到目前爲止,咱們已經看到了這篇文章中的一個簡單的算法,但咱們發現它並不合適,而這一次也不例外。若是咱們必須改變每兩棵相互衝突的樹之間的全部樹,那麼咱們冒着在咱們的算法中引入O(n ^ 2)操做的風險。
對於這個問題的解決方法相似於咱們前面介紹的移位問題的修復方法mod。每次發生衝突時,咱們都不須要將中間的每一個子樹都移動到中間,咱們將保存中間須要移動樹的值,而後在放置節點的全部子節點後應用這些移位。
爲了找出咱們想要移動中間節點的正確值,咱們須要可以找到衝突的兩個節點之間的樹數。當咱們只有兩棵樹時,顯然發生的任何衝突都是在左邊和右邊的樹之間。當可能有多少樹時,找出哪棵樹致使衝突成爲一個挑戰。
爲了迎接這個挑戰,咱們將引入一個default_ancestor變量,並將另外一個成員添加到咱們稱之爲的樹形數據結構中ancestor。祖先節點或者指向它本身或者指向它所屬的樹的根。當咱們須要找到一個節點屬於哪棵樹時,咱們將使用祖先成員(若是已設置),可是會回落到指向的樹上default_ancestor。
當咱們放置節點的第一個子樹時,咱們只需將default_ancestor設置爲指向該子樹,並假定由下一個樹形成的任何衝突都與第一個樹相沖突。在咱們放置第二個子樹以後,咱們區分兩種狀況。若是第二個子樹的深度小於第一個子樹的深度,咱們遍歷它的右邊界,將祖先成員設置爲等於第二棵樹的根。不然,第二棵樹比第一棵樹大,這意味着與下一棵樹的任何衝突都與第二棵樹放置在一塊兒,所以咱們只需將default_ancestor設置爲指向它便可。
因此,不用多說,如Buchheim提出的用於佈置富有吸引力的樹的O(n)算法的python實如今清單12中。
class DrawTree(object):
def __init__(self, tree, parent=None, depth=0, number=1):
self.x = -1.
self.y = depth
self.tree = tree
self.children = [DrawTree(c, self, depth+1, i+1)
for i, c
in enumerate(tree.children)]
self.parent = parent
self.thread = None
self.offset = 0
self.ancestor = self
self.change = self.shift = 0
self._lmost_sibling = None
#this is the number of the node in its group of siblings 1..n
self.number = number
def left_brother(self):
n = None
if self.parent:
for node in self.parent.children:
if node == self: return n
else: n = node
return n
def get_lmost_sibling(self):
if not self._lmost_sibling and self.parent and self != \
self.parent.children[0]:
self._lmost_sibling = self.parent.children[0]
return self._lmost_sibling
leftmost_sibling = property(get_lmost_sibling)
def buchheim(tree):
dt = firstwalk(tree)
second_walk(dt)
return dt
def firstwalk(v, distance=1.):
if len(v.children) == 0:
if v.leftmost_sibling:
v.x = v.left_brother().x + distance
else:
v.x = 0.
else:
default_ancestor = v.children[0]
for w in v.children:
firstwalk(w)
default_ancestor = apportion(w, default_ancestor,
distance)
execute_shifts(v)
midpoint = (v.children[0].x + v.children[-1].x) / 2
ell = v.children[0]
arr = v.children[-1]
w = v.left_brother()
if w:
v.x = w.x + distance
v.mod = v.x - midpoint
else:
v.x = midpoint
return v
def apportion(v, default_ancestor, distance):
w = v.left_brother()
if w is not None:
#in buchheim notation:
#i == inner; o == outer; r == right; l == left;
vir = vor = v
vil = w
vol = v.leftmost_sibling
sir = sor = v.mod
sil = vil.mod
sol = vol.mod
while vil.right() and vir.left():
vil = vil.right()
vir = vir.left()
vol = vol.left()
vor = vor.right()
vor.ancestor = v
shift = (vil.x + sil) - (vir.x + sir) + distance
if shift > 0:
a = ancestor(vil, v, default_ancestor)
move_subtree(a, v, shift)
sir = sir + shift
sor = sor + shift
sil += vil.mod
sir += vir.mod
sol += vol.mod
sor += vor.mod
if vil.right() and not vor.right():
vor.thread = vil.right()
vor.mod += sil - sor
else:
if vir.left() and not vol.left():
vol.thread = vir.left()
vol.mod += sir - sol
default_ancestor = v
return default_ancestor
def move_subtree(wl, wr, shift):
subtrees = wr.number - wl.number
wr.change -= shift / subtrees
wr.shift += shift
wl.change += shift / subtrees
wr.x += shift
wr.mod += shift
def execute_shifts(v):
shift = change = 0
for w in v.children[::-1]:
w.x += shift
w.mod += shift
change += w.change
shift += w.shift + change
def ancestor(vil, v, default_ancestor):
if vil.ancestor in v.parent.children:
return vil.ancestor
else:
return default_ancestor
def second_walk(v, m=0, depth=0):
v.x += m
v.y = depth
for w in v.children:
second_walk(w, m + v.mod, depth+1, min)
我在本文中略過了一些內容,僅僅是由於我認爲嘗試並向呈現的最終算法呈現合乎邏輯的進展比使用純代碼重載文章更重要。若是您想了解更多詳細信息,或者查看我在各類代碼清單中使用的樹形數據結構,能夠訪問http://github.com/llimllib/pymag-trees/下載每種算法的源代碼,一些基本測試以及用於生成本文圖形的代碼。
1 K. Marriott, NP-Completeness of Minimal Width Unordered Tree Layout, Journal of Graph Algorithms and Applications, vol. 8, no. 3, pp. 295-312 (2004). http://www.emis.de/journals/JGAA/accepted/2004/MarriottStuckey2004.8.3.pdf
2 D. E. Knuth, Optimum binary search trees, Acta Informatica 1 (1971)
3 C. Wetherell, A. Shannon, Tidy Drawings of Trees, IEEE Transactions on Software Engineering. Volume 5, Issue 5
4 E. M. Reingold, J. S Tilford, Tidier Drawings of Trees, IEEE Transactions on Software Engineering. Volume 7, Issue 2
5 C. Buchheim, M. J Unger, and S. Leipert. Improving Walker's algorithm to run in linear time. In Proc. Graph Drawing (GD), 2002. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.16.8757