【问题标题】:Find ancestors of node in BST in Standard ML在标准 ML 中查找 BST 中节点的祖先
【发布时间】:2023-09-29 00:06:01
【问题描述】:

我正在尝试在 SML 中的 BST 中查找节点的父节点和祖父节点。

我已尝试修改此功能:

fun search(tree : bst, compare, (data) = 
    let fun s(Leaf) = NONE
          | s(Node{data=nodedata,left=left,right=right}) = 
                (case compare(data,nodedata) of
                      LESS    => s(left)
                    | GREATER => s(right)
                    | EQUAL   => SOME (Node{data=nodedata,left=left,right=right}))
     in
         s(tree)
     end 

通过改变输入、比较和输出的格式:

fun search(tree : bst, compare, (data) = 
 let fun s(Leaf) = NONE   
 |s(Node{data=nodedata,left=Node{data=nodedata1,left=left1,right=right1},right=right}) =                          
       (case compare(data,nodedata) of
           LESS    => s(left)
           | GREATER => s(right)
           | EQUAL   => SOME nodedata)

显然它不起作用,我想有一种更简单的方法可以做到这一点。

我添加了一个新变量以保存我正在检查的每个节点的当前父节点并且它可以工作,但是(对我而言)很难找到一种方法来同时保留我检查的节点的祖父节点。

有什么想法吗?提前致谢。

【问题讨论】:

  • 你已经尝试了什么?
  • 我用我尝试过的内容编辑了我的帖子

标签: binary-search-tree sml smlnj


【解决方案1】:

首先,您的搜索功能无法正常工作,因此我对其进行了一些清理。为了避免类型声明错误中的未绑定类型变量,您必须将bst 类型定义为'a bst。我还修复了一个括号错误。这是我的search 函数版本:

datatype 'a bst = Leaf | Node of {data: 'a, left: 'a bst, right: 'a bst}

(*search(tree, compare, key) = t
    where t = the tree which has `key` as its root
  *)
fun search(tree : 'a bst, compare : ('a * 'a -> order), key : 'a)
          : 'a bst option = 
  let 
    fun s(tree: 'a bst) : 'a bst option =
      case tree of
        Leaf => NONE
      | Node({data=x, left=l, right=r}) =>
          case compare(key,x) of
            LESS    => s(l)
          | GREATER => s(r)
          | EQUAL   => SOME tree
   in
       s(tree)
   end
(*Example:
search(Node({data=5,
             left=Node({data=3,
                        left=Node({data=2,left=Leaf,right=Leaf}),
                        right=Node({data=4,left=Leaf,right=Leaf})}),
             right=Leaf}),
       Int.compare,
       3);
*)

由于您需要一个返回搜索的父节点和祖父节点的函数,因此您需要一个返回以下元组的函数:(搜索节点,搜索节点的父节点,搜索节点的祖父节点)。但是,您并不总是有父母或祖父母。例如,如果您要查找的密钥已经在主树的根部,那么您将没有父或祖父。因此,您需要使用 option 类型。如果该搜索没有父节点,这将帮助您返回 NONE 作为父节点。现在,您需要做的就是从(您的主树,NONENONE)开始搜索,然后当您沿着树移动时,将父节点向右移动。如果找不到密钥,只需返回一个三元组 NONE。这是代码:

(*searchParent(tree, compare, key) =
    (t, the parent of t, the grandparent of t)
    where t = the tree which has `key` as its root
  *)
fun searchParent(tree : 'a bst, compare : ('a * 'a -> order), key : 'a)
                : ('a bst option * 'a bst option * 'a bst option) = 
  let 
    fun s(tree: 'a bst, par: 'a bst option, gpar: 'a bst option)
         : ('a bst option * 'a bst option * 'a bst option) =
      case tree of
        Leaf => (NONE, NONE, NONE)
      | Node({data=x, left=l, right=r}) =>
          case compare(key,x) of
            LESS    => s(l, SOME tree, par)
          | GREATER => s(r, SOME tree, par)
          | EQUAL   => (SOME tree, par, gpar)
   in
       s(tree, NONE, NONE)
   end

(*Example:
searchParent(Node({data=5,
             left=Node({data=3,
                        left=Node({data=2,left=Leaf,right=Leaf}),
                        right=Node({data=4,left=Leaf,right=Leaf})}),
             right=Leaf}),
       Int.compare,
       4);
*)

我希望我能帮上忙。请询问您对此问题或代码是否有任何其他问题。

【讨论】:

  • 谢谢,帮了大忙!
【解决方案2】:

虽然 joom 的回答可能就足够了,但我觉得它有点冗长。以下是对问题的简短解释和答案的尝试:拥有一个 BST 和一个元素 x,如果存在这两个元素 (SOME parent, SOME grandparent),请找到它们。

我将使用的策略是:每当进行递归调用时,将parent 记录为刚刚访问的元素,并将grandparent 记录为前一个父元素。如果/当找到从EQUALx 的元素时,返回当前可用的(parent, grandparent)

因为这两个附加变量不是原始搜索函数签名的一部分,并且因为我不希望它们是,所以本地函数helper 被创建。

datatype 'a bst = Node of 'a * 'a bst * 'a bst
                | Leaf

fun search (initTree, compare, x) =
  let fun helper (tree, parent, grandparent) =
          case tree of
              Leaf           => (NONE, NONE)
            | Node (y, L, R) => case compare (x, y) of
                                    LESS    => helper (L, SOME y, parent)
                                  | GREATER => helper (R, SOME y, parent)
                                  | EQUAL   => (parent, grandparent)

  in helper (initTree, NONE, NONE) end

使用以下 BST 测试此功能:

val t = Node (5, Node (3, Node (2, Leaf, Leaf),
                          Node (4, Leaf, Leaf)),
                 Node (7, Node (6, Leaf, Leaf),
                          Node (8, Leaf, Leaf)))

我们看到了预期的结果:

- search (t, Int.compare, 2);
> val it = (SOME 3, SOME 5) : int option * int option
- search (t, Int.compare, 3);
> val it = (SOME 5, NONE) : int option * int option

不幸的是,这个函数不会判断一个元素是在 BST 的根目录中找到还是不存在。那是因为(NONE, NONE) 在这两种情况下都会返回。

【讨论】:

  • 我为二叉搜索树使用了一条记录,因为这就是问题所在,但使用元组是我要做的,也是 OP 应该做的,所以谢谢。不过,显式编写所有类型只是一种选择。
  • 元组比记录更容易阅读。一旦获得许多字段,记录就会很有用,在这种情况下,您通常希望对其中的 一些 进行模式匹配,例如:val { foo = x, ...} = r 其中... 实际有效。但理想情况下,人们想要一种访问字段的组合方式,比如 Haskell 的镜头。显式编写所有类型会使代码的可读性降低。相反,我会使用单独的签名。
最近更新 更多