One needs to do deletes. Four situations are possible
Remember that when the node to be deleted has two children, it can either be replaced by the largest node on the left subtree or the smallest node on the right subtree (to maintain binary tree properties).
Delete 30.
If algorithm chooses to take from the left subtree, then want largest value ... from node to delete (30) go left (25) then right right right until null - at replace (28)
Delete 40 (replacement from right subtree)
Remember that when the node to be deleted has two children, it can either be replaced by the largest node on the left subtree or the smallest node on the right subtree (to maintain binary tree properties). This example code uses the left's largest. sigh.
One can do it either way in examples/code as long as we are clear and consistent. Specifically, an algorithm does it one way or another (not back and forth).
Code which replaces a node with two children with largest node on the left subtree:
So, it selects, as the replacement R, the rightmost node in the left subtree. This is the largest node whose data value is less than that of the node to be deleted.
The following is from another book (Page 588 Data Strucutres with C++ (Ford
and Topp)) that has defined a FindNode method (to find the node AND its
parent). This Delete method does not return anything.
What methods and what variables?
Methods are Search, Find (to provide parent as well), Insert, Delete (maybe
Join, Split). Which should be public? which private?
What should the IVs be?
Some depends on implementation and where to hold the information. For example,
one does a Find so you can use the info in an Insert or Delete. Should the
results of a Find be passed back to the calling method or should they be put in
IVs?
This is a question you should always consider. If other methods are going to
make use of the variables too, it is better to put them into IVs.
IVs and CVs are always providing information about the state of the object.
If other methods need to know this state, let them have it easily (i.e.,
provide it in IVs). If in a method, the state is changing, maybe it is useful
to have this information. (Example, I am searching the binary tree and am at
node curNode...whatever)
Good question... In fact, as it is already, should any of our methods be
synchronized to prevent concurrency?
In other languages one did not need to think about this, but suppose someone
spawned a bunch of threads to do Inserts (or Deletes) in parallel. Trouble.
Solution? Of course, synchronize. But remember, "when one thread enters a
synchronized method, Java guarantees that it can finish it before another
thread can execute any synchronized method in the same object." Here,
our bst can block threads and notify them when it becomes available so it is
behaving as a monitor. (In Java, any object with one or more synchronized
methods is a monitor.) Which methods need to be monitored?
Clearly, Insert and Delete.
Which methods could be done using parallelism via recursion? Search and Find.
Note that this makes one see the usefulness of small, single purpose methods.
We might want to do the searches with multiple threads, but if they are
inside of the Insert or Delete, then the concurrency is useless.
Methods that are synchronized should always be as small as possible. It is
only the part that deals with changing the critical variable that should be
synchronized.
So, for example, in the binary trees method insert, the high level insert could
find where to put the node (and what this nodes parent is) and then call a
lower level synchronized part that makes the change.
Hmmm, suppose a bunch of Finds are done (and waiting) and info has changed
since Find was done. sigh. One often sees objects that might be changed while
another is being worked on be cloned first. However, that does not work for
data integrity, only for the idea of working on what was there when the work was started.
How about checking a flag when one enters Find or
Search that indicates a change is in the middle of being made (set upon entry
into and exit out of Delete/Insert, and wait for it (sleep (is wait only for
sychronizedthreads? notifyAll() (which must be in synchronized) notifies all
threads to wake up and check some condition... Remember we do not want to
synchronize here since we are trying to call recursion.)
Implementation Discussion
Issues: Note if you are reading these notes and not listening to a lecture:
- this section is "thinking out loud" to consider (and be aware
of) possibilities.