Binary Trees 101 Binary Trees Exercises 101 E1 Construct The 14 Binary Trees With Four Nodes
Binary Trees 101 Binary Trees Exercises 101 E1 Construct The 14 Binary Trees With Four Nodes
10
10.1 BINARY TREES
Exercises 10.1
E1. Construct the 14 binary trees with four nodes.
Answer
E2. Determine the order in which the vertices of the following binary trees will be visited under (1) preorder,
(2) inorder, and (3) postorder traversal.
1 1 1 1
2 2 2 2 3
3 3 3 4 4 5 6
4 4 5 6 7 8 9
5 5 7 8
441
442 Chapter 10 • Binary Trees
log – –
! – c a –
n a b b c
log n! (a – b) – c a – (b – c)
and
and <
< < c d
a b b c
(a < b) and (b < c) and (c < d)
E4. Write a method and the corresponding recursive function to count all the nodes of a linked binary tree.
{
if (sub_root == NULL) sub_root = new Binary_node< Entry >(x);
else
if (recursive_height(sub_root->right) < recursive_height(sub_root->left))
recursive_insert(sub_root->right, x);
else
recursive_insert(sub_root->left, x);
}
E8. Write a method and the corresponding recursive function to traverse a binary tree (in whatever order you
find convenient) and dispose of all its nodes. Use this method to implement a Binary_tree destructor.
Answer Postorder traversal is the best choice, since a node is then not disposed until its left and right
subtrees have both been cleared.
Binary_tree copy that will make a copy of a linked binary tree. The constructor should obtain the necessary new nodes from
constructor the system and copy the data from the nodes of the old tree to the new one.
E11. Write a function to perform a double-order traversal of a binary tree, meaning that at each node of the
double-order traversal tree, the function first visits the node, then traverses its left subtree (in double order), then visits the node
again, then traverses its right subtree (in double order).
E12. For each of the binary trees in Exercise E2, determine the order in which the nodes will be visited in the
mixed order given by invoking method A:
void Binary_tree< Entry > :: void Binary_tree< Entry > ::
A(void (*visit)(Entry &)) B(void (*visit)(Entry &))
{ {
if (root != NULL) { if (root != NULL) {
(*visit)(root->data); root->left. A(visit);
root->left. B(visit); (*visit)(root->data);
root->right. B(visit); root->right. A(visit);
} }
} }
E13. (a) Suppose that Entry is the type char. Write a function that will print all the entries from a binary tree
printing a binary tree in the bracketed form (data: LT, RT) where data is the Entry in the root, LT denotes the left subtree of
the root printed in bracketed form, and RT denotes the right subtree in bracketed form. For example, the
first tree in Figure 10.3 will be printed as
(b) Modify the function so that it prints nothing instead of (: , ) for an empty subtree, and x instead of (x: , )
for a subtree consisting of only one node with the Entry x. Hence the preceding tree will now print as
( + : a, b).
Answer Since both of the requested functions are minor variations of each other, we shall only list the
solution to the second half of this exercise. In this implementation, we assume that tree entries
can be printed with the stream output operator << . For non-primitive entry types, this requires
the implementation of an overloaded output operator.
E14. Write a function that will interchange all left and right subtrees in a linked binary tree. See the example
in Figure 10.7.
Section 10.1 • Binary Trees 447
Answer template < class Entry >
void Binary_tree< Entry > :: recursive_interchange
(Binary_node< Entry > *sub_root)
/* Post: In the subtree rooted at sub_root all pairs of left and right links are swapped. */
{
if (sub_root != NULL) {
Binary_node< Entry > *tmp = sub_root->left;
sub_root->left = sub_root->right;
sub_root->right = tmp;
recursive_interchange(sub_root->left);
recursive_interchange(sub_root->right);
}
}
template < class Entry >
void Binary_tree< Entry > :: interchange( )
/* Post: In the tree all pairs of left and right links are swapped round. */
{
recursive_interchange(root);
}
E15. Write a function that will traverse a binary tree level by level. That is, the root is visited first, then the
level-by-level traversal immediate children of the root, then the grandchildren of the root, and so on. [Hint: Use a queue to keep
track of the children of a node until it is time to visit them. The nodes in the first tree of Figure 10.7 are
numbered in level-by-level ordering.]
Answer In this solution, we assume that an implementation of a template class Queue with our standard
Queue methods is available.
E16. Write a function that will return the width of a linked binary tree, that is, the maximum number of nodes
on the same level.
Answer We perform a level by level traverse to measure the width. To perform the traversal, we assume
that an implementation of a template class Queue with our standard Queue methods is available.
{
if (root == NULL) return 0;
Extended_queue < Binary_node< Entry > * > current_level;
current_level. append(root);
int max_level = 0;
while (current_level. size( ) != 0) {
if (current_level. size( ) > max_level)
max_level = current_level. size( );
Extended_queue < Binary_node< Entry > * > next_level;
do {
Binary_node< Entry > *sub_root;
current_level. retrieve(sub_root);
if (sub_root->left) next_level. append(sub_root->left);
if (sub_root->right) next_level. append(sub_root->right);
current_level. serve( );
} while (!current_level. empty( ));
current_level = next_level;
}
return max_level;
}
traversal sequences For the following exercises, it is assumed that the data stored in the nodes of the binary trees are all
distinct, but it is not assumed that the trees are binary search trees. That is, there is no necessary
connection between any ordering of the data and their location in the trees. If a tree is traversed in a
particular order, and each key is printed when its node is visited, the resulting sequence is called the
sequence corresponding to that traversal.
E17. Suppose that you are given two sequences that supposedly correspond to the preorder and inorder traver-
sals of a binary tree. Prove that it is possible to reconstruct the binary tree uniquely.
Answer Since preorder traversal shows the root of the tree first, it is easy to detect the root key. The
inorder sequence then shows which of the remaining keys occur in the left and the right subtrees
as those appearing on the left and on the right of the root. Proceeding by recursion with each
of the subtrees, we can then reconstruct the binary tree uniquely from the preorder and inorder
traversals.
E18. Either prove or disprove (by finding a counterexample) the analogous result for inorder and postorder
traversal.
Answer Since postorder traversal shows the root of the tree last, it is easy to detect the root key. The
inorder sequence then shows which of the remaining keys occur in the left and the right subtrees
as those appearing on the left and on the right of the root. Proceeding by recursion with each
of the subtrees, we can then reconstruct the binary tree uniquely from the postorder and inorder
traversals.
E19. Either prove or disprove the analogous result for preorder and postorder traversal.
Answer It is not possible to reconstruct the binary tree uniquely from a preorder and postorder traversal.
Consider the tree of two nodes whose root has key 1 and whose right child has key 2. This binary
tree would have the same preorder and postorder traversal as the tree whose root has key 1 and
whose left child has key 2.
E20. Find a pair of sequences of the same data that could not possibly correspond to the preorder and inorder
traversals of the same binary tree. [Hint: Keep your sequences short; it is possible to solve this exercise
with only three items of data in each sequence.]
Answer The sequence a b c could not possibly represent the inorder traversal of a binary tree if the
sequence b c a represented the preorder traversal. For the preorder sequence shows that b is the
root, and then the inorder sequence shows that a is in the left subtree and c is in the right subtree.
But then c cannot come before a as required for the preorder sequence.
Section 10.2 • Binary Search Trees 449
c n
a e s
d h p
E1. Show the keys with which each of the following targets will be compared in a search of the preceding
binary search tree.
(a) c (d) a (g) f
Answer k, c k, c, a k, c, e, h (fails)
Answer k, n, s k, c, e, d k, c, a (fails)
(a) m (b) f
k k
c n c n
a e s a e s
d h p d h p
(c) b (d) t
k k
c n c n
a e s a e s
b d h p d h p t
450 Chapter 10 • Binary Trees
(e) c (f) s
k k
c n c n
a e s a e s
d h p d h p s*
E3. Delete each of the following keys from the preceding binary search tree, using the algorithm developed in
this section. Do each part independently, deleting the key from the original tree.
(a) a (b) p
k k
c n c n
e s a e s
d h p d h
(c) n (d) s
k k
c s c n
a e p a e p
d h d h
Section 10.2 • Binary Search Trees 451
(e) e (f) k
k n
c n s
a h s p
d p c
a e
d p
E4. Draw the binary search trees that function insert will construct for the list of 14 names presented in each
of the following orders and inserted into a previously empty binary search tree.
(a) Jan Guy Jon Ann Jim Eva Amy Tim Ron Kim Tom Roy Kay Dot
Answer
Jan
Guy Jon
Kay
(b) Amy Tom Tim Ann Roy Dot Eva Ron Kim Kay Guy Jon Jan Jim
452 Chapter 10 • Binary Trees
Answer
Amy
Tom
Tim
Ann
Roy
Dot
Eva
Ron
Kim
Kay
Guy
Jon
Jan
Jim
(c) Jan Jon Tim Ron Guy Ann Jim Tom Amy Eva Roy Kim Dot Kay
Answer
Jan
Guy Jon
Kay
(d) Jon Roy Tom Eva Tim Kim Ann Ron Jan Amy Dot Guy Jim Kay
Answer
Jon
Eva Roy
E5. Consider building two binary search trees containing the integer keys 1 to 63, inclusive, received in the
orders
(a) all the odd integers in order (1, 3, 5, . . . , 63), then 32, 16, 48, then the remaining even integers in order
(2, 4, 6, . . . ).
(b) 32, 16, 48, then all the odd integers in order (1, 3, 5, . . . , 63), then the remaining even integers in order
(2, 4, 6, . . . ).
Section 10.2 • Binary Search Trees 453
Which of these trees will be quicker to build? Explain why. [Try to answer this question without actually
drawing the trees.]
Answer The second tree will be quicker to build, because it is more balanced than the first. (For example,
in the first tree all entries go to the right of the root entry of 1, whereas in the second tree equal
numbers of entries are placed on the two sides of the root entry of 32.)
E6. All parts of this exercise refer to the binary search trees shown in Figure 10.8 and concern the different
orders in which the keys a, b, . . . , g can be inserted into an initially empty binary search tree.
(a) Give four different orders for inserting the keys, each of which will yield the binary search tree shown in
part (a).
Answer 1. d, b, f, a, c, e, g 3. d, f, g, e, b, c, a
2. d, b, a, c, f, g, e 4. d, f, b, g, e, c, a
There are many other possible orders.
(b) Give four different orders for inserting the keys, each of which will yield the binary search tree shown in
part (b).
Answer 1. e, b, a, d, c, f, g 3. e, f, g, b, d, c, a
2. e, b, d, a, c, f, g 4. e, b, f, a, d, g, e
There are several other possible orders.
(c) Give four different orders for inserting the keys, each of which will yield the binary search tree shown in
part (c).
Answer 1. a, g, e, b, d, c, f 3. a, g, e, b, f, d, c
2. a, g, e, b, d, f, c 4. a, g, e, f, b, d, c
These are the only possible orders.
(d) Explain why there is only one order for inserting the keys that will produce a binary search tree that
reduces to a given chain, such as the one shown in part (d) or in part (e).
Answer The parent must be inserted before its children. In a chain, each node (except the last) is the
parent of exactly one other node, and hence the nodes must be inserted in descending order,
starting with the root.
E7. The use of recursion in function insert is not essential, since it is tail recursion. Rewrite function insert
in nonrecursive form. [You will need a local pointer variable to move through the tree.]
Answer We introduce a local pointer variable sub_root that will move to the left or right subtree as we
compare keys, searching for the place to insert the new node. If the new key is not a duplicate, then
this search would normally terminate with sub_root == NULL. To make the insertion, however,
we need the value of sub_root one step before it becomes NULL. We therefore introduce another
pointer parent to record this information. We must be careful to treat the case of an empty tree
specially, because in this case, no initial value can be supplied for the pointer parent.
454 Chapter 10 • Binary Trees
Answer Listings of most of the methods appear in the text. Here is the complete package. Binary node
class:
enum Balance_factor { left_higher, equal_height, right_higher };
template <class Entry>
struct Binary_node {
// data members:
Entry data;
Binary_node<Entry> *left;
Binary_node<Entry> *right;
// constructors:
Binary_node();
Binary_node(const Entry &x);
// virtual methods:
virtual void set_balance(Balance_factor b);
virtual Balance_factor get_balance() const;
};
Implementation:
{
if (sub_root != NULL) {
recursive_inorder(sub_root->left, visit);
(*visit)(sub_root->data);
recursive_inorder(sub_root->right, visit);
}
}
*/
{
if (sub_root != NULL) {
(*visit)(sub_root->data);
recursive_preorder(sub_root->left, visit);
recursive_preorder(sub_root->right, visit);
}
}
{
if (sub_root != NULL) {
recursive_postorder(sub_root->left, visit);
recursive_postorder(sub_root->right, visit);
(*visit)(sub_root->data);
}
}
{
if (sub_root == NULL) sub_root = new Binary_node<Entry>(x);
else
if (recursive_height(sub_root->right) <
recursive_height(sub_root->left))
recursive_insert(sub_root->right, x);
else
recursive_insert(sub_root->left, x);
}
{
if (sub_root == NULL) return 0;
return 1 + recursive_size(sub_root->left) +
recursive_size(sub_root->right);
}
{
if (sub_root == NULL) return 0;
int l = recursive_height(sub_root->left);
int r = recursive_height(sub_root->right);
if (l > r) return 1 + l;
else return 1 + r;
}
Section 10.2 • Binary Search Trees 457
template <class Entry>
void Binary_tree<Entry>::recursive_clear(Binary_node<Entry> *&sub_root)
/*
Post: The subtree rooted at
sub_root is cleared.
*/
{
Binary_node<Entry> *temp = sub_root;
if (sub_root == NULL) return;
recursive_clear(sub_root->left);
recursive_clear(sub_root->right);
sub_root = NULL;
delete temp;
}
{
if (sub_root == NULL) return NULL;
Binary_node<Entry> *temp = new Binary_node<Entry>(sub_root->data);
temp->left = recursive_copy(sub_root->left);
temp->right = recursive_copy(sub_root->right);
return temp;
}
{
if (sub_root == NULL) return;
Binary_node<Entry> *temp = sub_root->left;
sub_root->left = sub_root->right;
sub_root->right = temp;
recursive_swap(sub_root->left);
recursive_swap(sub_root->right);
}
{
if (sub_root == NULL || sub_root->data == x) return sub_root;
else {
Binary_node<Entry> *&temp = find_node(sub_root->left, x);
if (temp != NULL) return temp;
else return find_node(sub_root->right, x);
}
}
{
if (sub_root == NULL) return not_present;
Binary_node<Entry> *to_delete = sub_root;
// Remember node to delete at end.
if (sub_root->right == NULL) sub_root = sub_root->left;
else if (sub_root->left == NULL) sub_root = sub_root->right;
void recursive_double_traverse
(Binary_node<Entry> *sub_root, void (*visit)(Entry &));
void recursive_bracketed
(Binary_node<Entry> *sub_root);
void recursive_interchange
(Binary_node<Entry> *sub_root);
int recursive_leaf_count
(Binary_node<Entry> *sub_root) const;
Error_code remove(Entry &);
void swap();
void recursive_inorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_preorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_postorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_insert(Binary_node<Entry> *&sub_root, const Entry &x);
int recursive_size(Binary_node<Entry> *sub_root) const;
int recursive_height(Binary_node<Entry> *sub_root) const;
void recursive_clear(Binary_node<Entry> *&sub_root);
Binary_node<Entry> *recursive_copy(Binary_node<Entry> *sub_root);
void recursive_swap(Binary_node<Entry> *sub_root);
Binary_node<Entry> *&find_node(Binary_node<Entry> *&,
const Entry &) const;
Error_code remove_root(Binary_node<Entry> *&sub_root);
protected:
// Add auxiliary function prototypes here.
Binary_node<Entry> *root;
};
Implementation:
{
root = NULL;
}
460 Chapter 10 • Binary Trees
{
Binary_tree<Entry> new_copy(original);
clear();
root = new_copy.root;
new_copy.root = NULL;
return *this;
}
{
recursive_swap(root);
}
{
if (sub_root != NULL) {
(*visit)(sub_root->data);
recursive_double_traverse(sub_root->left, visit);
(*visit)(sub_root->data);
recursive_double_traverse(sub_root->right, visit);
}
}
template <class Entry>
void Binary_tree<Entry>::double_traverse(void (*visit)(Entry &))
/*
Post: The tree is doubly traversed.
*/
{
recursive_double_traverse(root, visit);
}
{
Binary_node<Entry> *sub_root;
if (root != NULL) {
Queue<Binary_node<Entry> *> waiting_nodes;
waiting_nodes.append(root);
do {
waiting_nodes.retrieve(sub_root);
(*visit)(sub_root->data);
if (sub_root->left) waiting_nodes.append(sub_root->left);
if (sub_root->right) waiting_nodes.append(sub_root->right);
waiting_nodes.serve();
} while (!waiting_nodes.empty());
}
}
template <class Entry>
int Binary_tree<Entry>::width()
/*
Post: The width of the tree is returned.
*/
{
if (root == NULL) return 0;
Extended_queue<Binary_node<Entry> *> current_level;
current_level.append(root);
int max_level = 0;
while (current_level.size() != 0) {
if (current_level.size() > max_level)
max_level = current_level.size();
Extended_queue<Binary_node<Entry> *> next_level;
do {
Binary_node<Entry> *sub_root;
current_level.retrieve(sub_root);
if (sub_root->left) next_level.append(sub_root->left);
if (sub_root->right) next_level.append(sub_root->right);
current_level.serve();
} while (!current_level.empty());
current_level = next_level;
}
return max_level;
}
template <class Entry>
int Binary_tree<Entry>::recursive_leaf_count
(Binary_node<Entry> *sub_root) const
/*
Post: The number of leaves in the subtree
rooted at sub_root is returned.
*/
{
if (sub_root == NULL) return 0;
if (sub_root->left == NULL && sub_root->right == NULL) return 1;
return recursive_leaf_count(sub_root->left)
+ recursive_leaf_count(sub_root->right);
}
template <class Entry>
int Binary_tree<Entry>::leaf_count() const
/*
Section 10.2 • Binary Search Trees 465
Post: The number of leaves in the tree is returned.
*/
{
return recursive_leaf_count(root);
}
Implementation:
{
return search_and_destroy(root, target);
}
/*
Post: If there is an entry in the tree whose key matches that in target,
the parameter target is replaced by the corresponding record from
the tree and a code of success is returned. Otherwise
a code of not_present is returned.
Uses: function search_for_node
*/
{
Error_code result = success;
Binary_node<Record> *found = search_for_node(root, target);
if (found == NULL)
result = not_present;
else
target = found->data;
return result;
}
Section 10.2 • Binary Search Trees 467
P2. Produce a menu-driven demonstration program to illustrate the use of binary search trees. The entries
may consist of keys alone, and the keys should be single characters. The minimum capabilities that the
demonstration user should be able to demonstrate include constructing the tree, inserting and removing an entry with
program a given key, searching for a target key, and traversing the tree in the three standard orders. The project
may be enhanced by the inclusion of additional capabilities written as exercises in this and the previous
section. These include determining the size of the tree, printing out all entries arranged to show the shape
of the tree, and traversing the tree in various ways. Keep the functions in your project as modular as
possible, so that you can later replace the package of operations for a binary search tree by a functionally
equivalent package for another kind of tree.
Answer
#include "../../c/utility.h"
void help()
/* PRE: None.
POST: Instructions for the tree operations have been printed.
*/
{
cout << "\n";
cout << "\t[S]ize [I]nsert [D]elete \n"
"\t[H]eight [E]rase [F]ind \n"
"\t[W]idth [C]ontents [L]eafs [B]readth first \n"
"\t[P]reorder P[O]storder I[N]order [?]help \n" << endl;
}
#include <string.h>
char get_command()
/* PRE: None.
POST: A character command belonging to the set of legal commands for
the tree demonstration has been returned.
*/
{
char c, d;
cout << "Select command (? for help) and press <Enter>:";
while (1) {
do {
cin.get(c);
} while (c == ’\n’);
do {
cin.get(d);
} while (d != ’\n’);
c = tolower(c);
if(strchr("clwbe?sidfponqh",c) != NULL)
return c;
cout << "Please enter a valid command or ? for help:" << endl;
help();
}
}
char get_char()
{
char c;
cin >>c;
return c;
}
#include "../../6/linklist/list.h"
#include "../../6/linklist/list.cpp"
#include "../../8/linklist/sortable.h"
#include "../../8/linklist/merge.cpp"
#include "queue.h"
#include "queue.cpp"
#include "extqueue.h"
#include "extqueue.cpp"
#include "node.h"
#include "tree.h"
#include "node.cpp"
#include "tree.cpp"
#include "stree.h"
#include "snode.cpp"
#include "stree.cpp"
{
char x;
switch (c) {
case ’?’: help();
break;
case ’s’:
cout << "The size of the tree is " << test_tree.size() << "\n";
break;
case ’i’:
cout << "Enter new character to insert:";
x = get_char();
test_tree.insert(x);
break;
case ’d’:
cout << "Enter character to remove:" << flush;
x = get_char();
if (test_tree.remove(x) != success) cout << "Not found!\n";
break;
Section 10.2 • Binary Search Trees 469
case ’f’:
cout << "Enter character to look for:";
x = get_char();
if (test_tree.tree_search(x) != success)
cout << " The entry is not present";
else
cout << " The entry is present";
cout << endl;
break;
case ’h’:
cout << "The height of the tree is " << test_tree.height() << "\n";
break;
case ’e’:
test_tree.clear();
cout << "Tree is cleared.\n";
break;
case ’w’:
cout << "The width of the tree is "
<< test_tree.width() << "\n";
break;
case ’c’:
test_tree.bracketed_print();
break;
case ’l’:
cout << "The leaf count of the tree is "
<< test_tree.leaf_count() << "\n";
break;
case ’b’:
test_tree.level_traverse(write_ent);
cout << endl;
break;
case ’p’:
test_tree.preorder(write_ent);
cout << endl;
break;
case ’o’:
test_tree.postorder(write_ent);
cout << endl;
break;
case ’n’:
test_tree.inorder(write_ent);
cout << endl;
break;
case ’q’:
cout << "Tree demonstration finished.\n";
return 0;
}
return 1;
}
470 Chapter 10 • Binary Trees
int main()
/* PRE: None.
POST: A binary tree demonstration has been performed.
USES: get_command, do_command, Tree methods
*/
{
Binary_tree<char> s; Binary_tree<char> t = s;
Search_tree<char> test_tree;
cout << "Menu driven demo program for a binary search tree of "
<< "char entries."
<< endl << endl;
P3. Write a function for treesort that can be added to Project P1 of Section 8.2 (page 328). Determine whether
treesort it is necessary for the list structure to be contiguous or linked. Compare the results with those for the
other sorting methods in Chapter 8.
Answer This method of sorting does not require the initial list to be in either a linked or contiguous
implementation. It however requires another structure in which to store and sort the entries of
the list. Use either of the sorting demonstration programs developed in Project P1 of Section 8.2
(page 328) and include the additional declarations and functions needed for a binary search tree
as developed in this section. The method is to remove all of the entries from the list and insert
them into the tree. To insert all the entries in order into the list, an inorder traversal is all that is
necessary.
P4. Write a function for searching, using a binary search tree with sentinel as follows: Introduce a new
sentinel search sentinel node, and keep a pointer called sentinel to it. See Figure 10.11. Replace all the NULL links
within the binary search tree with sentinel links (links to the sentinel). Then, for each search, store the
target key into the sentinel node before starting the search. Delete the test for an unsuccessful search
from tree_search, since it cannot now occur. Instead, a search that now finds the sentinel is actually an
unsuccessful search. Run this function on the test data of the preceding project to compare the performance
of this version with the original function tree_search.
Answer The directory contains a package for binary search trees with a sentinel for Project P4 and a
menu-driven demonstration program.
The files in the directory are:
Binary node class:
// data members:
Entry data;
Binary_node<Entry> *left;
Binary_node<Entry> *right;
// constructors:
Binary_node();
Binary_node(const Entry &x);
// virtual methods:
virtual void set_balance(Balance_factor b);
virtual Balance_factor get_balance() const;
};
Section 10.2 • Binary Search Trees 471
Implementation:
{
if (sub_root != sentinel) {
recursive_postorder(sub_root->left, visit);
recursive_postorder(sub_root->right, visit);
(*visit)(sub_root->data);
}
}
{
if (sub_root == sentinel) {
sub_root = new Binary_node<Entry>(x);
sub_root->left = sub_root->right = sentinel;
}
else
if (recursive_height(sub_root->right) <
recursive_height(sub_root->left))
recursive_insert(sub_root->right, x);
else
recursive_insert(sub_root->left, x);
}
{
if (sub_root == sentinel) return 0;
return 1 + recursive_size(sub_root->left) +
recursive_size(sub_root->right);
}
Section 10.2 • Binary Search Trees 473
template <class Entry>
int Binary_tree<Entry>::
recursive_height(Binary_node<Entry> *sub_root) const
/*
Post: The height of the subtree rooted at
sub_root is returned.
*/
{
if (sub_root == sentinel) return 0;
int l = recursive_height(sub_root->left);
int r = recursive_height(sub_root->right);
if (l > r) return 1 + l;
else return 1 + r;
}
{
Binary_node<Entry> *temp = sub_root;
if (sub_root == sentinel) return;
recursive_clear(sub_root->left);
recursive_clear(sub_root->right);
sub_root = sentinel;
delete temp;
}
{
if (sub_root == old_s) return new_s;
Binary_node<Entry> *temp = new Binary_node<Entry>(sub_root->data);
temp->left = recursive_copy(sub_root->left, old_s, new_s);
temp->right = recursive_copy(sub_root->right, old_s, new_s);
return temp;
}
{
if (sub_root == sentinel) return;
Binary_node<Entry> *temp = sub_root->left;
sub_root->left = sub_root->right;
sub_root->right = temp;
recursive_swap(sub_root->left);
recursive_swap(sub_root->right);
}
template <class Entry>
Binary_node<Entry> *&Binary_tree<Entry>::find_node(
Binary_node<Entry> *&sub_root, const Entry &x) const
/*
Post: If the subtree rooted at
sub_root contains x as a entry, a pointer
to the subtree node storing x is returned.
Otherwise the pointer sentinel is returned.
*/
{
if (sub_root == sentinel || sub_root->data == x) return sub_root;
else {
Binary_node<Entry> *&temp = find_node(sub_root->left, x);
if (temp != sentinel) return temp;
else return find_node(sub_root->right, x);
}
}
template <class Entry>
Error_code Binary_tree<Entry>::remove_root(Binary_node<Entry> *&sub_root)
/*
Pre: sub_root either points to the sentinel,
or points to a subtree of the Binary_tree.
Post: If sub_root is the sentinel, a code of not_present is returned.
Otherwise the root of the subtree is removed in such a way
that the properties of a binary search tree are preserved.
The parameter sub_root is reset as
the root of the modified subtree and
success is returned.
*/
{
if (sub_root == sentinel) return not_present;
Binary_node<Entry> *to_delete = sub_root;
// Remember node to delete at end.
if (sub_root->right == sentinel) sub_root = sub_root->left;
else if (sub_root->left == sentinel) sub_root = sub_root->right;
else { // Neither subtree is empty
to_delete = sub_root->left; // Move left to find predecessor
Binary_node<Entry> *parent = sub_root; // parent of to_delete
while (to_delete->right != sentinel) {
// to_delete is not the predecessor
parent = to_delete;
to_delete = to_delete->right;
}
sub_root->data = to_delete->data; // Move from to_delete to root
if (parent == sub_root) sub_root->left = to_delete->left;
else parent->right = to_delete->left;
}
Section 10.2 • Binary Search Trees 475
delete to_delete; // Remove to_delete from tree
return success;
}
Binary_tree();
bool empty() const;
void preorder(void (*visit)(Entry &));
void inorder(void (*visit)(Entry &));
void postorder(void (*visit)(Entry &));
void recursive_double_traverse
(Binary_node<Entry> *sub_root, void (*visit)(Entry &));
void recursive_bracketed
(Binary_node<Entry> *sub_root);
void recursive_interchange
(Binary_node<Entry> *sub_root);
int recursive_leaf_count
(Binary_node<Entry> *sub_root) const;
Error_code remove(Entry &);
void swap();
void recursive_inorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_preorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_postorder(Binary_node<Entry> *, void (*visit)(Entry &));
void recursive_insert(Binary_node<Entry> *&sub_root, const Entry &x);
int recursive_size(Binary_node<Entry> *sub_root) const;
int recursive_height(Binary_node<Entry> *sub_root) const;
void recursive_clear(Binary_node<Entry> *&sub_root);
Binary_node<Entry> *recursive_copy(Binary_node<Entry> *sub_root,
Binary_node<Entry> *old_s, Binary_node<Entry> *new_s);
void recursive_swap(Binary_node<Entry> *sub_root);
Binary_node<Entry> *&find_node(Binary_node<Entry> *&,
const Entry &) const;
Error_code remove_root(Binary_node<Entry> *&sub_root);
476 Chapter 10 • Binary Trees
protected:
// Add auxiliary function prototypes here.
Binary_node<Entry> *root;
Binary_node<Entry> *sentinel;
};
Implementation:
{
clear();
delete sentinel;
}
template <class Entry>
Binary_tree<Entry>::Binary_tree (const Binary_tree<Entry> &original)
/*
Post: A new binary tree is initialized to copy original.
*/
{
sentinel = new Binary_node<Entry>;
sentinel->left = sentinel->right = sentinel;
root = recursive_copy(original.root, original.sentinel, sentinel);
}
template <class Entry>
Binary_tree<Entry> &Binary_tree<Entry>::operator =(const
Binary_tree<Entry> &original)
/*
Post: The binary tree is reset to copy original.
*/
{
Binary_tree<Entry> new_copy(original);
clear();
Binary_node<Entry> *temp = sentinel;
sentinel = new_copy.sentinel;
root = new_copy.root;
new_copy.root = temp;
new_copy.sentinel= temp;
return *this;
}
template <class Entry>
void Binary_tree<Entry>::swap()
/*
Post: All left and right subtrees are switched
in the binary tree.
*/
{
recursive_swap(root);
}
template <class Entry>
void Binary_tree<Entry>::recursive_double_traverse
(Binary_node<Entry> *sub_root, void (*visit)(Entry &))
/*
Post: The subtree rooted at sub_root is
doubly traversed.
*/
{
if (sub_root != sentinel) {
(*visit)(sub_root->data);
recursive_double_traverse(sub_root->left, visit);
(*visit)(sub_root->data);
recursive_double_traverse(sub_root->right, visit);
}
}
Section 10.2 • Binary Search Trees 479
template <class Entry>
void Binary_tree<Entry>::double_traverse(void (*visit)(Entry &))
/*
Post: The tree is doubly traversed.
*/
{
recursive_double_traverse(root, visit);
}
template <class Entry>
void Binary_tree<Entry>::recursive_bracketed
(Binary_node<Entry> *sub_root)
/*
Post: The subtree rooted at sub_root is
printed with brackets.
*/
{
if (sub_root != sentinel) {
if (sub_root->left == sentinel && sub_root->right == sentinel)
cout << sub_root->data;
else {
cout << "(" << sub_root->data << ":";
recursive_bracketed(sub_root->left);
cout << ",";
recursive_bracketed(sub_root->right);
cout << ")";
}
}
}
template <class Entry>
void Binary_tree<Entry>::bracketed_print()
/*
Post: The tree is printed with brackets.
*/
{
recursive_bracketed(root);
cout << endl;
}
template <class Entry>
void Binary_tree<Entry>::recursive_interchange
(Binary_node<Entry> *sub_root)
/*
Post: In the subtree rooted at
sub_root all pairs of left and right
links are swapped.
*/
{
if (sub_root != sentinel) {
Binary_node<Entry> *tmp = sub_root->left;
sub_root->left = sub_root->right;
sub_root->right = tmp;
recursive_interchange(sub_root->left);
recursive_interchange(sub_root->right);
}
}
480 Chapter 10 • Binary Trees
{
if (sub_root == sentinel || sub_root->data == target)
return remove_root(sub_root);
else if (target < sub_root->data)
return search_and_destroy(sub_root->left, target);
else
return search_and_destroy(sub_root->right, target);
}
Implementation:
#include "../../c/utility.h"
void help()
/* PRE: None.
POST: Instructions for the tree operations have been printed.
*/
{
cout << "\n";
cout << "\t[S]ize [I]nsert [D]elete \n"
"\t[H]eight [E]rase [F]ind \n"
"\t[W]idth [C]ontents [L]eafs [B]readth first \n"
"\t[P]reorder P[O]storder I[N]order [?]help \n" << endl;
}
#include <string.h>
char get_command()
/* PRE: None.
POST: A character command belonging to the set of legal commands for the
tree demonstration has been returned.
*/
{
char c, d;
cout << "Select command (? for help) and press <Enter>:";
while (1) {
do {
cin.get(c);
} while (c == ’\n’);
do {
cin.get(d);
} while (d != ’\n’);
c = tolower(c);
484 Chapter 10 • Binary Trees
if(strchr("clwbe?sidfponqh",c) != NULL)
return c;
cout << "Please enter a valid command or ? for help:" << endl;
help();
}
}
// auxiliary input/output functions
void write_ent(char &x)
{
cout << x;
}
char get_char()
{
char c;
cin >>c;
return c;
}
// include auxiliary data structures
#include "../../6/linklist/list.h"
#include "../../6/linklist/list.cpp"
#include "../../8/linklist/sortable.h"
#include "../../8/linklist/merge.cpp"
#include "queue.h"
#include "queue.cpp"
#include "extqueue.h"
#include "extqueue.cpp"
// include binary search tree data structure
#include "node.h"
#include "tree.h"
#include "node.cpp"
#include "tree.cpp"
#include "stree.h"
#include "snode.cpp"
#include "stree.cpp"
int do_command(char c, Search_tree<char> &test_tree)
/* PRE: The tree has been created and command is a valid tree
operation.
POST: The command has been executed.
USES: All the functions that perform tree operations.
*/
{
char x;
switch (c) {
case ’?’: help();
break;
case ’s’:
cout << "The size of the tree is " << test_tree.size() << "\n";
break;
case ’i’:
cout << "Enter new character to insert:";
x = get_char();
test_tree.insert(x);
break;
Section 10.2 • Binary Search Trees 485
case ’d’:
cout << "Enter character to remove:" << flush;
x = get_char();
if (test_tree.remove(x) != success) cout << "Not found!\n";
break;
case ’f’:
cout << "Enter character to look for:";
x = get_char();
if (test_tree.tree_search(x) != success)
cout << " The entry is not present";
else
cout << " The entry is present";
cout << endl;
break;
case ’h’:
cout << "The height of the tree is " << test_tree.height() << "\n";
break;
case ’e’:
test_tree.clear();
cout << "Tree is cleared.\n";
break;
case ’w’:
cout << "The width of the tree is "
<< test_tree.width() << "\n";
break;
case ’c’:
test_tree.bracketed_print();
break;
case ’l’:
cout << "The leaf count of the tree is "
<< test_tree.leaf_count() << "\n";
break;
case ’b’:
test_tree.level_traverse(write_ent);
cout << endl;
break;
case ’p’:
test_tree.preorder(write_ent);
cout << endl;
break;
case ’o’:
test_tree.postorder(write_ent);
cout << endl;
break;
case ’n’:
test_tree.inorder(write_ent);
cout << endl;
break;
486 Chapter 10 • Binary Trees
case ’q’:
cout << "Tree demonstration finished.\n";
return 0;
}
return 1;
}
int main()
/* PRE: None.
POST: A binary tree demonstration has been performed.
USES: get_command, do_command, Tree methods
*/
{
Binary_tree<char> s; Binary_tree<char> t = s;
Search_tree<char> test_tree;
cout << "Menu driven demo program for a binary search tree of "
<< "char entries."
<< endl << endl;
while (do_command(get_command(), test_tree));
}
P5. Different authors tend to use different vocabularies and to use common words with differing frequencies.
information retrieval Given an essay or other text, it is interesting to find what distinct words are used and how many times
program each is used. The purpose of this project is to compare several different kinds of binary search trees
useful for this information retrieval problem. The current, first part of the project is to produce a driver
program and the information-retrieval package using ordinary binary search trees. Here is an outline of
the main driver program:
➥ The input to the driver will be a file. The program will be executed with several different files; the name
of the file to be used should be requested from the user while the program is running.
➥ A word is defined as a sequence of letters, together with apostrophes (’) and hyphens (-), provided that
the apostrophe or hyphen is both immediately preceded and followed by a letter. Uppercase and lowercase
letters should be regarded as the same (by translating all letters into either uppercase or lowercase, as
you prefer). A word is to be truncated to its first 20 characters (that is, only 20 characters are to be
stored in the data structure) but words longer than 20 characters may appear in the text. Nonalphabetic
characters (such as digits, blanks, punctuation marks, control characters) may appear in the text file. The
appearance of any of these terminates a word, and the next word begins only when a letter appears.
➥ Be sure to write your driver so that it will not be changed at all when you change implementation of data
structures later.
Section 10.2 • Binary Search Trees 487
347
Here are specifications for the functions to be implemented first with binary search trees.
void write_method( );
postcondition: The function has written a short string identifying the abstract data type used
for structure.
#include <stdlib.h>
#include <string.h>
#include "../../c/utility.h"
#include "../../c/utility.cpp"
#include "../../6/doubly/list.h"
#include "../../6/doubly/list.cpp"
#include "../../6/strings/string.h"
#include "../../6/strings/string.cpp"
#include "../bt/node.h"
#include "../bt/tree.h"
#include "../bt/node.cpp"
#include "../bt/tree.cpp"
#include "../bst/stree.h"
#include "../bst/snode.cpp"
#include "../bst/stree.cpp"
#include "key.h"
#include "key.cpp"
#include "record.h"
#include "record.cpp"
#include "auxil.cpp"
#include "../../c/timer.h"
#include "../../c/timer.cpp"
int main(int argc, char *argv[]) // count, values of command-line arguments
/*
Pre: Name of an input file can be given as a command-line argument.
Post: The word storing project p6 has been performed.
*/
{
write_method();
Binary_tree<Record> t;
Search_tree<Record> the_words;
488 Chapter 10 • Binary Trees
char infile[1000];
char in_string[1000];
char *in_word;
Timer clock;
do {
int initial_comparisons = Key::comparisons;
clock.reset();
if (argc < 2) {
cout << "What is the input data file: " << flush;
cin >> infile;
}
else strcpy(infile, argv[1]);
while (!file_in.eof()) {
file_in >> in_string;
int position = 0;
bool end_string = false;
while (!end_string) {
in_word = extract_word(in_string, position, end_string);
if (strlen(in_word) > 0) {
int counter;
String s(in_word);
update(s, the_words, counter);
}
}
}
cout << "Elapsed time: " << clock.elapsed_time() << endl;
cout << "Comparisons performed = "
<< Key::comparisons - initial_comparisons << endl;
cout << "Do you want to print frequencies? ";
if (user_says_yes()) print(the_words);
cout << "Do you want to add another file of input? ";
} while (user_says_yes());
}
void write_method()
/*
Post: A short string identifying the abstract
data type used for the structure is written.
*/
{
cout << " Word frequency program: Binary Search Tree implementation";
cout << endl;
}
Section 10.2 • Binary Search Trees 489
void update(const String &word, Search_tree<Record> &structure,
int &num_comps)
/*
Post: If word was not already present in structure, then
word has been inserted into structure and its frequency
count is 1. If word was already present in structure,
then its frequency count has been increased by 1. The
variable parameter num_comps is set to the number of
comparisons of words done.
*/
{
int initial_comparisons = Key::comparisons;
Record r((Key) word);
if (structure.tree_search(r) == not_present)
structure.insert(r);
else {
structure.remove(r);
r.increment();
structure.insert(r);
}
num_comps = Key::comparisons - initial_comparisons;
}
void wr_entry(Record &a_word)
{
String s = ((Key) a_word).the_key();
cout << s.c_str();
for (int i = strlen(s.c_str()); i < 12; i++) cout << " ";
cout << " : " << a_word.frequency() << endl;
}
void print(Search_tree<Record> &structure)
/*
Post: All words in structure are printed at the terminal
in alphabetical order together with their frequency counts.
*/
{
structure.inorder(wr_entry);
}
char *extract_word(char *in_string, int &examine, bool &over)
{
int ok = 0;
while (in_string[examine] != ’\0’) {
if (’a’ <= in_string[examine] && in_string[examine] <= ’z’)
in_string[ok++] = in_string[examine++];
else if (’A’ <= in_string[examine] && in_string[examine] <= ’Z’)
in_string[ok++] = in_string[examine++] - ’A’ + ’a’;
else if ((’\’’ == in_string[examine] || in_string[examine] == ’-’)
&& (
(’a’ <= in_string[examine + 1] &&
in_string[examine + 1] <= ’z’) ||
(’A’ <= in_string[examine + 1] &&
in_string[examine + 1] <= ’Z’)))
in_string[ok++] = in_string[examine++];
else break;
}
490 Chapter 10 • Binary Trees
in_string[ok] = ’\0’;
if (in_string[examine] == ’\0’) over = true;
else examine++;
return in_string;
}
Methods for keys and records to include into a binary search tree for the project.
class Key {
String key;
public:
static int comparisons;
Key (String x = "");
String the_key() const;
};
Implementation:
int Key::comparisons = 0;
Key::Key (String x)
{
key = x;
}
Record definitions:
class Record {
public:
Record(); // default constructor
operator Key() const; // cast to Key
Implementation:
Record::Record() : name()
{
count = 1;
}
int Record::frequency()
{
return count;
}
void Record::increment()
{
count++;
}
Two text files are included for testing: COWPER and RHYMES.
492 Chapter 10 • Binary Trees
Exercises 10.3
E1. Draw the sequence of partial binary search trees (like Figure 10.13) that the method in this section will
construct for the following values of n . (a) 6, 7, 8; (b) 15, 16, 17; (c) 22, 23, 24; (d) 31, 32, 33.
Answer
n=6 n=7 n=8
4 4 8
2 6 2 6 4
1 3 5 1 3 5 7 2 6
1 3 5 7
E2. Write function build_tree for the case when supply is a queue.
Answer We assume that an implementation of a template class Queue with the standard Queue operations
is available.
E3. Write function build_tree for the case when the input structure is a binary search tree. [This version
gives a function to rebalance a binary search tree.]
Answer We first convert the input tree into a List and then apply the standard build_tree method from
the text.
Section 10.3 • Building a Binary Search Tree 493
template < class Record >
Error_code Buildable_tree< Record > :: build_tree(const Search_tree< Record > &supply)
/* Post: If the entries of supply are in increasing order, a code of success is returned and the
Buildable_tree is built out of these entries as a balanced tree. Otherwise, a code of fail
is returned and a balanced tree is constructed from the longest increasing sequence of
entries at the start of supply.
Uses: The methods of class Search_tree and the functions build_insert, connect_subtrees, and
find_root */
{
List< Record > resupply;
supply. tree_list(resupply);
return build_tree(resupply);
}
An auxiliary recursive method to turn a binary search tree into an ordered list is coded as follows.
E4. Write a version of function build_tree that will read keys from a file, one key per line. [This version gives
a function that reads a binary search tree from an ordered file.]
Answer We assume that the file has been opened as a readable ifstream object and that entries can be read
from the stream with the input operator >> . (If necessary, a client would overload the operator >>
for an instantiation of the template parameter class Record.) The implementation follows.
while (1) {
supply >> x;
if (count > 0 && x <= last_x) {ordered_data = fail; break; }
build_insert( ++ count, x, last_node);
last_x = x;
}
root = find_root(last_node);
connect_trees(last_node);
return ordered_data; // Report any data-ordering problems back to client.
}
E5. Extend each of the binary search trees shown in Figure 10.8 into a 2-tree.
Answer
d e
b f b f
a c e g a d g
(a)
(b)
a a a
g b
b
e g
c
b f f
d
d c
e
c e
d f
(c)
g
(d)
(e)
E6. There are 6 = 3! possible ordered sequences of the three keys 1, 2, 3, but only 5 distinct binary trees with
three nodes. Therefore, these binary trees are not equally likely to occur as search trees. Find which one
of the five binary search trees corresponds to each of the six possible ordered sequences of 1, 2, 3. Thereby
find the probability for building each of the binary search trees from randomly ordered input.
Answer Refer to Figure 10.2, the binary trees with three nodes, in the text. The following ordered se-
quences of keys will result in a binary search tree of one the listed shapes.
Section 10.3 • Building a Binary Search Tree 495
The probability of getting each tree is 16 except for the third tree, which is twice as likely, 1
3
probability, to occur than the others, due to its symmetrical shape.
E7. There are 24 = 4! possible ordered sequences of the four keys 1, 2, 3, 4, but only 14 distinct binary trees
with four nodes. Therefore, these binary trees are not equally likely to occur as search trees. Find which
one of the 14 binary search trees corresponds to each of the 24 possible ordered sequences of 1, 2, 3, 4.
Thereby find the probability for building each of the binary search trees from randomly ordered input.
Answer Of the 14 distinct binary trees, 8 are chains, each of which is obtained by only one input order
1
(the preorder traversal of the tree). Hence each chain has probability 24 . There are 2 trees with a
branch that is not at the root. The two nodes below this branch may be inserted in either order,
2 1
so each such tree has probability 24 or 12 . There are 4 trees with a branch at the root. Each of
these has a one-node tree on one side and a two-node tree on the other side of the root. The node
on a side by itself may be inserted at any of three times relative to the two nodes on the other
3
side, so each of these trees has probability 24 or 18 .
E8. If T is an arbitrary binary search tree, let S(T ) denote the number of ordered sequences of the keys in
T that correspond to T (that is, that will generate T if they are inserted, in the given order, into an
initially-empty binary search tree). Find a formula for S(T ) that depends only on the sizes of L and R
and on S(L) and S(R) , where L and R are the left and right subtrees of the root of T .
Answer If T = is the empty binary search tree, then it is constructed in exactly one way, from the empty
sequence, so S()= 1. Otherwise, let r be the key in the root of T , and let L and R be the left
and right subtrees of the root. Take any sequence of keys that generates T . Its first entry must
be r , since the root of T must be inserted before any other node. Remove r from the sequence
and partition the remaining sequence into two subsequences, with all the keys from L in one
subsequence and all the keys from R in the other. Then these two subsequences, separately, will
generate L and R . On the other hand, we can obtain a sequence that generates T by starting
with any sequences that generate L and R , merging these sequences together in any possible
way (preserving their orders), and putting r at the start of the sequence. How many ways are
there to merge two ordered sequences? If the two sequences have lengths nl and nr , then they
will merge into a sequence of length nl + nr in which nl of the places are filled with entries from
the first sequence (in the same order) and the remaining nr places are filled with entries from
the second sequence. Hence, the number of ways of merging the two sequences is the number
of ways of choosing nl positions out of nl + nr positions, which is the binomial coefficient
!
nl + nr
.
nl
It follows that, if T is a nonempty binary search tree whose root has subtrees L and R of sizes nl
and nr , then the number of ordered sequences of keys that will generate T is exactly
!
n l + nr
S(T )= · S(L)·S(R).
nl
496 Chapter 10 • Binary Trees
Answer (a) The tree is not an AVL tree because the right subtree is two levels taller than the left subtree.
(b) The left child of the root is too much unbalanced to the left.
(c) The top four nodes are all too unbalanced.
(d) The tree is not an AVL tree because the right subtree is two levels taller than the left subtree.
E2. In each of the following, insert the keys, in the order shown, to build them into an AVL tree.
(a) A, Z, B, Y, C, X. (d) A, Z, B, Y, C, X, D, W, E, V, F.
(b) A, B, C, D, E, F. (e) A, B, C, D, E, F, G, H, I, J, K, L.
(c) M, T, E, A, Z, G, P. (f) A, V, L, T, R, E, I, S, O, K.
Answer
(a) C (b) D
B Y B E
A X Z A C F
(c) M (d) E
E T C W
A G P Z B D V Y
A F X Z
H
(e)
D J
B F I K
A C E G L
Section 10.4 • Height Balance: AVL Trees 497
(f ) L
E T
A I R V
K O S
E3. Delete each of the keys inserted in Exercise E2 from the AVL tree, in LIFO order (last key inserted is first
removed).
Answer (a)
Delete B Delete
X, C, Y B, Z, A
A Z
(b)
Delete B Delete
F, E D, C, B, A
A D
(c) No rotations are required in the process of deleting all nodes in the given order.
(d)
Delete E Delete W
F, V E
C Y C Y
B D W Z B D X Z
A X A
Delete D Delete X
W D
B Y B Y
A C X Z A C Z
B Z A Z
A C
498 Chapter 10 • Binary Trees
(e)
Delete D Delete D
L, K, J I
B H B F
A C F I A C E H
E G G
Delete B Delete
H, G, F, E, D, C, B, A
A D
(f) No rotations are required in the process of deleting all nodes in the given order.
E4. Delete each of the keys inserted in Exercise E2 from the AVL tree, in FIFO order (first key inserted is first
removed).
Answer (a)
Delete X Delete
A, Z, B Y, C, X
C Y
(b)
Delete E Delete
A, B, C D, E, F
D F
(c)
Delete P Delete P Delete
M, T E, A Z, G, P
E Z G Z
A G
(d)
Delete E Delete V
A, Z, B, Y C
C W E W
D V X D F X
Delete F Delete
X, D, W E, V, F
E V
Section 10.4 • Height Balance: AVL Trees 499
(e)
Delete H Delete J
A, B, C D, E, F
F J H K
D G I K G I L
E L
Delete K Delete
G, H, I J, K, L
J L
(f)
Delete A L Delete V L
I T I R
E K R V E K O T
O S S
Delete L Delete O
O
T, R, E, I
I S K S
Delete
E K R T S, O, K
E5. Start with the following AVL tree and remove each of the following keys. Do each removal independently,
starting with the original tree each time.
e l
b f j m
a c g i k
Answer
(a)
–
e l
–
–
b f j – m
–
–
a – c g – – i
–
d –
(b)
– h
e – l
–
b – f – j – m
–
d
a – – c g – – i – k
(c)
h
–
e l
–
–
Unchanged k
j – m
–
– i k
Section 10.4 • Height Balance: AVL Trees 501
(d)
h –
Unchanged
b e –
=
Rotate: Unchanged
a c c – f
–
– d b – – d g –
(e)
e
=
h –
b f – Double
–
rotate
c – Unchanged
a – c g –
–
b – e
–
– d
a – d – – f
(f)
h h
–
–
Unchanged Unchanged
= l j
–
j – – m – i l
–
i – – k Rotate – k
(g)
i
h i
–
Unchanged or Unchanged
– l – l
j – m j – m
–
i – k – k
E6. Write a method that returns the height of an AVL tree by tracing only one path to a leaf, not by investigating
all the nodes in the tree.
{
if (sub_root == NULL) return 0;
if (sub_root->get_balance( ) == right_higher)
return 1 + recursive_height(sub_root->right);
return 1 + recursive_height(sub_root->left);
}
template < class Record >
int AVL_tree< Record > :: height( )
/* Post: Returns the height of an AVL tree. */
{
return recursive_height(root);
}
E7. Write a function that returns a pointer to the leftmost leaf closest to the root of a nonempty AVL tree.
Answer The following implementation returns a pointer to a private node object from within a tree
structure. It should therefore only be available as a private auxiliary member function of an AVL
tree.
Answer Let x0 , x1 , . . . , xn be the nodes encountered along the path from the removed node to the root of
the tree, where x0 is the removed node, x1 is the parent of the removed node, and xn is the root
of the tree. Let Ti be the tree rooted at xi , before the removal is made, where i = 0, . . . , n . For
any tree T , let ht(T ) denote the height of T . We then know that ht(Ti )≥ ht(Ti−1 )+1 for i = 1,
. . . , n . If a single or double rotation occurs at xi , then we see from Figure 9.20, Cases 3(a, b, c),
that ht(Ti )= ht(Ti−1 )+2. Let R be the set of indices i from {1, . . . , n} for which rotations occur
at xi , and let S be the remaining indices (for which no rotations occur). Let |R| be the number
of indices in R , so that the number in S is |S| = n − |R| . Since ht(T0 )≥ 0, we have:
n
X
ht(Tn ) = ht(Ti )−ht(Ti−1 ) + ht(T0 )
i=1
X X
= ht(Ti )−ht(Ti−1 ) + ht(Ti )−ht(Ti−1 ) + ht(T0 )
R S
{
Error_code result = remove_avl(sub_root->right, target, shorter);
if (shorter == true) switch (sub_root->get_balance()) {
case equal_height: // case 1 in text
sub_root->set_balance(left_higher);
shorter = false;
break;
case left_higher:
// case 3 in text: shortened shorter subtree; must rotate
Binary_node<Record> *temp = sub_root->left;
switch (temp->get_balance()) {
case equal_height: // case 3a
temp->set_balance(right_higher);
rotate_right(sub_root);
shorter = false;
break;
case left_higher:
sub_root->set_balance(right_higher);
temp->set_balance(equal_height);
break;
Section 10.4 • Height Balance: AVL Trees 505
case right_higher:
sub_root->set_balance(equal_height);
temp->set_balance(left_higher);
break;
}
temp_right->set_balance(equal_height);
rotate_left(sub_root->left);
rotate_right(sub_root);
break;
}
}
return result;
}
{
Error_code result = remove_avl(sub_root->left, target, shorter);
if (shorter == true) switch (sub_root->get_balance()) {
case equal_height: // case 1 in text
sub_root->set_balance(right_higher);
shorter = false;
break;
case right_higher:
// case 3 in text: shortened shorter subtree; must rotate
Binary_node<Record> *temp = sub_root->right;
switch (temp->get_balance()) {
case equal_height: // case 3a
temp->set_balance(left_higher);
rotate_left(sub_root);
shorter = false;
break;
case right_higher:
sub_root->set_balance(left_higher);
temp->set_balance(equal_height);
break;
506 Chapter 10 • Binary Trees
case left_higher:
sub_root->set_balance(equal_height);
temp->set_balance(right_higher);
break;
}
temp_left->set_balance(equal_height);
rotate_right(sub_root->right);
rotate_left(sub_root);
break;
}
}
return result;
}
P2. Substitute the AVL tree class into the menu-driven demonstration program for binary search trees in
Section 10.2, Project P2 (page 460), thereby obtaining a demonstration program for AVL trees.
#include "../../c/utility.h"
void help()
/* PRE: None.
POST: Instructions for the tree operations have been printed. */
{
cout << "\n";
cout << "\t[S]ize [I]nsert [D]elete \n"
"\t[H]eight [E]rase [F]ind \n"
"\t[W]idth [C]ontents [L]eafs [B]readth first \n"
"\t[P]reorder P[O]storder I[N]order [?]help \n" << endl;
}
#include <string.h>
char get_command()
/* PRE: None.
POST: A character command belonging to the set of legal commands for the
tree demonstration has been returned.
*/
{
char c, d;
cout << "Select command (? for Help) and press <Enter>:";
while (1) {
do {
cin.get(c);
} while (c == ’\n’);
do {
cin.get(d);
} while (d != ’\n’);
c = tolower(c);
if(strchr("clwbe?sidfponqh",c) != NULL)
return c;
cout << "Please enter a valid command or ? for help:" << endl;
help();
}
}
// auxiliary input/output functions
Section 10.4 • Height Balance: AVL Trees 507
void write_ent(char &x)
{
cout << x;
}
char get_char()
{
char c;
cin >>c;
return c;
}
#include "../../6/linklist/list.h"
#include "../../6/linklist/list.cpp"
#include "../../8/linklist/sortable.h"
#include "../../8/linklist/merge.cpp"
#include "../2p1p2/queue.h"
#include "../2p1p2/queue.cpp"
#include "../2p1p2/extqueue.h"
#include "../2p1p2/extqueue.cpp"
#include "../2p1p2/node.h"
#include "../2p1p2/tree.h"
#include "../2p1p2/node.cpp"
#include "../2p1p2/tree.cpp"
#include "../2p1p2/stree.h"
#include "../2p1p2/snode.cpp"
#include "../2p1p2/stree.cpp"
#include "../avlt/anode.h"
#include "../avlt/atree.h"
#include "../avlt/anode.cpp"
#include "../avlt/atree.cpp"
{
char x;
switch (c) {
case ’?’: help();
break;
case ’s’:
cout << "The size of the tree is " << test_tree.size() << "\n";
break;
case ’i’:
cout << "Enter new character to insert:";
x = get_char();
test_tree.insert(x);
break;
508 Chapter 10 • Binary Trees
case ’d’:
cout << "Enter character to remove:" << flush;
x = get_char();
if (test_tree.remove(x) != success) cout << "Not found!\n";
break;
case ’f’:
cout << "Enter character to look for:";
x = get_char();
if (test_tree.tree_search(x) != success)
cout << " The entry is not present";
else
cout << " The entry is present";
cout << endl;
break;
case ’h’:
cout << "The height of the tree is " << test_tree.height() << "\n";
break;
case ’e’:
test_tree.clear();
cout << "Tree is cleared.\n";
break;
case ’w’:
cout << "The width of the tree is "
<< test_tree.width() << "\n";
break;
case ’c’:
test_tree.bracketed_print();
break;
case ’l’:
cout << "The leaf count of the tree is "
<< test_tree.leaf_count() << "\n";
break;
case ’b’:
test_tree.level_traverse(write_ent);
cout << endl;
break;
case ’p’:
test_tree.preorder(write_ent);
cout << endl;
break;
case ’o’:
test_tree.postorder(write_ent);
cout << endl;
break;
case ’n’:
test_tree.inorder(write_ent);
cout << endl;
break;
Section 10.4 • Height Balance: AVL Trees 509
case ’q’:
cout << "Tree demonstration finished.\n";
return 0;
}
return 1;
}
int main()
/* PRE: None.
POST: A binary tree demonstration has been performed.
USES: get_command, do_command, Tree methods
*/
{
Binary_tree<char> s; Binary_tree<char> t = s;
Search_tree<char> s1; Search_tree<char> t1 = s1;
AVL_tree<char> test_tree;
while (do_command(get_command(), test_tree));
}
Implementation:
Implementation:
case equal_height:
sub_root->set_balance(right_higher);
break;
case right_higher:
right_balance(sub_root);
taller = false; // Rebalancing always shortens the tree.
break;
}
}
return result;
}
template <class Record>
void AVL_tree<Record>::right_balance(Binary_node<Record> *&sub_root)
/*
Pre: sub_root points to a subtree of an AVL_tree that
is doubly unbalanced on the right.
Post: The AVL properties have been restored to the subtree.
Uses: Methods of struct AVL_node;
functions rotate_right and rotate_left.
*/
{
Binary_node<Record> *&right_tree = sub_root->right;
switch (right_tree->get_balance()) {
case right_higher: // single rotation left
sub_root->set_balance(equal_height);
right_tree->set_balance(equal_height);
rotate_left(sub_root);
break;
case equal_height: // impossible case
cout << "WARNING: program error detected in right_balance" << endl;
case left_higher: // double rotation left
Binary_node<Record> *sub_tree = right_tree->left;
switch (sub_tree->get_balance()) {
case equal_height:
sub_root->set_balance(equal_height);
right_tree->set_balance(equal_height);
break;
case left_higher:
sub_root->set_balance(equal_height);
right_tree->set_balance(right_higher);
break;
case right_higher:
sub_root->set_balance(left_higher);
right_tree->set_balance(equal_height);
break;
}
sub_tree->set_balance(equal_height);
rotate_right(right_tree);
rotate_left(sub_root);
break;
}
}
Section 10.4 • Height Balance: AVL Trees 513
template <class Record>
void AVL_tree<Record>::left_balance(Binary_node<Record> *&sub_root)
{
Binary_node<Record> *&left_tree = sub_root->left;
switch (left_tree->get_balance()) {
case left_higher:
sub_root->set_balance(equal_height);
left_tree->set_balance(equal_height);
rotate_right(sub_root);
break;
case right_higher:
Binary_node<Record> *sub_tree = left_tree->right;
switch (sub_tree->get_balance()) {
case equal_height:
sub_root->set_balance(equal_height);
left_tree->set_balance(equal_height);
break;
case right_higher:
sub_root->set_balance(equal_height);
left_tree->set_balance(left_higher);
break;
case left_higher:
sub_root->set_balance(right_higher);
left_tree->set_balance(equal_height);
break;
}
sub_tree->set_balance(equal_height);
rotate_left(left_tree);
rotate_right(sub_root);
break;
}
}
{
if (sub_root == NULL || sub_root->right == NULL) // impossible cases
cout << "WARNING: program error detected in rotate_left" << endl;
else {
Binary_node<Record> *right_tree = sub_root->right;
sub_root->right = right_tree->left;
right_tree->left = sub_root;
sub_root = right_tree;
}
}
514 Chapter 10 • Binary Trees
The removal method is not listed here, since it appears in the solution to Project P1.
P3. Substitute the AVL tree class into the information-retrieval project of Project P5 of Section 10.2 ((page
461)). Compare the performance of AVL trees with ordinary binary search trees for various combinations
of input text files.
Answer
#include <stdlib.h>
#include <string.h>
#include "../../c/utility.h"
#include "../../c/utility.cpp"
#include "../../6/doubly/list.h"
#include "../../6/doubly/list.cpp"
#include "../../6/strings/string.h"
#include "../../6/strings/string.cpp"
#include "../bt/node.h"
#include "../bt/tree.h"
#include "../bt/node.cpp"
#include "../bt/tree.cpp"
#include "../bst/stree.h"
#include "../bst/snode.cpp"
#include "../bst/stree.cpp"
#include "../2p5/key.h"
#include "../2p5/key.cpp"
#include "../2p5/record.h"
#include "../2p5/record.cpp"
#include "../avlt/anode.h"
#include "../avlt/atree.h"
#include "../avlt/anode.cpp"
#include "../avlt/atree.cpp"
#include "auxil.cpp"
#include "../../c/timer.h"
#include "../../c/timer.cpp"
int main(int argc, char *argv[]) // count, values of command-line arguments
/*
Pre: Name of an input file can be given as a command-line argument.
Post: The word storing project p6 has been performed.
*/
{
write_method();
Binary_tree<Record> t;
Search_tree<Record> t1;
Section 10.4 • Height Balance: AVL Trees 515
AVL_tree<Record> the_words;
char infile[1000];
char in_string[1000];
char *in_word;
Timer clock;
do {
int initial_comparisons = Key::comparisons;
clock.reset();
if (argc < 2) {
cout << "What is the input data file: " << flush;
cin >> infile;
}
else strcpy(infile, argv[1]);
while (!file_in.eof()) {
file_in >> in_string;
int position = 0;
bool end_string = false;
while (!end_string) {
in_word = extract_word(in_string, position, end_string);
if (strlen(in_word) > 0) {
int counter;
String s(in_word);
update(s, the_words, counter);
}
}
}
cout << "Elapsed time: " << clock.elapsed_time() << endl;
cout << "Comparisons performed = "
<< Key::comparisons - initial_comparisons << endl;
cout << "Do you want to print frequencies? ";
if (user_says_yes()) print(the_words);
cout << "Do you want to add another file of input? ";
} while (user_says_yes());
}
void write_method()
/*
Post: A short string identifying the abstract
data type used for the structure is written.
*/
{
cout << " Word frequency program: AVL-Tree implementation";
cout << endl;
}
d b g f a d b d
Each part builds on the previous; that is, use the final tree of each solution as the starting tree for the next
part. [Check: The tree should be completely balanced after the last part, as well as after one previous
part.]
Answer
Initial: 1. Splay
at d :
a a a
zag-
zag
b b b
g g d
zig-
zig
f f c f
c d e g
zag-
zig
e c e
d 2. Splay b
at b : zig
b f a d
a c e g c f
e g
518 Chapter 10 • Binary Trees
f a f a d
d d c e
c e c e
5. Splay a 6. Splay a d
at a: at d: zag- zag
zig- zig
zig
b d a f
f b f b e g
d g c e g c
c e
d 7. Splay b
at b : zig-
zag
a f a d
b e g c f
c e g
8. Splay d
at d : zag
b f
a c e g
E2. The depth of a node in a binary tree is the number of branches from the root to the node. (Thus the root
has depth 0, its children depth 1, and so on.) Define the credit balance of a tree during preorder traversal
to be the depth of the node being visited. Define the (actual) cost of visiting a vertex to be the number of
branches traversed (either going down or up) from the previously visited node. For each of the following
binary trees, make a table showing the nodes visited, the actual cost, the credit balance, and the amortized
cost for a preorder traversal.
Section 10.5 • Splay Trees: A Self-Adjusting Data Structure 519
1 1 1 1
2 2 2 2 3
3 3 3 4 4 5 6
4 4 5 6 7 8 9
5 5 7 8 (d)
Answer In the following tables, the actual cost of visiting vertex i is denoted ti , the credit balance (rank)
by ci , and the amortized cost by ai . The vertices are arranged in order of preorder traversal.
(a) i ti ci ai (b) i ti ci ai (c) i ti ci ai (d) i ti ci ai
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0
2 1 1 2 2 1 1 2 2 1 1 2 2 1 1 2
3 1 2 2 3 1 2 2 3 1 2 2 4 1 2 2
4 1 3 2 4 1 3 2 5 1 3 2 7 1 3 2
5 1 4 2 5 1 4 2 7 1 4 2 3 4 1 2
8 2 4 2 5 1 2 2
6 3 3 2 6 2 2 2
4 3 2 2 8 1 3 2
9 2 3 2
E3. Define a rank function r (x) for the nodes of any binary tree as follows: If x is the root, then r (x)= 0 .
If x is the left child of a node y , then r (x)= r (y)−1 . If x is the right child of a node y , then
r (x)= r (y)+1 . Define the credit balance of a tree during a traversal to be the rank of the node being
visited. Define the (actual) cost of visiting a vertex to be the number of branches traversed (either going
down or up) from the previously visited node. For each of the binary trees shown in Exercise E2, make a
table showing the nodes visited, the actual cost, the credit balance, and the amortized cost for an inorder
traversal.
Answer In the following tables, the actual cost of visiting vertex i is denoted ti , the credit balance (rank)
by ci , and the amortized cost by ai . The vertices are arranged in order of inorder traversal.
(a) i ti ci ai (b) i ti ci ai (c) i ti ci ai (d) i ti ci ai
2 1 −1 0 1 0 0 0 1 0 0 0 4 2 −2 0
4 2 −1 2 3 2 0 2 7 4 −2 2 7 1 −1 2
5 1 0 2 5 2 0 2 5 1 −1 2 2 2 −1 2
3 2 0 2 4 1 1 2 8 1 0 2 1 1 0 2
1 2 0 2 2 2 1 2 3 2 0 2 5 2 0 2
6 1 1 2 3 1 1 2
2 2 1 2 8 2 1 2
4 1 2 2 6 1 2 2
9 1 3 2
E4. In analogy with Exercise E3, devise a rank function for binary trees that, under the same conditions as
in Exercise E3, will make the amortized costs of a postorder traversal almost all the same. Illustrate your
rank function by making a table for each of the binary trees shown in Exercise E2, showing the nodes
visited, the actual cost, the credit balance, and the amortized cost for a postorder traversal.
Answer Define the credit balance of a tree during preorder traversal to be the negative of the depth of the
node being visited. In the following tables, corresponding to the binary trees shown in Exercise
E2, the actual cost of visiting vertex i is denoted ti , the credit balance (rank) by ci , and the
amortized cost by ai . The vertices are arranged in order of postorder traversal.
520 Chapter 10 • Binary Trees
5 4 −4 0 5 4 −4 0 7 4 −4 0 7 3 −3 0
4 1 −3 2 4 1 −3 2 8 2 −4 2 4 1 −2 2
3 1 −2 2 3 1 −2 2 5 1 −3 2 2 1 −1 2
2 1 −1 2 2 1 −1 2 6 2 −3 2 5 3 −2 2
1 1 0 2 1 1 0 2 3 1 −2 2 8 3 −3 2
4 2 −2 2 9 2 −3 2
2 1 −1 2 6 1 −2 2
1 1 0 2 3 1 −1 2
1 1 0 2
E5. Generalize the amortized analysis given in the text for incrementing four-digit binary integers to n -digit
binary integers.
Answer Let k be the number of 1’s at the far right of the integer before step i . (Then, for example, k = 0
if the rightmost digit is a 0.) Suppose, first, that we are not at the very last step, so the integer is
not all 1’s, which means k < n . Then the actual work done to increment the integer consists of
changing these k 1’s into 0 and changing the 0 immediately on their left into a 1. Hence ti = k+ 1.
The total number of 1’s in the integer decreases by k − 1, since k 1’s are changed to 0 and one 0 is
changed to 1; this means that the credit balance changes by ci − ci−1 = −(k − 1) . The amortized
cost of step i is therefore
At the very last step (when i = 2n ), the integer changes from all 1’s to all 0’s. Hence the actual
cost is t2n = n , the credit before the increment is c2n −1 = n , and the credit after the increment is
c2n = 0. Hence the amortized cost of the last step is
a2n = n + 0 − n = 0.
E6. Prove Lemma 10.8. The method is similar to the proof of Lemma 10.7.
Ti – 1 y Ti
x
w
Zig-zag: w y
D
x
A
A B C D
B C
The actual complexity ti of a zig-zag or a zag-zig step is 2 units, and only the sizes of the subtrees
rooted at w , x , and y change in this step. Therefore, all terms in the summation defining ci
cancel against those for ci−1 except those indicated in the following equation:
ai = ti + ci − ci−1
= 2 + ri (w)+ri (x)+ri (y)−ri−1 (w)−ri−1 (x)−ri−1 (y)
= 2 + ri (w)+ri (y)−ri−1 (w)−ri−1 (x)
We obtain the last line by taking the logarithm of |Ti (x)| = |Ti−1 (y)| , which is the observation
that the subtree rooted at y before the splaying step has the same size as that rooted at x after
Section 10.5 • Splay Trees: A Self-Adjusting Data Structure 521
the step. Lemma 10.6 can now be applied to cancel the 2 in this equation (the actual complexity).
Let α = |Ti (w)| , β = |Ti (y)| , and γ = |Ti (x)| . From the diagram for this case, we see that
Ti (w) contains components w , A , and B ; Ti (y) contains components y , C , and D ; and Ti (x)
contains all these components (and x besides). Hence α + β < γ , so Lemma 10.6 implies
ri (w)+ri (y)≤ 2ri (x)−2, or 2ri (x)−ri (w)−ri (y)−2 ≥ 0. Adding this nonnegative quantity to
the right side of the last equation for ai , we obtain
Before step i , w is the parent of x , so |Ti−1 (w)| > |Ti−1 (x)| . Taking logarithms, we have
ri−1 (w)> ri−1 (x) . Hence we finally obtain
E7. Prove Lemma 10.9. This proof does not require Lemma 10.6 or any intricate calculations.
Ti – 1 y Ti x
x y
Zig:
C A
A B B C
The actual complexity ti of a zig or a zag step is 1 unit, and only the sizes of the subtrees rooted
at x and y change in this step. Therefore, all terms in the summation defining ci cancel against
those for ci−1 except those indicated in the following equation:
ai = ti + ci − ci−1
= 1 + ri (x)+ri (y)−ri−1 (x)−ri−1 (y)
= 1 + ri (y)−ri−1 (x)
We obtain the last line by taking the logarithm of |Ti (x)| = |Ti−1 (y)| , which is the observation
that the subtree rooted at y before the splaying step has the same size as that rooted at x after
the step. After step i , x is the parent of y , so |Ti (x)| > |Ti (y)| . Taking logarithms, we have
ri (x)> ri (y) . Hence we finally obtain
#include "../../c/utility.h"
void help()
/* PRE: None.
POST: Instructions for the tree operations have been printed.
*/
522 Chapter 10 • Binary Trees
{
cout << "\n";
cout << "\t[S]ize [I]nsert [D]elete \n"
"\t[H]eight [E]rase [F]ind spla[Y] \n"
"\t[W]idth [C]ontents [L]eafs [B]readth first \n"
"\t[P]reorder P[O]storder I[N]order [?]help \n" << endl;
}
#include <string.h>
char get_command()
/* PRE: None.
POST: A character command belonging to the set of legal commands for
the tree demonstration has been returned.
*/
{
char c, d;
cout << "Select command (? for Help) and press <Enter>:";
while (1) {
do {
cin.get(c);
} while (c == ’\n’);
do {
cin.get(d);
} while (d != ’\n’);
c = tolower(c);
if(strchr("clwbe?sidfponqhy",c) != NULL)
return c;
cout << "Please enter a valid command or ? for help:" << endl;
help();
}
}
char get_char()
{
char c;
cin >>c;
return c;
}
#include "../../6/linklist/list.h"
#include "../../6/linklist/list.cpp"
#include "../../8/linklist/sortable.h"
#include "../../8/linklist/merge.cpp"
#include "../2p1p2/queue.h"
#include "../2p1p2/queue.cpp"
#include "../2p1p2/extqueue.h"
#include "../2p1p2/extqueue.cpp"
Section 10.5 • Splay Trees: A Self-Adjusting Data Structure 523
// include binary search tree data structure
#include "../2p1p2/node.h"
#include "../2p1p2/tree.h"
#include "../2p1p2/node.cpp"
#include "../2p1p2/tree.cpp"
#include "../2p1p2/stree.h"
#include "../2p1p2/snode.cpp"
#include "../2p1p2/stree.cpp"
#include "../sst/splaytre.h"
#include "../sst/splaynod.cpp"
#include "../sst/splaytre.cpp"
int do_command(char c, Splay_tree<char> &test_tree)
/* PRE: The tree has been created and command is a valid tree
operation.
POST: The command has been executed.
USES: All the functions that perform tree operations.
*/
{
char x;
switch (c) {
case ’?’: help();
break;
case ’y’:
cout << "Enter character to splay for:";
x = get_char();
if (test_tree.splay(x) == entry_inserted)
cout << "Entry has been inserted" << endl;
else cout << "Entry has been located" << endl;
break;
case ’s’:
cout << "The size of the tree is " << test_tree.size() << "\n";
break;
case ’i’:
cout << "Enter new character to insert:";
x = get_char();
if (test_tree.splay(x) == entry_inserted)
cout << "Entry has been inserted" << endl;
else cout << "A duplicate entry has been located" << endl;
break;
case ’d’:
cout << "Enter character to remove:" << flush;
x = get_char();
if (test_tree.remove(x) != success) cout << "Not found!\n";
break;
case ’f’:
cout << "Enter character to look for:";
x = get_char();
if (test_tree.tree_search(x) != success)
cout << " The entry is not present";
else
cout << " The entry is present";
cout << endl;
break;
524 Chapter 10 • Binary Trees
case ’h’:
cout << "The height of the tree is " << test_tree.height() << "\n";
break;
case ’e’:
test_tree.clear();
cout << "Tree is cleared.\n";
break;
case ’w’:
cout << "The width of the tree is "
<< test_tree.width() << "\n";
break;
case ’c’:
test_tree.bracketed_print();
break;
case ’l’:
cout << "The leaf count of the tree is "
<< test_tree.leaf_count() << "\n";
break;
case ’b’:
test_tree.level_traverse(write_ent);
cout << endl;
break;
case ’p’:
test_tree.preorder(write_ent);
cout << endl;
break;
case ’o’:
test_tree.postorder(write_ent);
cout << endl;
break;
case ’n’:
test_tree.inorder(write_ent);
cout << endl;
break;
case ’q’:
cout << "Tree demonstration finished.\n";
return 0;
}
return 1;
}
int main()
/* PRE: None.
POST: A binary tree demonstration has been performed.
USES: get_command, do_command, Tree methods
*/
{
Binary_tree<char> s; Binary_tree<char> t = s;
Search_tree<char> s1; Search_tree<char> t1 = s1;
Splay_tree<char> test_tree;
while (do_command(get_command(), test_tree));
}
Section 10.5 • Splay Trees: A Self-Adjusting Data Structure 525
Splay tree class:
Implementation:
/*
Pre: The pointer first_large points to an actual Binary_node
(in particular, it is not NULL). The three-way invariant holds.
Post: The node referenced by current (with its right subtree) is linked
to the left of the node referenced by first_large.
The pointer first_large is reset to current.
The three-way invariant continues to hold.
*/
{
first_large->left = current;
first_large = current;
current = current->left;
}
/*
Pre: The pointer last_small points to an actual Binary_node
(in particular, it is not NULL). The three-way invariant holds.
Post: The node referenced by current (with its left subtree) is linked
to the right of the node referenced by last_small.
The pointer last_small is reset to current.
The three-way invariant continues to hold.
*/
{
last_small->right = current;
last_small = current;
current = current->right;
}
{
Binary_node<Record> *right_tree = current->right;
current->right = right_tree->left;
right_tree->left = current;
current = right_tree;
}
{
Binary_node<Record> *left_tree = current->left;
current->left = left_tree->right;
left_tree->right = current;
current = left_tree;
}
P2. Substitute the function for splay retrieval and insertion into the information-retrieval project of Project
information retrieval P5 of Section 10.2 (page 461). Compare the performance of splay trees with ordinary binary search trees
for various combinations of input text files.
#include <stdlib.h>
#include <string.h>
#include "../../c/utility.h"
#include "../../c/utility.cpp"
#include "../../6/doubly/list.h"
#include "../../6/doubly/list.cpp"
#include "../../6/strings/string.h"
#include "../../6/strings/string.cpp"
#include "../bt/node.h"
#include "../bt/tree.h"
#include "../bt/node.cpp"
#include "../bt/tree.cpp"
#include "../bst/stree.h"
#include "../bst/snode.cpp"
#include "../bst/stree.cpp"
#include "../2p5/key.h"
#include "../2p5/key.cpp"
#include "../2p5/record.h"
#include "../2p5/record.cpp"
#include "../sst/splaytre.h"
#include "../sst/splaynod.cpp"
#include "../sst/splaytre.cpp"
#include "auxil.cpp"
#include "../../c/timer.h"
#include "../../c/timer.cpp"
int main(int argc, char *argv[]) // count, values of command-line arguments
/*
Pre: Name of an input file can be given as a command-line argument.
Post: The word storing project p6 has been performed.
*/
{
write_method();
Binary_tree<Record> t;
Search_tree<Record> t1;
Splay_tree<Record> the_words;
char infile[1000];
char in_string[1000];
char *in_word;
Timer clock;
do {
int initial_comparisons = Key::comparisons;
clock.reset();
if (argc < 2) {
cout << "What is the input data file: " << flush;
cin >> infile;
}
else strcpy(infile, argv[1]);
Section 10.5 • Splay Trees: A Self-Adjusting Data Structure 529
ifstream file_in(infile); // Declare and open the input stream.
if (file_in == 0) {
cout << "Can’t open input file " << infile << endl;
exit (1);
}
while (!file_in.eof()) {
file_in >> in_string;
int position = 0;
bool end_string = false;
while (!end_string) {
in_word = extract_word(in_string, position, end_string);
if (strlen(in_word) > 0) {
int counter;
String s(in_word);
update(s, the_words, counter);
}
}
}
cout << "Elapsed time: " << clock.elapsed_time() << endl;
cout << "Comparisons performed = "
<< Key::comparisons - initial_comparisons << endl;
cout << "Do you want to print frequencies? ";
if (user_says_yes()) print(the_words);
cout << "Do you want to add another file of input? ";
} while (user_says_yes());
}
Auxiliary functions:
void write_method()
/*
Post: A short string identifying the abstract
data type used for the structure is written.
*/
{
cout << " Word frequency program: Splay-Tree implementation";
cout << endl;
}
{
int initial_comparisons = Key::comparisons;
Record r((Key) word);
if (structure.splay(r) != entry_inserted) {
structure.tree_search(r);
structure.remove(r);
r.increment();
structure.insert(r);
}
num_comps = Key::comparisons - initial_comparisons;
}
void wr_entry(Record &a_word)
{
String s = ((Key) a_word).the_key();
cout << s.c_str();
for (int i = strlen(s.c_str()); i < 12; i++) cout << " ";
cout << " : " << a_word.frequency() << endl;
}
void print(Splay_tree<Record> &structure)
/*
Post: All words in structure are printed at the terminal
in alphabetical order together with their frequency counts.
*/
{
structure.inorder(wr_entry);
}
char *extract_word(char *in_string, int &examine, bool &over)
{
int ok = 0;
while (in_string[examine] != ’\0’) {
if (’a’ <= in_string[examine] && in_string[examine] <= ’z’)
in_string[ok++] = in_string[examine++];
else if (’A’ <= in_string[examine] && in_string[examine] <= ’Z’)
in_string[ok++] = in_string[examine++] - ’A’ + ’a’;
else if ((’\’’ == in_string[examine] || in_string[examine] == ’-’)
&& (
(’a’ <= in_string[examine + 1] && in_string[examine + 1] <= ’z’) ||
(’A’ <= in_string[examine + 1] && in_string[examine + 1] <= ’Z’)))
in_string[ok++] = in_string[examine++];
else break;
}
in_string[ok] = ’\0’;
if (in_string[examine] == ’\0’) over = true;
else examine++;
return in_string;
}
REVIEW QUESTIONS
A binary tree is either empty, or it consists of a node called the root together with two binary
trees called the left subtree and the right subtree of the root.
Chapter 10 • Review Questions 531
2. What is the difference between a binary tree and an ordinary tree in which each vertex has at most two
branches?
In a binary tree left and right are distinguished, but they are not in an ordinary tree. When a
node has just one child, there are two cases for a binary tree but only one for an ordinary tree.
3. Give the order of visiting the vertices of each of the following binary trees under (a) preorder, (b) inorder,
and (c) postorder traversal.
1 1 1
2 3 2 2 3
4 3 4 5 6
(1) 1, 2, 4, 3 1, 2, 3, 4 1, 2, 4, 5, 3, 6
(2) 4, 2, 1, 3 2, 3, 4, 1 4, 2, 5, 1, 3, 6
(3) 4, 2, 3, 1 4, 3, 2, 1 4, 5, 2, 6, 3, 1
4. Draw the expression trees for each of the following expressions, and show the result of traversing the tree
in (a) preorder, (b) inorder, and (c) postorder.
– l
a b n !
log +
! log log
m x y
532 Chapter 10 • Binary Trees
≤ ||
× + > ≥
x y x y a b b a
A binary search tree is a binary tree that is either empty or in which each node contains a key
that satisfies the condition: (1) all keys (if any) in the left subtree of the root precede the key in
the root; (2) the key if the root precedes all keys (if any) in its right subtree; and (3) the left and
right subtrees are binary search trees.
6. If a binary search tree with n nodes is well balanced, what is the approximate number of comparisons of
keys needed to find a target? What is the number if the tree degenerates to a chain?
The approximate number of comparisons of keys needed to find a target in a well balanced
binary search tree is 2 lg n (with two comparisons per node) as opposed to approximately n
comparisons (halfway, on average, through a chain of length n ) if the tree degenerates to a
chain.
The items are built into a binary search tree which is then traversed in inorder yielding a sorted
list.
In quicksort the keys are partitioned into left and right sublists according to their size relative to
the pivot. In treesort the keys are partitioned into the left and right subtrees according to their
sizes relative to the root key. Both methods then proceed by recursion. Hence treesort proceeds
by making exactly the same comparisons as quicksort does when the first key in every list is
chosen as the pivot.
9. What causes removal from a search tree to be more difficult than insertion into a search tree?
Since it is possible to make all insertions at the leaves of the tree, insertions are very easy to
perform without destroying the binary search tree property. Deletions, however, must occur at
the designated node, a node which may have children thus complicating the function of easily
removing it without destroying the binary search tree property.
10. When is the algorithm for building a binary search tree developed in Section 10.3 useful, and why is it
preferable to simply using the function for inserting an item into a search tree for each item in the input?
The algorithm is designed for applications in which the input is already sorted by key. The
algorithm will then build the input into a nearly balanced search tree, while the simple insertion
algorithm will construct a tree that degenerates into a single long chain, for which retrieval time
will not be logarithmic.
Chapter 10 • Review Questions 533
11. How much slower, on average, is searching a random binary search tree than is searching a completely
balanced binary search tree?
The average binary search tree requires approximately 2 ln 2 ≈ 1.39 times as many comparisons
as a completely balanced tree.
The purpose of AVL trees is to have a binary search tree that remains balanced as insertions and
removals take place.
13. What condition defines an AVL tree among all binary search trees?
An AVL tree is a binary search tree in which the heights of the left and right subtrees of the root
differ by at most 1 and in which the left and right subtrees are again AVL trees.
14. Suppose that A is a base class and B is a derived class, and that we declare: A *pA; B *pB; Can pA
reference an object of class B? Can pB reference an object of class A?
A pointer to an object from a base class can also point to an object of a derived class. Accordingly,
the pointer pA can point to an object of B. The pointer pB cannot point to an object of A.
15. Explain how the virtual methods of a class differ from other class methods.
The choice of implementation called for a virtual method is selected dynamically, at run time.
This choice is made to correspond to the dynamically changing type of the object from which the
method is called.
16. Draw a picture explaining how balance is restored when an insertion into an AVL tree puts a node out
of balance.
Refer to Figures Figure 10.17 (page 474), Figure 10.18 (page 477), and Figure 10.19 on page 480
of the text.
17. How does the worst-case performance of an AVL tree compare with the worst-case performance of a
random binary search tree? How does it compare with its average-case performance? How does the
average-case performance of an AVL tree compare with that of a random binary search tree?
The worst-case performance of an AVL tree is approximately 1.44 lg n as opposed to the worst-
case performance of a random binary tree which is n . The average-case performance of an AVL
tree is approximately lg n + 0.25 and the average-case performance of a random binary tree is
approximately 1.39 lg n .
By doing certain rotations, splaying lifts each node when inserted or retrieved to become the
root.
Splaying helps to balance the binary search tree and to keep more frequently accessed nodes near
the root where they will be found more quickly.
We consider a sequence of operations on the data structure and determine the worst-case cost
of the entire sequence. This may be much less than the worst-case cost of a single operation
multiplied by the number of operations in the sequence.
534 Chapter 10 • Binary Trees
This function is a device added to the actual cost to help simplify the calculation of the amortized
cost of operations. Its goal is to even out the differences between the costs of the operations. By
telescoping series, it disappears from the final calculations.
22. In the big- O notation, what is the cost of splaying amortized over a sequence of retrievals and insertions?
Why is this surprising?
If the tree never has more than n nodes, then insertion or retrieval with splaying can always be
done in time O(log n) , amortized over a long sequence of operations. This result is surprising
because the tree may begin as, or degenerate to, a chain or other tree that is highly unbalanced,
so an individual insertion or removal may require n operations.