P66 - 二叉树紧凑布局

2026-09-07 00:00    #Haskell   #99题   #二叉树  

P66 - 二叉树紧凑布局

Binary tree layout: compact

官方模块:Problems.P66 核心函数:layoutCompact


← P65 层级等距布局 | P67 字符串表示 →


题目描述

尽可能紧凑地布局二叉树,使得宽度最小。同一层的节点不重叠,且父节点居中于子节点。

函数签名

1layoutCompact :: Tree a -> Tree (a, (Int, Int))

实现

方法一:相对坐标 + 绝对定位

 1type Bounds = [(Int, Int)]
 2data Relative a = REmpty | RNode a Int Bounds (Relative a) (Relative a)
 3
 4layoutCompact :: Tree a -> Tree (a, (Int, Int))
 5layoutCompact tree = absolute relative (1 - leftMost relative) 1
 6  where
 7    relative = compact tree
 8
 9compact :: Tree a -> Relative a
10compact Empty = REmpty
11compact (Branch value left right) =
12  RNode value 0 bounds (move (-distance) l) (move distance r)
13  where
14    l = compact left
15    r = compact right
16    distance = safeDistance (contour l) (contour r)
17    bounds = merge (shiftBounds (-distance) (contour l))
18                   (shiftBounds distance (contour r))
19
20contour :: Relative a -> Bounds
21contour REmpty = []
22contour (RNode _ _ bounds _ _) = (0, 0) : bounds
23
24move :: Int -> Relative a -> Relative a
25move _ REmpty = REmpty
26move distance (RNode value position bounds left right) =
27  RNode value (position + distance) bounds left right
28
29shiftBounds :: Int -> Bounds -> Bounds
30shiftBounds distance =
31  map (\(left, right) -> (left + distance, right + distance))
32
33merge :: Bounds -> Bounds -> Bounds
34merge [] ys = ys
35merge xs [] = xs
36merge ((left, _):xs) ((_, right):ys) =
37  (left, right) : merge xs ys
38
39safeDistance :: Bounds -> Bounds -> Int
40safeDistance left right = head
41  [distance | distance <- [1..], and (zipWith (separated distance) left right)]
42  where
43    separated distance (_, leftRight) (rightLeft, _) =
44      leftRight - distance < rightLeft + distance
45
46leftMost :: Relative a -> Int
47leftMost REmpty = 0
48leftMost (RNode _ position bounds _ _) =
49  minimum (position : map fst bounds)
50
51absolute :: Relative a -> Int -> Int -> Tree (a, (Int, Int))
52absolute REmpty _ _ = Empty
53absolute (RNode value offset _ left right) parentX depth =
54  Branch (value, (x, depth))
55    (absolute left x (depth + 1))
56    (absolute right x (depth + 1))
57  where
58    x = parentX + offset

每棵相对布局保存逐层的左右轮廓。合并子树时寻找最小安全距离,最后一次遍历把相对坐标转换成绝对坐标。

方法二:直接检查逐层坐标

 1layoutCompactByLevels :: Tree a -> Tree (a, (Int, Int))
 2layoutCompactByLevels tree = normalize (build tree)
 3  where
 4    build Empty = Empty
 5    build (Branch value left right) =
 6      Branch (value, (0, 1))
 7        (shiftLayout (-distance) 1 leftLayout)
 8        (shiftLayout distance 1 rightLayout)
 9      where
10        leftLayout = build left
11        rightLayout = build right
12        distance = head
13          [d | d <- [1..], levelsSeparated d leftLayout rightLayout]
14
15    normalize Empty = Empty
16    normalize layout =
17      shiftLayout (1 - minimum (map fst (positions layout))) 0 layout
18
19levelsSeparated :: Int -> Tree (a, (Int, Int)) -> Tree (a, (Int, Int)) -> Bool
20levelsSeparated distance left right = and
21  [maximum leftLevel - distance < minimum rightLevel + distance
22  | (leftLevel, rightLevel) <- zip (levels left) (levels right)]
23
24levels :: Tree (a, (Int, Int)) -> [[Int]]
25levels tree = takeWhile (not . null) [atDepth depth tree | depth <- [1..]]
26  where
27    atDepth _ Empty = []
28    atDepth target (Branch (_, (x, y)) left right)
29      | target == y = [x]
30      | otherwise = atDepth target left ++ atDepth target right
31
32positions :: Tree (a, (Int, Int)) -> [(Int, Int)]
33positions Empty = []
34positions (Branch (_, position) left right) =
35  position : positions left ++ positions right
36
37shiftLayout :: Int -> Int -> Tree (a, (Int, Int)) -> Tree (a, (Int, Int))
38shiftLayout _ _ Empty = Empty
39shiftLayout dx dy (Branch (value, (x, y)) left right) =
40  Branch (value, (x + dx, y + dy))
41    (shiftLayout dx dy left)
42    (shiftLayout dx dy right)

这个版本不建立专门的轮廓类型,而是先生成带相对坐标的子树,再逐层读取实际横坐标并测试安全距离。代码更直观,但重复扫描子树,效率低于方法一。

方法对比

方法中间数据特点
相对坐标 + 轮廓每层左右边界合并高效,推荐
直接检查逐层坐标完整坐标树容易理解,重复遍历较多

测试

1>>> layoutCompact (Branch 'a' (Branch 'b' Empty (leaf 'c')) (leaf 'd'))
2Branch ('a',(2,1)) (Branch ('b',(1,2)) Empty (Branch ('c',(2,3)) Empty Empty)) (Branch ('d',(3,2)) Empty Empty)
3>>> positions (layoutCompactByLevels (Branch 'a' (leaf 'b') (leaf 'c')))
4[(2,1),(1,2),(3,2)]

参考