我正在尝试在 LaTeX 中表示一个实现地图数据结构的二叉树。有人告诉我 TikZ 是实现此目的的最佳工具,但我无法实现我的目标。我觉得 TikZ...太难用了?
在探索了很多替代方案之后,这是我第一次认为我接近一个可接受的解决方案,至少对于这棵树来说是这样。
\documentclass{article}
\usepackage{tikz}
\usepackage{array}
\usetikzlibrary{arrows}
\begin{document}
\newcolumntype{x}[1]{>{\centering\arraybackslash\hspace{0pt}}p{#1}}
\newcommand{\tnode}[2]{
\begin{tabular}{x{2em}|x{2em}}
\tiny{}clave & \tiny{}dato \\
$#1$ & $#2$ \\\hline
\tiny{}izq & \tiny{}der \\
&
\end{tabular}}
\newcommand{\leftedge}{[edge from parent path = {
([xshift=0px, yshift=0px]\tikzparentnode.center)
-- (\tikzchildnode.north)}]}
\newcommand{\rightedge}{[edge from parent path = {
([xshift=0px, yshift=-0px]\tikzparentnode.center)
-- (\tikzchildnode.north)}]}
\begin{tikzpicture}[
level distance=7em,
every node/.style = {align=center, font=\ttfamily,
inner sep=0pt, draw,},
level 1/.style={sibling distance=20em},
level 2/.style={sibling distance=12em},
level 3/.style={sibling distance=6em},
thick, *->, shorten >=2px, >=latex,
]
\node (root) {\tnode{5}{15}}
child[left] { node {\tnode{1}{11}} \leftedge{}
child[right] { node {\tnode{3}{13}} \rightedge{}
child[left] { node {\tnode{2}{12}} \leftedge{}}
child[right] { node {\tnode{4}{14}} \rightedge{}}
}
}
child[right] { node {\tnode{7}{17}} \rightedge{}
child[left] { node {\tnode{6}{16}} \leftedge{}}
};
\end{tikzpicture}
\end{document}
我需要指向左子节点的箭头从相应izq
四分之一的中心出发,右子节点和 也需要同样的箭头der
。但我不知道如何在 中设置这些锚点\tnode
。因此,我决定接受使用手动设置的像素数量从众所周知的锚点(即节点中心)偏移。
但后来我发现我想要作为起始点的黑色圆圈并不以边缘起点为中心,因此移动它的圆圈位置并没有以我的目的所期望的方式固定。
我知道 TikZ 中存在多部分节点,还有一些叫做 的东西matrix
。 我\tikzmark
也知道存在(用于定义新锚点),但它似乎与 配合得不好\tabular
。
在这种绝望的时刻,我只想用我能理解的方式修复我的代码!
我读过了应该是一个解决方案,但是很抱歉,我不明白。
有没有更简单的解决方案?或者有人能帮我按照链接的图片修复我的图片吗?
谢谢,并且很抱歉我的问题质量较低。
答案1
我认为你想要类似下面的东西:
\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{arrows.meta, matrix}
\newcommand{\matrixbody}[2]{
\ttfamily\tiny clave \& \ttfamily\tiny dato \\
$#1$ \& $#2$ \\
\ttfamily\tiny izq \& \ttfamily\tiny der \\
{} \& {} \\
}
\tikzset{
my matrix/.style={
ampersand replacement=\&,
matrix of nodes,
every node/.style={
text width=3em,
minimum height=1.25em,
inner sep=0pt,
align=center,
execute at begin node={\strut}
},
draw,
thick,
inner sep=0pt,
row 3/.style={
execute at end cell={
\draw[very thin]
(\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north west) --
(\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north east);
}
},
column 2/.style={
execute at end cell={
\draw[very thin]
(\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.north west) --
(\tikzmatrixname-\the\pgfmatrixcurrentrow-\the\pgfmatrixcurrentcolumn.south west);
}
}
},
my tree/.style={
every node/.style={
my matrix
},
level distance=7em,
level 1/.style={sibling distance=25em},
level 2/.style={sibling distance=10em},
level 3/.style={sibling distance=7.5em},
edge from parent/.style={
draw,
thick,
{Circle[width=4pt, length=4pt]}-{Latex[]},
shorten >=2pt,
shorten <=-2pt,
},
left/.style={
edge from parent path={
(\tikzparentnode-4-1.center) --
(\tikzchildnode.north)
}
},
right/.style={
edge from parent path={
(\tikzparentnode-4-2.center) --
(\tikzchildnode.north)
}
}
},
}
\begin{document}
\begin{tikzpicture}[my tree]
\node (root) {\matrixbody{5}{15}}
child[left] { node {\matrixbody{1}{11}}
child[missing]
child[right] { node {\matrixbody{3}{13}}
child[left] { node {\matrixbody{2}{12}} }
child[right] { node {\matrixbody{4}{14}} }
}
}
child[right] { node {\matrixbody{7}{17}}
child[left] { node {\matrixbody{6}{16}} }
child[missing]
};
\end{tikzpicture}
\end{document}
首先,正如你所说,在这里使用矩阵可能是一个好主意。我使用了以下方法这个很好的答案在矩阵内添加细线。使用另一种更复杂的方法也可能实现这一点,但我认为这个想法仍然相当简单。
现在,树的节点本质上是外观非常相似的矩阵,我认为简化其内容的排版是一个好主意,这就是我为此创建自定义命令的原因。请注意,您需要退出&
并使用ampersand replacement
键才能使其工作。
最后,我用的是方法你链接将圆圈精确地定位在连接箭头的起点处。为此,最好使用库arrows.meta
,因为您可以精确指定小圆圈的大小,这对于将选项设置shorten <
为正确的值是必要的。
为了使节点放置得更漂亮一些,我插入了两个missing
子节点。
答案2
这是一个 Forest 解决方案。
宏\cdid
构成每个矩阵。其样式设置为第一行和第三行以及第二行和第四行大小相等,这样中间的十字也位于几何中心,只需使用矩阵的四个主罗盘锚点即可轻松绘制。
由于我还没有找到在森林节点内的矩阵内引用命名坐标/节点的解决方案 - 即将节点行走!u
与后缀-1
或-2
- 相结合,我通过根据父节点计算来确定边的起点。
代码
\documentclass{standalone}
\usepackage{forest}
\usetikzlibrary{positioning, ext.arrows}
\tikzset{
cdid text/.style={
shape=rectangle, node font=\tiny\ttfamily, text depth=+0pt, text height=+1em,
text width=width("clave"), align=center},
cdid val/.style={shape=rectangle, text width=width("00"), align=center},
cdid matrix/.style={
draw, thin, every outer matrix/.append style={inner sep=+0pt},
every cell/.append code=\pgfpositionnodelater\relax, % disable forest
append after command={% simple cross
(\tikzlastnode.west) edge[very thin] (\tikzlastnode.east)
(\tikzlastnode.north) edge[very thin] (\tikzlastnode.south)}}}
\newcommand*\cdid[2]{%
\node[cdid text]{clave};\pgfmatrixnextcell\node[cdid text]{dato};\pgfmatrixendrow
\node[cdid val]{$#1$}; \pgfmatrixnextcell\node[cdid val]{$#2$}; \pgfmatrixendrow
\node[cdid text]{izq}; \pgfmatrixnextcell\node[cdid text]{der}; \pgfmatrixendrow
\node[cdid val]{\phantom{$#1$}};\pgfmatrixnextcell
\node[cdid val]{\phantom{$#2$}};\pgfmatrixendrow}
\forestset{
cdid tree/.style={
for tree={
node options={matrix, cdid matrix},
before typesetting nodes={+content=\cdid},
edge={arrows={Centered Circle[scale=.75]}-Latex},
edge path={
\noexpand\path[path only](!u.south west)--
coordinate[pos=\forestoption{n}>1?.75:.25](@) (!u.south east);
\noexpand\path[\forestoption{edge}]([yshift=1ex]@)--
(.child anchor)\forestoption{edge label};}},
phantom/.append style={content=00}}}
\begin{document}
\begin{forest} cdid tree
[{5}{15}
[{1}{11}
[,phantom]
[{3}{13}
[{2}{12}]
[{4}{14}]
]
]
[{7}{17}
[{6}{16}]
[,phantom]
]
]
\end{forest}
\end{document}
输出
答案3
这是另一种方法,几乎和你的方法一样。我的策略与你的非常相似。以下是两个(半)步骤的改变:
- 将父母的锚点从
.south
改为.south west
- 用极坐标代替笛卡尔坐标中的移位(更容易形象化)
- 稍微调整角度和半径
% ~~~ change anchor point AND apply shift in polar coordinates ~~~~~~~~~~
\newcommand{\leftedge}{[edge from parent path = {
([shift=(40:6mm)]\tikzparentnode.south west)
-- (\tikzchildnode.north)}]}
结果:
选择:
- 保持锚点
.center
- 再次在极坐标中将其向外移动
有关极角的信息仅供读者参考:
- 0 度 == 正 x 方向(向右)
- 90 度 == 正 y 方向(向上)
- 180 度 = 负 x 方向(向左)
- 270 度 = 负 y 方向(向下)
\documentclass{article}
\usepackage{tikz}
\usepackage{array}
\usetikzlibrary{arrows}
\begin{document}
\newcolumntype{x}[1]{>{\centering\arraybackslash\hspace{0pt}}p{#1}}
\newcommand{\tnode}[2]{
\begin{tabular}{x{2em}|x{2em}}
\tiny{}clave & \tiny{}dato \\
$#1$ & $#2$ \\\hline
\tiny{}izq & \tiny{}der \\
&
\end{tabular}}
% ~~~ change anchor point AND apply shift in polar coordinates ~~~~~~~~~~
\newcommand{\leftedge}{[edge from parent path = {
([shift=(40:6mm)]\tikzparentnode.south west)
-- (\tikzchildnode.north)}]}
\newcommand{\rightedge}{[edge from parent path = {
([shift=(140:6mm)]\tikzparentnode.south east)
-- (\tikzchildnode.north)}]}
\begin{tikzpicture}[
level distance=7em,
every node/.style = {align=center, font=\ttfamily,
inner sep=0pt, draw,},
level 1/.style={sibling distance=20em},
level 2/.style={sibling distance=12em},
level 3/.style={sibling distance=6em},
thick, *->, shorten >=2px, >=latex,
]
\node (root) {\tnode{5}{15}}
child[left] { node {\tnode{1}{11}} \leftedge{}
child[right] { node {\tnode{3}{13}} \rightedge{}
child[left] { node {\tnode{2}{12}} \leftedge{}}
child[right] { node {\tnode{4}{14}} \rightedge{}}
}
}
child[right] { node {\tnode{7}{17}} \rightedge{}
child[left] { node {\tnode{6}{16}} \leftedge{}}
};
\end{tikzpicture}
\end{document}