트리 구조(forest/qtree)

forest의 매뉴얼은 저자의 고백으로 시작합니다. 오랫동안 qtreesynttree를 고맙게 써 왔지만, 시중의 도구들에는 같은 불만이 있었다는 것입니다. 트리가 때때로 가로로 너무 벌어진다는 점입니다. 이 한 가지 짜증에서 나온 것이, 지금 LaTeX에서 트리 구조(구문 트리, 디렉터리 도표, 결정 트리, 확률 트리)를 그릴 때의 첫 번째 선택지입니다. 그렇다고 고참인 qtree·tikz-qtree가 물러난 것은 아니며, 둘의 대괄호 표기는 매우 닮았지만 딱 한 군데에서 결정적으로 다릅니다. 그 한 군데를 모르고 옮기면 오류 하나 없이 .S라는 이름의 노드가 생깁니다. 이 페이지는 그 차이에서 시작해 도구 선택까지를 다룹니다.

브래킷 표기 — 좌표를 하나도 쓰지 않고 트리를 만들기

트리를 그리는 도구가 하나같이 괄호의 중첩을 입력으로 받는 이유는, 트리 자체가 재귀 구조이기 때문입니다. 뿌리가 있고, 그 아래 자식이 달리고, 자식은 다시 손자를 갖습니다. 이 중첩을 [ ]의 중첩으로 그대로 적으면, 어느 노드를 어디에 둘지 하는 좌표 계산은 전부 도구 쪽 일이 됩니다. forest라면 [S [NP] [VP]], qtree라면 \Tree [.S [.NP ] [.VP ] ]입니다. 적고 있는 것은 배치가 아니라 구조입니다.

선택지는 사실상 셋입니다. TikZ와 pgfkeys 위에 세워진 forest(Sašo Živanović, TeX Live 2024에서는 v2.1.5)가 가장 유연하고 자동 정렬도 해 줍니다. 언어학 구문 트리를 최소한의 타이핑으로 그리는 고전은 qtree(v3.1b)와, 같은 표기를 TikZ로 다시 구현한 tikz-qtree(David Chiang, v1.2)입니다. 그리고 패키지를 늘리고 싶지 않은 몇 단짜리 트리라면 TikZ 본체의 trees 라이브러리(childnode)로 충분합니다. 아래에서는 먼저 두 브래킷 표기의 차이를 정리하고, 이어서 각각을 살펴봅니다.

[.S [.NP ]][S [NP]]의 차이 — qtree의 점은 forest에서 통하지 않습니다

결론부터 말하면, qtree에서 앞머리의 마침표는 “여기가 가지가 갈라지는 노드다”라는 표시이고, forest에서 마침표는 그냥 한 글자입니다. qtree[.S ... ]는 “S라는 비말단 노드”를 뜻하고, 마침표 없는 맨 단어는 잎(말단)이 됩니다. forest에는 그런 약속이 없어서, [ 바로 뒤부터 다음 []까지가 통째로 라벨입니다. 그래서 forest[.S [.NP Kim ]]를 주면 — 오류가 전혀 나지 않은 채로 — 라벨이 .S.NP Kim인 노드 둘이 만들어집니다. Kim은 잎이 되지 못하고 .NP와 같은 상자에 들어가 버립니다.

latex
% qtree / tikz-qtree: a leading dot marks a branching node,
% and a closing ] must be preceded by whitespace
\Tree [.S [.NP Kim ] [.VP [.V saw ] [.NP Lee ] ] ]

% forest: no dots, and every word is a node of its own
\begin{forest}
[S
  [NP [Kim]]
  [VP [V [saw]] [NP [Lee]]]
]
\end{forest}

반대 방향의 실수는 훨씬 요란하게 실패합니다. qtree닫는 괄호 ] 바로 앞에 공백을 요구하므로, [.NP Kim]처럼 붙여 쓰면 ! LaTeX Error: \begin{tabular} on input line 5 ended by \end{document}.라는, 표를 하나도 쓰지 않았는데 tabular를 들먹이는 알 수 없는 오류가 납니다. tikz-qtree에 같은 짓을 하면 ! Emergency stop.이 나고 PDF조차 나오지 않습니다. forest에 익숙한 손이 qtree 원고를 만질 때 가장 먼저 걸리는 곳이 여기입니다. 요컨대 두 표기법 모두 상대의 표기를 조용히 받아 주지 않으므로, 파일을 옮길 때는 패키지와 표기법을 한 쌍으로 확인하세요.

forest의 자동 레이아웃과, 간격을 손으로 조일 때

forest의 핵심 기능은 이 페이지 첫머리의 “너무 벌어지는 트리”에 대한 답, 곧 자동 패킹입니다. 각 노드를 그것이 지배하는 부분 트리의 실제 너비에 따라 배치하고, 겹치지 않는 선에서 최대한 붙이므로 복잡한 트리도 그대로 볼 만한 모양이 됩니다. 직접 손대고 싶을 때는 트리 전체나 부분 트리에 for tree={...}를 적용합니다. 자주 쓰는 세 가지는 형제 사이의 가로 간격 s sep, 층(레벨) 사이의 세로 거리 l, 노드 내용 주위의 여백 inner sep입니다.

document.tex
\usepackage[linguistics]{forest}

\begin{forest}
for tree={s sep=10mm, inner sep=0, l=0}
[CP
  [C]
  [IP, for tree={s sep=20mm}
    [I, name=src]
    [VP
      [V]
      [DP, roof, name=tgt [the woman]]
    ]
  ]
]
\draw[->] (src) to[out=south west, in=south] (tgt);
\end{forest}

이 짧은 예 하나에 forest의 작법이 대체로 들어 있습니다. 처음의 for tree={...}가 트리 전체의 기본값이 되고, [IP, for tree={s sep=20mm}처럼 노드 라벨 뒤에 쉼표를 두면 그 부분 트리만 설정을 덮어쓸 수 있습니다. name=으로 노드에 이름을 붙여 두면 트리를 닫은 뒤에 \draw[->] (src) to[...] (tgt);라고 써서 이동을 나타내는 화살표를 그릴 수 있습니다. forest 환경 안에서는 TikZ 명령을 그대로 쓸 수 있기 때문입니다. 라벨 자체에 쉼표를 넣고 싶다면 {NP, PP}처럼 중괄호로 감싸 레이아웃 지시와 구별합니다. 같은 모양을 매번 쓰기 싫다면 프리앰블에서 \forestset{default preamble={for tree={...}}}로 기본값을 정의해 둘 수 있습니다.

여기서 가장 자주 걸리는 함정 하나. forest 소스에 빈 줄을 넣으면 구문 분석이 실패합니다. 빈 줄은 TeX에게 문단 구분이므로 트리 도중에 파싱이 끊기고, ! Paragraph ended before \forestOappto was complete.라는, 트리와 무관해 보이는 오류가 납니다. 긴 트리를 읽기 좋게 띄우고 싶은 마음은 이해되지만, 넣을 수 있는 것은 줄바꿈과 들여쓰기뿐이고 빈 줄은 안 됩니다. 반면 들여쓰기 깊이는 자유이니 계층은 그쪽으로 표현하세요.

linguistics와 edges — 라이브러리를 읽지 않으면 roof도 folder도 못 씁니다

forest의 기능 일부는 라이브러리로 나뉘어 있어서, \usepackage[linguistics]{forest}처럼 옵션으로 읽어 들여야 쓸 수 있습니다. 특히 걸리기 쉬운 것이 구 위에 삼각형 “지붕”을 씌우는 roof입니다. 이는 핵심 forest가 아니라 linguistics 라이브러리의 정의라서, 맨 \usepackage{forest} 상태로 [DP, roof [the woman]]이라고 쓰면 ! Package pgfkeys Error: I do not know the key '/tikz/roof'에서 멈춥니다. 마찬가지로 디렉터리 도표에서 익숙한 folderedges 라이브러리 쪽 정의입니다.

덧붙여 linguistics 라이브러리가 실제로 무엇을 하는지 알아 두면 헤맬 일이 줄어듭니다. 첫째는 sn edges입니다. forest의 기본값은 가지를 부모와 자식의 “경계끼리” 잇는데, 이를 부모의 아래쪽(south)에서 자식의 위쪽(north)으로 바꿉니다. 언어학 구문 트리에서 눈에 익은 모양이 이쪽입니다. 둘째는 베이스라인을 루트에 맞추는 것입니다. 예문 번호나 주석 행과 트리 높이가 나란해지는 것은 이 설정 덕분입니다. 셋째는 c 통어(c-command)에 관한 노드워크 정의로, c-commandedc-commanders로 그 관계에 있는 노드들을 한꺼번에 지정할 수 있습니다. edges 라이브러리 쪽은 folder 외에 가지를 직각으로 꺾는 forked edge / forked edges를 제공합니다.

qtree와 tikz-qtree — 구문 트리를 한 줄로

구문 트리 하나를 후딱 그릴 뿐이라면 \Tree 표기가 여전히 가장 짧습니다. \usepackage{qtree}를 읽고 본문에 \Tree [.S [.NP Kim ] [.VP [.V saw ] [.NP Lee ] ] ] 한 줄을 쓰면 트리가 나옵니다. 속을 보면 두 사람의 작업이 합류해 있습니다. 트리를 그리는 매크로 본체는 Jeffrey Mark Siskind의 QobiTree이고, 그것을 LaTeX에서 쓰기 편하게 만든 프런트엔드와 확장이 Alexis Dimitriadis의 것입니다. 버전 3.0b(2005년)부터는 선을 pict2e로 그리므로, DVI/PostScript 경로든 PDF 직접 출력이든 같은 결과를 얻습니다.

qtree에는 구문 트리를 위한 잔재주가 두루 들어 있습니다. 트리 안에서 NP_iN^0자동으로 수식 모드의 아래첨자·위첨자로 정리되고, X\1X$'$(X′)의 약어입니다. 구 위에 삼각형을 씌우려면 \qroof{out of style}.PP처럼 씁니다(forest의 roof와 역할은 같지만 철자가 다릅니다). 트리는 기본으로 가운데 정렬되므로, 왼쪽 정렬을 원하면 \usepackage[nocenter]{qtree}를 씁니다.

tikz-qtree(David Chiang)는 같은 \Tree 표기를 TikZ 위에서 다시 만든 것입니다. \usepackage{tikz}\usepackage{tikz-qtree}를 나란히 읽어 들이면 같은 소스가 그대로 통과합니다. 저자 자신이 꼽는 이점은 셋으로, TikZ 기본 트리와 달리 겹치지 않게 배치한다는 점, qtree에 TikZ의 기능(가지 라벨, 노드 사이 화살표)을 더한다는 점, 그리고 pst-qtree와 달리 pdfTeX와 XeTeX에서 쓸 수 있다는 점입니다. 트리 방향도 바꿀 수 있어서, tikzpicture[grow'=right]를 주면 왼쪽에서 오른쪽으로 자랍니다. 호환성은 높지만 완전하지는 않으니, 복잡한 트리를 옮길 때는 컴파일해서 눈으로 확인하세요.

document.tex
\usepackage{tikz}
\usepackage{tikz-qtree}

% sideways tree: grow to the right instead of downwards
\begin{tikzpicture}[grow'=right]
  \Tree [.S [.NP Kim ] [.VP [.V saw ] [.NP Lee ] ] ]
\end{tikzpicture}

언어학 밖의 트리 — 디렉터리 구성·결정 트리·확률 트리

forest는 언어학 전용 도구가 아닙니다. 디렉터리 구성도라면 edges 라이브러리의 foldergrow'=east 조합이 정석이며, 폴더처럼 보이는 직각 가지로 왼쪽에서 오른쪽으로 펼쳐집니다. 결정 트리·확률 트리라면 가지 자체에 숫자를 얹고 싶어집니다. forest의 가지는 TikZ의 경로이므로 edge label={node[midway,above]{0.3}}처럼 노드를 얹으면 확률이나 조건을 변 위에 그대로 적을 수 있습니다. forest에 딸린 매뉴얼에도 결정 트리 예제가 실려 있습니다.

document.tex
\usepackage[edges]{forest}

% a file hierarchy: right-angled branches, growing to the right
\begin{forest}
for tree={grow'=east, folder, s sep=1mm}
[project
  [src [main.tex] [chapters]]
  [figures [plot.pdf]]
]
\end{forest}

% a probability tree: labels ride on the edges
\begin{forest}
for tree={grow'=east, l sep=18mm, s sep=6mm, anchor=west}
[start
  [rain, edge label={node[midway,above,font=\scriptsize]{0.3}}]
  [dry,  edge label={node[midway,below,font=\scriptsize]{0.7}}]
]
\end{forest}

추가 패키지를 전혀 늘리고 싶지 않은 몇 단짜리 트리라면 TikZ 본체의 trees 라이브러리로 충분합니다. 노드를 놓은 뒤 child {...}를 이어 쓰면 그 내용이 자식으로 배치되고 부모에서 자식으로 가는 가지가 자동으로 그려집니다. child 안에는 보통 node {...}를 쓰고, 다시 child를 중첩해 손자를 만듭니다. 배치를 정하는 것은 두 거리로, 부모 층과 자식 층 사이의 세로 간격이 level distance, 옆으로 나란한 자식 사이의 간격이 sibling distance이며 둘 다 기본은 15 mm입니다. 층마다 바꾸려면 level 1/.style={sibling distance=4cm}처럼 level ⟨n⟩/.style를 쓰고, 트리 방향은 [grow=right] 등으로, 가지 모양은 edge from parent(직각으로 꺾으려면 edge from parent fork down)로 제어합니다.

latex
\usetikzlibrary{trees}

\begin{tikzpicture}[level distance=12mm, sibling distance=24mm]
  \node {root}
    child { node {a} }
    child { node {b}
      child { node {c} }
      child { node {d} }
    };   % one semicolon for the whole tree
\end{tikzpicture}

쓸 때 주의할 점은 세미콜론 ;이 트리 전체에서 딱 하나, \node ... ;의 끝에 놓인다는 것입니다. 그리고 순수 TikZ에는 forest 같은 자동 패킹이 없어서, 자식 수나 깊이가 늘면 sibling distance를 손으로 넓혀 겹침을 피하게 됩니다. 몇 단짜리 작은 트리에는 충분하지만, “간격이 내용을 따라갔으면” 하고 바라는 순간이 forest로 옮길 신호입니다.

무엇을 고를까

망설여지면 새로 그리는 트리는 forest입니다. 자동 레이아웃이 되고, 언어학부터 일반 도표까지 폭이 넓으며, 장식에는 TikZ의 모든 기능을 그대로 쓸 수 있습니다. 기존 \Tree 원고를 이어받거나 공저자가 \Tree로 써 온다면 qtree·tikz-qtree를 쓰고, 둘 중에서는 tikz-qtree가 출력 품질과 조정 여지에서 낫습니다. 의존성을 전혀 늘리고 싶지 않은 몇 단짜리 트리라면 순수 TikZ의 child로 충분합니다. 넷 모두 CTAN에 있고, foresttikz-qtreegraphics/pgf/contrib/ 아래에서 TikZ/pgf에 의존합니다. 세부 사항은 자기 컴퓨터에서 texdoc forest, texdoc qtree, texdoc tikz-qtree를 보는 것이 가장 확실합니다.

도구표기적합한 용도
forest[S [NP] [VP]](점 없음)새로 그리는 트리 전반. 자동 패킹, 언어학부터 결정 트리까지
qtree\Tree [.S [.NP Kim ] ](점과 공백 필수)가장 짧은 구문 트리. pict2e 기반으로 가벼움
tikz-qtree\Tree(qtree와 동일)같은 표기를 TikZ 품질로. 가지 라벨과 화살표도 추가 가능
tikz trees library\node {r} child {node {a}};몇 단짜리 작은 트리. 추가 패키지를 피하고 싶을 때

끝으로 실무적인 이야기 하나. 학회 발표나 강의 슬라이드에 구문 트리를 넣고 싶다는 수요가 아주 많은데, 그럴 때는 트리를 LaTeX 문서 안에서 끝내기보다 독립된 이미지로 내보내 PowerPoint나 Keynote에 붙이는 편이 빠를 때가 있습니다. foresttikz-qtree도 TikZ 위에 있으므로 standalone 클래스로 감싸면 그대로 딱 맞게 잘린 PDF나 SVG가 됩니다. 그 절차는 이미지화 페이지에 정리해 두었습니다.