coder4lyf
coder4lyf

Reputation: 927

Delete an element from a binary search tree in F#

I'm trying to write a method to delete an element from a BST. So far, this is what I have. I'm not sure if I'm on the right track or if there is a better way to do it by using pattern matching to match the different delete cases ie: no children, 1 child, 2 children.

type 'a bst = NL | BinTree of 'a * 'a bst * 'a bst;;

let rec smallest = function
    | NL -> failwith "tree is empty"
    | BinTree(m, lst, rst) -> if lst = NL then BinTree(m, lst, rst)
                              else smallest lst;;

let rec smallest2 = function
    | NL -> failwith "tree is empty"
    | BinTree(m, lst, rst) -> if lst = NL then m
                              else smallest2 lst;;

let rec rem2 = function
    | NL -> NL
    | BinTree(m, NL, NL) -> NL
    | BinTree(m, NL, rst) -> rst
    | BinTree(m, lst, NL) -> lst
    | BinTree(m, lst, rst) -> BinTree(smallest2 rst, lst, rst);;


let rec rem x = function
    |NL -> failwith "Node doesn't exit"
    |BinTree(m, lst, rst) -> if m = x then rem2 (BinTree(m, lst, rst)) 
                             elif m < x then BinTree(m, lst, rem x rst) 
                             else BinTree(m, rem x lst, rst);;

The cases of no children and a single child work perfectly, but when the node to be deleted has 2 children, I cannot figure out how to implement this case. I want to replace that node's value with the smallest item on it's right subtree, and then remove the smallest item on its right subtree.

Upvotes: 1

Views: 986

Answers (2)

Olaf
Olaf

Reputation: 3996

I have followed the steps Tomas decribes in his post and I came up with this solution:

// BST - binary search tree
type BST<'a when 'a: comparison> = | Leaf
                                   | Node of BST<'a> * 'a * BST<'a>

let rec rmMaxBST = function
    | Leaf              -> failwith "Tree was empty"
    | Node(tL, x, Leaf) -> x, tL
    | Node(tL, x, tR  ) -> let m, newTR = rmMaxBST tR
                           m, Node(tL, x, newTR)

let rec rmMinBST = function
    | Leaf              -> failwith "Tree was empty"
    | Node(Leaf, x, tR) -> x, tR
    | Node(tL,   x, tR) -> let m, newTL = rmMinBST tL
                           m, Node(newTL, x, tR)

let mergeBST t1 t2 =
    match t1, t2 with
    | (Leaf, Leaf)      -> Leaf
    | (t1,   Leaf)      -> let x, t = rmMaxBST t1
                           Node(t, x, Leaf)               
    | (t1,   t2  )      -> let x, t = rmMinBST t2
                           Node(t1, x, t)              

let rec delBST x = function
    | Leaf                       -> Leaf
    | Node(tL, a, tR) when x < a -> Node(delBST x tL, a,          tR)
    | Node(tL, a, tR) when a < x -> Node(         tL, a, delBST x tR)
    | Node(tL, _, tR)            -> mergeBST tL tR

I tried this in the REPL:

> delBST 3 Leaf;;

val it : BST<int> = Leaf

> delBST 3 (Node(Leaf, 4, Leaf));;

val it : BST<int> = Node (Leaf,4,Leaf)

> delBST 3 (Node(Leaf, 3, Leaf));;

val it : BST<int> = Leaf

> delBST 3 (Node(Node(Leaf, 1, Leaf), 3, Node(Leaf, 5,Leaf)));;

val it : BST<int> = Node (Node (Leaf,1,Leaf),5,Leaf)

> delBST 1 (Node(Node(Leaf, 1, Leaf), 3, Node(Leaf, 5,Leaf)));;

val it : BST<int> = Node (Leaf,3,Node (Leaf,5,Leaf))

> delBST 5 (Node(Node(Leaf, 1, Leaf), 3, Node(Leaf, 5,Leaf)));;

val it : BST<int> = Node (Node (Leaf,1,Leaf),3,Leaf)

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243096

I'm not quite sure I understand the logic that your remove function is trying to implement. The usual way to do this is to write a recursive function that:

  • if x is smaller than the current, remove x from the left sub-tree recursively
  • if x is larger than the current, remove x from the rightsub-tree recursively
  • if x is equal to the current, drop the current node and merge the two trees

The way to encode this in F# is to write a recursive function using pattern matching - quite similar to the functions you wrote:

let rec remove x = function
  | NL -> NL
  | BinTree(m, lst, rst) when x = m -> merge lst rst
  | BinTree(m, lst, rst) when x < m -> BinTree(m, remove x lst, rst)
  | BinTree(m, lst, rst) (* x > m *) -> BinTree(m, lst, remove x rst)

[EDIT The following is not actually going to work!] This is almost complete, but you need to add the merge function. The logic for the merge function is the following:

  • If both trees are empty, return empty tree
  • If left is Bin(n, llst, lrst) and the right is rst then return a tree that contains n with llst on the left and (recursively) merged lrst and rst on the right (because elements in both of them are larger than n).
  • Similarly if right is Bin and left is anything else.

This is not going to produce a balanced binary tree, but it is a good start.

EDIT: I think that perhaps an easiest option is to write two functions - one to remove the largest and one to remove the smallest element of the tree (then when merging, you can just call one of these two). This might be easier than writing a fully general remove function.

The following removes the largest element and returns it, together with a new tree (without the largest element):

let rec remLargest = function
  | NL -> failwith "Tree was empty!"
  | BinTree(m, l, NL) -> m, l
  | BinTree(m, l, r) -> 
      let res, newR = remLargest r
      res, BinTree(m, l, newR)

Upvotes: 3

Related Questions