你可能不想让addNode带上拉链。如果节点以随机顺序插入,您希望始终从树的顶部调用 addNode,并且没有拉链的简单递归解决方案更好。
基于拉链的addNode 仅在按每个添加的节点“接近”前一个节点的顺序添加节点时才有意义。但是,如果是这种情况,不平衡二叉树可能是一种糟糕的数据结构选择。我很难想象一个用例,其中节点插入的顺序使得基于拉链的 addNode 是有效的和不平衡的二叉树保持近似平衡。
如果你做决定继续使用基于拉链的addNode,我认为您需要重新考虑“访问”节点的含义。在您的示例中,您大概有:
addNode 3 (Leaf, []) = (Node Leaf 3 1 Leaf, [])
意味着新创建的节点已经被访问过一次。当你 addNode 4 到这里时,你会期望得到:
(Node Leaf 4 1 Leaf, [Rt 3 2 Leaf])
因为您再次访问节点“3”并创建一个已访问过一次的新节点“4”。但是,当你进一步addNode 2时,你必须重新访问节点“4”(确定“2”小于“4”),重新访问节点“3”(确定“2”也是小于“3”),最后插入新的“2”节点给出:
(Node Leaf 2 1 Leaf, [Lt 3 3 (Node Leaf 4 2 Leaf)])
这些访问次数与您在问题中给出的不同。
不管怎样,这里有一些想法——再一次,只有当你真的决定你想要一个基于拉链的addNode。我认为您应该将问题分为两个递归步骤。第一次递归应该向上移动树以找到合适的起点。第二次递归应该向下移动子树以执行实际的插入。
第一个递归是微妙的。考虑这棵树:
4
/
3 7
/
6 9
并想象我们正在查看“6”并试图插入“2”。为了决定我们是否需要向上移动,我们需要将新节点“2”与当前节点“6”进行比较。我们还需要查看第一个碎屑的方向,我们可能需要看看那块面包屑的价值。
例如,由于 2<6 并且留下了第一个碎屑,我们知道我们必须无条件地向上移动,因为总是有可能有较小的数字在树中检查更高的位置。
将此与在节点“9”处插入“2”进行对比。我们仍然有 2<9,但是由于第一个 crumb 是正确的,所以只有当父节点的值也大于(或等于)2 时,我们才向上移动。在这里,由于 2<=7,我们向上移动,但是如果“7”改为“1”,我们将结束第一次递归并将“2”插入到节点“9”的子树中。
基本上,只要 (1) 我们小于当前节点,并且第一个碎屑剩下或者我们小于或等于第一个碎屑,我们就向上移动;或 (2) 我们大于当前节点并且第一个碎屑是正确的或者我们大于或等于第一个碎屑。
您需要决定“访问”一个节点是否意味着向上移动到它和/或是否在不向上移动的情况下检查碎屑方向和碎屑的值应该算在内。
一旦我们根据这些条件完成向上移动,我们就可以继续在当前子树中插入新节点,以通常的方式向下移动。
如果您想尝试一下,您可以从以下模板开始:
addNode:: Ord a => a -> Zipper a -> Zipper a
addNode x = insertDown . upToSubtree
where
upToSubtree z@(_, []) = z -- at root, so we're done moving up
upToSubtree z@(Leaf, _) = ... -- unconditionally move up
upToSubtree z@(Node l y n r, (crumb:crumbs))
= case compare x y of
LT -> case crumb of
Lt _ _ _ -> ... -- move up, regardless of crumb value
Rt u _ _ | x<=u -> ... -- move up only if x <= crumb
| otherwise -> ... -- don't move up, but count visit to crumb?
GT -> case crumb of
Rt _ _ _ -> ... -- move up, regardless of crumb value
Lt u _ _ | x>=u -> ... -- move up only if x >= crumb
| otherwise -> ... -- don't move up, but count visit to crumb?
EQ -> ... -- don't move up
insertDown (Leaf, crumbs) = ... -- insert new node
insertDown (Node l y n r, crumbs)
= case compare x y of
LT -> ... go left and recurse ...
GT -> ... go right and recruse ...
-- don't insert duplicates, but consider as visited
EQ -> ... note visit ...
剧透
以下是我想出的完整解决方案。我还没有对它进行过广泛的测试。我的意图是将每次与上行节点的比较计为一次访问,并将每次与下行节点的比较计为一次访问,但要避免重复计算对 upToSubtree 返回的节点的访问,即使在大多数情况下,下面的代码实际上对该节点进行了两次比较。我怀疑逻辑是否 100% 正确。
.
.
.
data BTree a = Leaf | Node (BTree a) a Int (BTree a) deriving (Show)
data Direction a = Lt a Int (BTree a) | Rt a Int (BTree a) deriving (Show)
type Breadcrumbs a = [Direction a]
type Zipper a = (BTree a, Breadcrumbs a)
addNode:: Ord a => a -> Zipper a -> Zipper a
addNode x = insertDown . upToSubtree
where
upToSubtree z@(_, []) = z
upToSubtree z@(Leaf, _) = up z
upToSubtree z@(Node l y n r, (crumb:crumbs))
= let z' = visit z -- count visit for this comparison
in case compare x y of
LT -> case crumb of
Lt _ _ _ -> up z'
Rt u _ _ | x <= u -> up z'
-- we note comparison with parent, visit of this node counted by insertDown
| otherwise -> visitParent z
GT -> case crumb of
Rt _ _ _ -> up z'
Lt u _ _ | x >= u -> up z'
-- we note comparison with parent, visit of this node counted by insertDown
| otherwise -> visitParent z
EQ -> z -- visit will be counted by insertDown
up (t, Lt y n r : crumbs) = upToSubtree (Node t y n r, crumbs)
up (t, Rt y n l : crumbs) = upToSubtree (Node l y n t, crumbs)
insertDown (Leaf, crumbs) = (Node Leaf x 1 Leaf, crumbs)
insertDown (Node l y n r, crumbs)
= case compare x y of
LT -> insertDown (l, Lt y (n+1) r : crumbs)
GT -> insertDown (r, Rt y (n+1) l : crumbs)
-- don't insert duplicates, but consider as visited
EQ -> (Node l y (n+1) r, crumbs)
visit (Node l x n r, crumbs) = (Node l x (n+1) r, crumbs)
visitParent (t, Lt x n r : crumbs) = (t, Lt x (n+1) r : crumbs)
visitParent (t, Rt x n l : crumbs) = (t, Rt x (n+1) l : crumbs)
main = do
print $ addNode 2 $ addNode 4 $ addNode 3 (Leaf,[])
-- adding "2" to my ASCII tree example at node "9"
print $ addNode 2 $ (Node Leaf 9 0 Leaf,
[ Rt 7 0 (Node Leaf 6 0 Leaf)
, Rt 4 0 (Node Leaf 3 0 Leaf)])
-- adding "2" to my ASCII tree example at node "6"
print $ addNode 2 $ (Node Leaf 6 0 Leaf,
[ Lt 7 0 (Node Leaf 9 0 Leaf)
, Rt 4 0 (Node Leaf 3 0 Leaf)])