这个任务可以通过一种简单的结构拉链来实现。这个结构在遍历列表时保持状态非常有效。它是一种将(单)链接列表分成两部分的技术结构。第一部分是当前位置左侧(或前面)以相反顺序存储的列表部分,第二部分是剩余的成员。可以将其想象为以下结构:
struct {
list *revleft;
list *right;
};
或者在一些使用列表作为单链表的函数式语言中,例如 Erlang
{Left, Right}
这些列表中的每个成员都将是包含一个垂直线的列表。
然后,我们必须定义操作zip_left
和zip_right
。这两个操作中的每一个都将从拉链的一侧移动一个元素到另一侧。如果没有任何元素可以移除,这个操作也必须创建空列表。例如,在Erlang中:
zip_left({[], R}) -> {[], [[]|R]};
zip_left({[H|T], R}) -> {T, [H |R]}.
zip_right({L, []}) -> {[[]|L], []};
zip_right({L, [H|T]}) -> {[H |L], T}.
[H|T]
是列表头部删除或添加新的列表头部操作,具体取决于 ->
的哪一侧。在左侧时,它会删除;在右侧时,它会添加。
然后我们需要定义操作 zip_add
,将树节点值添加到相应的垂直线上。为了方便起见,假设当前值是拉链右侧的头部。我们必须处理空列表的情况。
zip_add(V, {L, [ ]}) -> {L, [[V] ]});
zip_add(V, {L, [H|R]}) -> {L, [[V|H]|R]}.
现在我们只需要将拉链围绕树发送即可。当我们向右移动时,我们将把拉链向右移动,然后当从子树返回到左侧时再向左移动。当我们向左移动时,我们将把拉链向左移动,然后向右移动。顺序无关紧要,它只影响每个垂直线中值的顺序。
-record(node, {
value,
left = nil,
right = nil
}).
zip_new() -> {[], []}.
get_verical_lines(Root) ->
{L, R} = get_verical_lines(Root, zip_new()),
tl(lists:reverse(L, R)).
get_verical_lines(nil, Zip) -> Zip;
get_verical_lines(#node{value = V, left = L, right = R}, Zip) ->
Zip1 = zip_right(get_verical_lines(L, zip_left(Zip))),
Zip2 = zip_left(get_verical_lines(R, zip_right(Zip1))),
zip_add(V, Zip2).
这就是全部了。所有的操作 zip_left
、zip_right
和 zip_add
都是 O(1)
操作。在每个节点中,我们执行它们固定次数(zip_left
x2,zip_right
x2,zip_add
x1),因此使用简单的单链表结构可以得到漂亮的 O(N)
时间复杂度。在任何语言中实现都相当简单,除了代码会更加简洁。
首先是树形问题。
> io:write(vertical_tree:get_verical_lines({node, 1, {node, 2, {node, 4, nil, nil}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil, nil}, {node, 7, nil, nil}}})).
[[4],[2],[1,6,5],[3],[7]]ok
第二棵树
> io:write(vertical_tree:get_verical_lines({node, 1, {node, 2, {node, 4, {node, 8, nil, nil}, {node, 9, nil, nil}}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil ,nil},{node, 7, {node, 10, nil, nil}, {node, 11, nil, nil}}}})).
[[8],[4],[2,9],[1,6,5],[3,10],[7],[11]]ok
有一个完整的模块code,其中包括树形漂亮打印机。
> io:put_chars(vertical_tree:draw({node, 1, {node, 2, {node, 4, nil, nil}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil, nil}, {node, 7, nil, nil}}})).
1
/ \
2 3
/ \ / \
4 5 6 7
ok
> io:put_chars(vertical_tree:draw({node, 1, {node, 2, {node, 4, {node, 8, nil, nil}, {node, 9, nil, nil}}, {node, 5, nil, nil}}, {node, 3, {node, 6, nil ,nil},{node, 7, {node, 10, nil, nil}, {node, 11, nil, nil}}}})).
1
/ \
2 3
/ \ / \
4 5 6 7
/ \ / \
8 9 10 11
ok
顺便说一下,拉链的类型远不止用于处理单向链表。例如,可以制作用于遍历树的拉链,它允许在不使用递归或仅使用尾递归函数的情况下遍历列表。