0

I have the two datatypes:

datatype 'a Tree = LEAF of 'a | NODE of ('a Tree) * ('a Tree)

and

datatype 'a myTree = myLEAF of 'a | myNODE of 'a * 'a * 'a myTree * 'a myTree

With these two, I need to be able to find the min and max values of a tree.

For example:

findMin (NODE(NODE(LEAF(5),NODE(LEAF(6),LEAF(8))),LEAF(4)))

will produce 4.

I have been working on this for some time now, and I'm quite confused.

Any guidance would be helpful. Thank you.

sshine
  • 15,635
  • 1
  • 41
  • 66
Oppa
  • 154
  • 8
  • What have you tried? Where did you get stuck? What is the intended purpose of the `myTree` datatype? – Sam Westrick Feb 14 '18 at 02:03
  • I really dont know where to start. I can't wrap my mind around how to go about this, as I am brand new to SML. I understand how to do this, in a general sense - look at the node, check the children, keep track of the smallest, then return it after going through each node. The myTree datatype is a polymorphic binary tree that has values on both leaves and nodes, whereas the Tree datatype only carries values within the leaf nodes. – Oppa Feb 14 '18 at 02:10
  • It sounds like the problem you're trying to solve is writing a function `findMin : int Tree -> int`, but I don't see how the `myTree` datatype fits into that problem. Are you *required* to use `myTree` somehow? Or is it your own definition, which you believe will be helpful for implementing `findMin`? – Sam Westrick Feb 14 '18 at 02:26
  • That is the function im required to write, as well as findMax. My instructions do not say whether I am required to use myTree, but it was given (which plays into part of my confusion.) Judging based on the test cases, it does not look as if I am required to use it. Those test cases being: findMin (NODE(NODE(NODE(LEAF(0),LEAF(11)),LEAF(6)),NODE(LEAF(3),LEAF(10)))) findMax (NODE(NODE(LEAF(5),NODE(LEAF(6),LEAF(8))),LEAF(4))) findMax (LEAF(5)) – Oppa Feb 14 '18 at 02:55

3 Answers3

2

You know that there is at least one element in every 'a Tree, so there is always a min/max.

Use pattern matching on each of the two constructors LEAF and NODE, and use recursion in the NODE case, since the two branches might have different min/max values and the min/max for the node is determined by whatever is min/max for its branches. And use the built-in helper functions Int.min and Int.max, if you're finding the min/max integers of a tree. (Your example suggests that this is the case.)

fun findMin (LEAF x) = (* ... *)
  | findMin (NODE (leftTree, rightTree)) =
    let (* ... use findMin recursively on each branch ... *)
    in (* ... find the minimal value of the two branches ... *)
    end

I'm not sure what the 'a myTree type is good for: It is a binary tree in that it has two 'a myTree branches per node, but it also has two 'a elements per node? Should you be interested in finding the min/max of either of those values? Or is one a key and another a value in some tree-based dictionary structure? If so, then why is it 'a -> 'a and not 'a -> 'b? It is hard to solve a problem when you don't understand the problem statement, and the datatype is a large portion of that.

Edit: Since you've provided a solution yourself, let me give some feedback on it:

fun findMin (LEAF(v)) = v
  | findMin (NODE(left, right)) =
      if findMin(left) < findMin(right)
      then findMin(left)
      else findMin(right)

This solution is very inefficient since it calls itself three times for each node's entire subtree. That means the number of function calls roughly follows the recurrence relation f(0) = 1 and f(n) = 3 ⋅ f(n-1). This is equivalent to 3n or exponentially many calls to find the minimal element in a list of n elements.

  • Here is a way that take linear time by temporarily storing the result you use twice:

    fun findMin (LEAF v) = v
      | findMin (NODE (left, right)) =
        let val minLeft = findMin left
            val minRight = findMin right
        in if minLeft < minRight then minLeft else minRight
        end
    

    There is no reason to perform the Herculean task of calculating findMin left and findMin right more than once in every node of the tree. Since we refer to it multiple time, a let-in-end is an easy way to bind the results to lexically scoped names, minLeft and minRight.

  • The expression if minLeft < minRight then minLeft else minRight actually has a name in the standard library: Int.min. So we could do:

    fun findMin (LEAF v) = v
      | findMin (NODE (left, right)) =
        let val minLeft = findMin left
            val minRight = findMin right
        in Int.min (minLeft, minRight)
        end
    
  • But the reason for using a let-in-end has actually evaporated, since, with the help of a library function, we're now only referring to findMin left (aka minLeft) and findMin right (aka minRight) once now. (Actually, we are referring to them more than once, but that is inside Int.min in which the result has also been bound to a temporary, lexically scoped name.)

    So we ditch the let-in-end for a much shorter:

    fun findMin (LEAF v) = v
      | findMin (NODE (left, right)) = Int.min (findMin left, findMin right)
    

    In any case, these are all equally optimal: They use only n recursive function calls for n elements in the tree, which is the least you can do when the elements aren't sorted. Now, if you knew the smaller elements were always to the left, you'd have a binary search tree and you could find the min/max much faster. :-)

Edit (again): Just for fun, you could find the min/max simultaneously:

fun findMinMax (LEAF v) = (v, v)
  | findMinMax (NODE (left, right)) =
    let val (minLeft, maxLeft) = findMinMax left
        val (minRight, maxRight) = findMinMax right
    in (Int.min (minLeft, minRight), Int.max(maxLeft, maxRight))
    end
sshine
  • 15,635
  • 1
  • 41
  • 66
  • Very nice simplification, and explanation of how to get there. (Smaller, faster, *and* clearer is such a great combination.) – molbdnilo Feb 20 '18 at 08:12
1

I think one problem is that you're thinking hard about how to traverse the tree and check all the nodes in some order and keep track of things, but recursion will handle that for you.

Your tree has two cases; it is either a leaf, or a node with two subtrees.
This suggests that the solution will also have two cases: one for leaves and one for internal nodes.

Write down (in your own words, not code) how you would find the minimum in

  1. a leaf; and
  2. an internal node if you already knew the respective minimums of its subtrees -- don't worry about how to find them yet, but pretend that you know what they are.

Then write down how you find the minimums of the subtrees of an internal node.
(This is a recursion, so you've already solved this problem, before you started thinking about it.)

Then you translate it into ML.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
1

I was 100% just overthinking the problem too much. Thank you both for your help! I got my answer.

fun findMin (LEAF(v)) = v
  | findMin (NODE(left, right)) =
      if findMin(left) < findMin(right)
      then findMin(left)
      else findMin(right)

fun findMax (LEAF(v)) = v
  | findMax (NODE(left, right)) =
      if findMax(left) > findMax(right)
      then findMax(left)
      else findMax(right)
sshine
  • 15,635
  • 1
  • 41
  • 66
Oppa
  • 154
  • 8