Day 8: Advanced Data Structures

On day 7, you learned how to create a binary tree and how to print its class members. Limitations exist in binary trees; you can use other structures to help solve some of these restrictions. Today you will learn:

Printing a Tree

On day 7, you learned two ways to print a tree. The first way was rather awkward. The second way treated printing as a special case of walking the tree, which can be done in any number of sequences. You can imagine walking the tree in key-sequence order-alphabetical, in most cases. You also can imagine depth-first (go down the tree before you go across any levels) or width-first (go across a level before you go down.)

Today you will see two printing methods in more detail. The first method uses a first-in-first-out (FIFO) queue, and the second method revisits day 7's approach.

Using FIFO Queues

A queue is a structure in which objects enter one side, and leave the same or the other side. A last-in-first-out (LIFO) queue works so that the last thing added to the queue is the first thing to come out. A stack is a LIFO queue.

A first-in-first-out (FIFO) queue is like a line at a theater, as in figure 8.1 (and the British, in fact, talk about queuing up at a theater). The first person on line should be the first person through the door.

The easiest way to implement a FIFO queue is with an unsorted linked list. Listing 8.1 illustrates the interface to a simple FIFO queue. Listing 8.2 shows the implementation of the queue's methods, and listing 8.3 shows a driver program for the queue.

Figure 8.1 A FIFO queue.

Listing 8.1 FIFO Queue Header

    1:     // **************************************************
    2:     // PROGRAM:  FIFO queue header
    3:     // FILE:     queue.hpp
    4:     // PURPOSE:  provide first in first out queue
    5:     // NOTES:
    6:     // AUTHOR:   Jesse Liberty (jl)
    7:     // REVISIONS: 10/24/94 1.0 jl  initial release
    8:     // **************************************************
    9:
    10:      template <class T>
    11:      class Node
    12:      {
    13:      public:
    14:         Node (T*);
    15:         ~Node();
    16:         void InsertAfter(Node *);
    17:         Node * GetNext() const            { return itsNext; }
    18:         void SetNext(Node * next)          { itsNext = next; }
    19:         T* GetObject() const            { return itsObject; }
    20:
    21:      private:
    22:         T * itsObject;
    23:         Node * itsNext;
    24:      };
    25:
    26:    template <class T>
    27:    class Queue
    28:    {
    29:      public:
    30:         Queue();
    31:         ~Queue();
    32:         void       Push(T &);
    33:         T*           Pop();
    34:
    35:      private:
    36:         Node<T>  itsHead;
    37:    };

Listing 8.2 FIFOImplementation

    1:     // **************************************************
    2:     // PROGRAM:  FIFO queue implementation
    3:     // FILE:     queue.cpp
    4:     // PURPOSE:  provide first in first out queue
    5:     // NOTES:
    6:     // AUTHOR:   Jesse Liberty (jl)
    7:     // REVISIONS: 10/24/94 1.0 jl  initial release
    8:     // **************************************************
    9:
    10:       #include "queue.hpp"
    11:       template <class T>
    12:        Node<T>::Node(T * pObject):
    13:        itsObject(pObject),
    14:        itsNext(0)
    15:        {}
    16:
    17:         template <class T>
    18:         Node<T>::~Node()
    19:        {
    20:           itsObject = 0;
    21:           itsNext = 0;
    22:        }
    23:
    24:         template <class T>
    25:         void Node<T>::InsertAfter(Node* newNode)
    26:        {
    27:         newNode->SetNext(itsNext);
    28:         itsNext=newNode;
    29:        }
    30:
    31:        template<class T>
    32:        Queue <T>::Queue():
    33:           itsHead(0)  // initialize head node to have no Object
    34:           {}
    35:
    36:        template<class T>
    37:        Queue <T>::~Queue()
    38:        {
    39:        }
    40:
    41:        template<class T>
    42:        void Queue<T>::Push(T & Object)
    43:        {
    44:            Node<T> * NewNode = new Node<T>(&Object);
    45:
    46:            for (Node<T> * pNode = &itsHead;;pNode = pNode->GetNext())
    47:            {
    48:               if (pNode->GetNext() == NULL)
    49:               {
    50:                  pNode->InsertAfter(NewNode);
    51:                  break;
    52:               }
    53:            }
    54:        }
    55:
    56:       template<class T>
    57:       T* Queue<T>::Pop()
    58:       {
    59:          Node<T> * first = itsHead.GetNext();
    60:          Node<T> * second = first->GetNext();
    61:          if (first)
    62:          {
    63:             T* object = first->GetObject();
    64:             if (second)
    65:                itsHead.SetNext(second);
    66:             else
    67:                itsHead.SetNext(0);
    68:             delete first;
    69:             return object;
    70:          }
    71:          else
    72:             return 0;
    73:       }

Listing 8.3 Queue Driver Program

    1:       #include "word.hpp"
    2:       #include "queue.cpp"
    3:
    4:     int main(int argc, char **argv)
    5:     {
    6:        Queue<rWord> myQueue;
    7:
    8:        for (int i = 1; i< argc; i++)
    9:        {
    10:          rWord* word = new rWord(argv[i]);
    11:          myQueue.Push(*word);
    12:       }
    13:
    14:       for (i = 1; i< argc; i++)
    15:          cout << myQueue.Pop()->GetText()<< "\n";
    16:
    17:       return 0;
    18:    }
Output:
    d:\>0803 eternal vigilance is the price of liberty
    eternal
    vigilance
    is
    the
    price
    of
    liberty
Analysis:

Listing 8.1 provides a template-based FIFO queue. The Node class is very close to the Node class for the sorted linked list, and you could well make this new one a base class of that more complex node.

The Queue class itself is far simpler than the linked list: it offers Push(), which adds the entry to the end of the queue, and Pop(), which removes and returns the first item on the queue.

Using the Queue Class to Print

On day 7, you saw how to print the binary tree, using static members to keep track of indentation. The FIFO queue enables you to walk the tree horizontally. With this capability, you can print the tree, spacing out the words appropriately, or you can print it sideways by using gotoxy() to position each word in turn. Because the sideways approach provides space for a deeper tree, it is the one I'll use here. I'll leave as an exercise at the end of the chapter the job of printing this tree vertically, using the FIFO queue.

Listing 8.4 provides the interface to a Binary Tree class, where each node holds an rWord. A third class, BinaryNodeWrapper, also is provided, which will be the object placed into the FIFO queue. The BinaryNodeWrapper has a wordNode pointer, along with information about the indentation and level of the entry in the tree.

Listing 8.5 provides the implementation of these three classes, and listing 8.6 provides a driver program that walks the tree and prints the chart.

Listing 8.4 The Interfaces for a Binary Tree of Words

    1:        #include "word.hpp"
    2:        #include <conio.h>
    3:
    4:        class WordNode;     // forward declaration
    5:
    6:        class BinaryTree
    7:        {
    8:        public:
    9:          BinaryTree():myHead(0),myCount(0){}
    10:         ~BinaryTree(){}
    11:         long GetCount() const { return myCount; }
    12:         void Insert (rWord &);
    13:         void Delete (rWord &);
    14:         void Iterate(void (rWord::*f)() const);
    15:         WordNode* FindObject( rWord& target);
    16:          void PrintTree();
    17:
    18:       private:
    19:          WordNode* myHead;
    20:          long myCount;
    21:       };
    22:
    23:       class WordNode
    24:       {
    25:       public:
    26:          WordNode(rWord* word);
    27:          WordNode(rWord& word);
    28:          WordNode( WordNode&);
    29:          ~WordNode() {}
    30:          rWord* GetWord()  { return myWord; }
    31:          const rWord* const GetWord() const { return myWord; }
    32:          void SetWord(rWord* target) { myWord = target;}
    33:
    34:          void InsertSmaller(WordNode* NewNode);
    35:          void InsertBigger(WordNode* NewNode);
    36:
    37:          const WordNode* const  GetSmaller() const  { return mySmaller;  }
    38:          const WordNode* const GetBigger() const  { return myBigger; }
    39:          const WordNode* const GetParent() const { return myParent; }
    40:
    41:           WordNode*   GetSmaller()   { return mySmaller;  }
    42:           WordNode*  GetBigger()   { return myBigger; }
    43:           WordNode*  GetParent()  { return myParent; }
    44:
    45:
    46:          void SetSmaller(WordNode* target)  {mySmaller = target; }
    47:          void SetBigger(WordNode* target)  {myBigger = target; }
    48:          void SetParent(WordNode* target)  {myParent = target; }
    49:
    50:          int operator<(const WordNode &rhs) const
    51:             {return *myWord < *(rhs.GetWord());}
    52:          int operator>(const WordNode &rhs) const
    53:             {return *myWord > *(rhs.GetWord());}
    54:          BOOL operator==(const WordNode &rhs) const
    55:             { return *myWord ==*(rhs.GetWord());}
    56:          BOOL operator==(const rWord& target) const
    57:             {return *myWord == target.GetText();}
    58:
    59:       private:
    60:
    61:          WordNode * mySmaller;
    62:          WordNode * myBigger;
    63:          WordNode * myParent;
    64:          rWord * myWord;
    65:       };
    66:
    67:       class WNWrapper
    68:       {
    69:       public:
    70:          WNWrapper(WordNode* wn):myWordNode(wn){}
    71:          ~WNWrapper(){}
    72:         //   WordNode& GetMyWordNode() { return myWordNode; }
    73:          int GetLevel() { return myLevel; }
    74:          void SetLevel (int level) {myLevel = level; }
    75:          int GetIndent() { return myIndent; }
    76:          void SetIndent (int Indent) {myIndent = Indent; }
    77:          WordNode* GetSmaller() { return myWordNode->GetSmaller(); }
    78:          WordNode* GetBigger() { return myWordNode->GetBigger(); }
    79:          WordNode* GetWordNode() { return myWordNode; }
    80:
    81:       private:
    82:          WordNode* myWordNode;
    83:          int myLevel;
    84:          int myIndent;
    85:       };

Listing 8.5 The Implementation of the Binary Tree of Words

    1:     #include "btree.hpp"
    2:     #include "queue.cpp"
    3:
    4:
    5:     WordNode::WordNode(rWord* word):
    6:        mySmaller(0),
    7:        myBigger(0),
    8:        myParent(0),
    9:        myWord(word)
    10:    {  }
    11:
    12:    WordNode::WordNode(rWord &word):
    13:       mySmaller(0),
    14:       myBigger(0),
    15:       myParent(0),
    16:       myWord(&word)
    17:    {  }
    18:
    19:    WordNode::WordNode(  WordNode& rhs)
    20:    {
    21:       mySmaller=rhs.GetSmaller();
    22:       myBigger=rhs.GetBigger();
    23:       myParent=rhs.GetParent();
    24:       myWord = rhs.GetWord();
    25:    }
    26:
    27:
    28:    void WordNode::InsertSmaller(WordNode* newNode)
    29:    {
    30:         newNode->SetSmaller(mySmaller);
    31:         newNode->SetParent(this);
    32:         mySmaller=newNode;
    33:    }
    34:
    35:
    36:    void WordNode::InsertBigger(WordNode* newNode)
    37:    {
    38:         newNode->SetBigger(myBigger);
    39:         newNode->SetParent(this);
    40:         myBigger=newNode;
    41:    }
    42:
    43:
    44:    void BinaryTree::Insert(rWord& rhs)
    45:    {
    46:       WordNode* newNode = new WordNode(&rhs);
    47:
    48:       if (myHead == 0)
    49:       {
    50:          myHead = newNode;
    51:          return;
    52:       }
    53:
    54:          WordNode* node = myHead;
    55:          while (node)
    56:          {
    57:             // duplicate? replace
    58:             if (newNode == node)
    59:             {
    60:                newNode->SetSmaller(node->GetSmaller());
    61:                newNode->SetBigger(node->GetBigger());
    62:                newNode->SetParent(node->GetParent());
    63:                if (node == myHead)
    64:                   myHead = newNode;
    65:                else
    66:                {
    67:                   if ( node->GetParent()->GetSmaller() == node)
    68:                      node->GetParent()->SetSmaller(newNode);
    69:                   else
    70:                      node->GetParent()->SetBigger(newNode);
    71:                }
    72:                delete node;
    73:                break;
    74:             }
    75:             if (*newNode < *node)
    76:             {
    77:                if (!node->GetSmaller())
    78:                {
    79:                   node->SetSmaller(newNode);
    80:                   newNode->SetParent(node);
    81:                   break;
    82:                }
    83:                else
    84:                   node = node->GetSmaller();
    85:             }
    86:             else
    87:             {
    88:                if (!node->GetBigger())
    89:                {
    90:                   node->SetBigger(newNode);
    91:                   newNode->SetParent(node);
    92:                   break;
    93:                }
    94:                else
    95:                   node = node->GetBigger();
    96:             }
    97:          }   // end while
    98:    }          // end function
    99:
    100:
    101:   WordNode* BinaryTree::FindObject(rWord& rhs)
    102:   {
    103:      WordNode* node = myHead;
    104:
    105:
    106:      while (node)
    107:      {
    108:         if (*node == rhs)
    109:            break;
    110:
    111:         if (*node < rhs)
    112:            node = node->GetBigger();
    113:         else
    114:            node = node->GetSmaller();
    115:      }
    116:      return node;
    117:   }
    118:
    119:   void BinaryTree::Delete (rWord & target)
    120:   {
    121:      WordNode* node = FindObject(target);
    122:
    123:      if (node)
    124:      {
    125:         if (!node->GetBigger())
    126:         {
    127:            if (!node->GetSmaller())
    128:            {
    129:               if (node == myHead)
    130:                  myHead = 0;
    131:               else
    132:               {
    133:                  if (node->GetParent()->GetSmaller() == node)
    134:                     node->GetParent()->SetSmaller(0);
    135:                  else
    136:                     node->GetParent()->SetBigger(0);
    137:               }
    138:            }
    139:            else // has a smaller
    140:            {
    141:               if (node == myHead)
    142:                  myHead = node->GetSmaller();
    143:               else
    144:               {
    145:                  if (node->GetParent()->GetSmaller() == node)
    146:                     node->GetParent()->SetSmaller(node->GetSmaller());
    147:                  else
    148:                     node->GetParent()->SetBigger(node->GetSmaller());
    149:               }
    150:            }
    151:         }
    152:         else // node does have a bigger
    153:         {
    154:            if (!node->GetSmaller())
    155:            {
    156:               if (node == myHead)
    157:                  myHead = node->GetBigger();
    158:               else
    159:               {
    160:                if (node->GetParent()->GetSmaller() == node)
    161:                  node->GetParent()->SetSmaller(node->GetBigger());
    162:               else
    163:                  node->GetParent()->SetBigger(node->GetBigger());
    164:               }
    165:            }
    166:            else // node has both!!
    167:            {
    168:               WordNode * next = node->GetBigger();
    169:
    170:               while (next->GetSmaller())
    171:                  next=next->GetSmaller();
    172:
    173:               if (next->GetParent()->GetSmaller() == next)
    174:                  next->GetParent()->SetSmaller(next->GetBigger());
    175:               else
    176:                  next->GetParent()->SetBigger(next->GetBigger());
    177:
    178:               node->SetWord(next->GetWord());
    179:
    180:               if (next->GetBigger())
    181:                  next->GetBigger()->SetParent(node);
    182:
    183:               node = next;
    184:            }
    185:         } // end else does have bigger
    186:      } // end if node
    187:      delete node;
    188:   } // end function
    189:
    190:   // walk the tree horizontally and create wrappers in FIFO
    191:   void BinaryTree::PrintTree()
    192:   {
    193:      Queue<WNWrapper> FIFO;
    194:      if (!myHead)
    195:      {
    196:         cout << "Nothing in tree.\n" << endl;
    197:         return;
    198:      }
    199:
    200:      WNWrapper* theWrapper = new WNWrapper(myHead);
    201:      int level = 1;
    202:      theWrapper->SetLevel(level);
    203:      int indent = level;
    204:      theWrapper->SetIndent(indent);
    205:      FIFO.Push(*theWrapper);
    206:
    207:      WNWrapper* wnr;
    208:
    209:      // ignore warning here!
    210:      while (wnr = FIFO.Pop())
    211:      {
    212:         WordNode* pWN = wnr->GetSmaller();
    213:         level = wnr->GetLevel();
    214:         indent = wnr->GetIndent();
    215:         if (pWN)
    216:         {
    217:            theWrapper = new WNWrapper(pWN);
    218:            theWrapper->SetLevel(level+1);
    219:            theWrapper->SetIndent(indent+1);
    220:            FIFO.Push(*theWrapper);
    221:         }
    222:         pWN = wnr->GetBigger();
    223:         if (pWN)
    224:         {
    225:            theWrapper = new WNWrapper(pWN);
    226:            theWrapper->SetLevel(level+1);
    227:            theWrapper->SetIndent(indent-1);
    228:            FIFO.Push(*theWrapper);
    229:         }
    230:         int indent = wnr->GetIndent();
    231:         gotoxy(5*level,indent+10);
    232:         cout << wnr->GetWordNode()->GetWord()->GetText();
    233:         delete wnr;
    234:       }
    235:      gotoxy(1,24);
    236:   }

Listing 8.6 A Driver Program to Print a Binary Tree of Words

    1:   // A Driver Program to Print a Binary Tree of Words
    2:     int main(int argc, char **argv)
    3:     {
    4:        BinaryTree tree;
    5:        for (int i = 1; i< argc; i++)
    6:        {
    7:           rWord* word = new rWord(argv[i]);
    8:           tree.Insert(*word);
    9:        }
    10:
    11:       tree.PrintTree();
    12:
    13:       return 0;
    14:    }
Output:
    d:\>0806 now is the time for all good men to

                       to
                  time
             the
        now       men
             is        good
                  for
                       all


    d:\>0806 now is the time for all good men to come to the aid of
                            to
                       to
                  time
             the       the
        now       ofn
             is        good
                  for       come
                       all
                            aid
Analysis:

The first printout demonstrates the use of the program.

In line 5 of listing 8.6, each word of the command line is used to create an rWord pointer, which then is inserted into the tree. The Insert() method works as discussed on day 7, and the word is moved into the right position in the tree.

After all the words are in the tree, the function PrintTree() is called in line 11. The implementation of this method is in lines 191 through 236 of listing 8.5.

A FIFO queue is instantiated in line 193, and the tree is checked to see whether it has any members. If not, a message is printed and the program ends.

Assuming that there is a head of the tree, a WNWrapper pointer is created based on the head word node in the tree. A number of local counting variables are initialized, and the wrapper is set to level 1 and indentation 1. Finally, in line 205, the wrapper is put into the queue.

The first WNWrapper is popped out of the queue and is assigned to the pointer to wrapper. Note that this assignment generates a warning with many compilers. The compiler is concerned that you might have confused the assignment operator (=) with the equal operator (==), but in this case, you do want the assignment operator.

If there is a smaller member, a new wrapper is created and its level and indentation are set based on the current wrapper, and the new (child) wrapper is pushed into the FIFO list.

In line 222, the bigger child is extracted in the same way. Now that the children have been wrapped up and put in the queue, the current word can be printed.

If you wanted to avoid using gotoxy(), you would rewrite this to indent according to the indentation level, and use new lines when the level variable changes.

This program is somewhat complex. You may want to run it a few times and then study the output, trying to understand line by line how the items are put on and then taken off the tree, and how they are pushed into and popped off of the queue.

Problems with This Approach

The second run of the program includes a larger set of text and reveals a bug in this approach. When the child of the is extracted (men), its level is 3 and its indentation is 0. This makes sense: the parent had a level of 2 and an indentation of 1as the left child 1 is reduced from the parent's indentation, it returns to 0.

When the bigger child of is (of) is extracted, it too has a level of 3 and an indentation of 0. Its parent had a level of 2 and an indentation of -1, so the bigger child adds 1 to the indentation. Unfortunately, this position level 3, indentation 0 already is taken, and the word of overwrites the first two letters of men.

Because symmetrical actions are taken on each level of the program, collisions are inevitable. It is possible to overcome this problem using complex algorithms, but the next set of programs uses a simpler solution.

Using Recursion to Print

Listing 8.7 provides a simple solution to printing the tree. The premise is to walk all the way down the left leg of the tree, print the last entry, then its parent, and then its sibling, working back up the tree.

Examine this tree from the previous listing:

    d:\>0807 now is the time for all
            all
          for
        is
      now
        the
          time

One way to print this is to get to the top of the tree (now) and to pass it to the PrintNode() function. In PrintNode(), get the smaller child (is) and call PrintNode() on that; then call DoPrint() (which actually prints) and then call PrintNode() on the larger child.

When the first node, now, calls PrintNode() on the child, is, it in turn calls PrintNode() on its smaller child, for, which calls PrintNode() on its smaller child, all. Because all has no children, it prints itself, and then returns. The word for prints, has no larger child, and so returns, and so on up the recursion tree. Here's the listing. Note that only the declaration of the PrintTree and its implementation need to change.

Listing 8.7 Using Regression To Print the Tree

    1:       // Listing 8.7 - Using Regression To Print the Tree
    2:
    3:       #include "word.hpp"
    4:       #include "queue.cpp"
    5:       #include <conio.h>
    6:
    7:       class WordNode;     // forward declaration
    8:
    9:       class BinaryTree
    10:      {
    11:      public:
    12:        BinaryTree():myHead(0),myCount(0){}
    13:        ~BinaryTree(){}
    14:        long GetCount() const { return myCount; }
    15:        void Insert (rWord &);
    16:        void Delete (rWord &);
    17:        void Iterate(void (rWord::*f)() const);
    18:        WordNode* FindObject( rWord& target);
    19:
    20:                              // the changes are the next three!
    21:         void PrintTree();
    22:         static void PrintNode(WordNode* pNode, int indent);
    23:         static void DoPrint(WordNode* pNode, int indent);
    24:
    25:      private:
    26:         WordNode* myHead;
    27:         long myCount;
    28:      };
    29:
    30:      class WordNode
    31:      {
    32:      public:
    33:         WordNode(rWord* word);
    34:         WordNode(rWord& word);
    35:         WordNode( WordNode&);
    36:         ~WordNode() {}
    37:         rWord* GetWord()  { return myWord; }
    38:         const rWord* const GetWord() const { return myWord; }
    39:
    40:         void SetWord(rWord* target) { myWord = target;}
    41:
    42:         void InsertSmaller(WordNode* NewNode);
    43:         void InsertBigger(WordNode* NewNode);
    44:
    45:         WordNode* GetSmaller() const  { return mySmaller;  }
    46:         WordNode* GetBigger() const  { return myBigger; }
    47:         WordNode* GetParent() const { return myParent; }
    48:
    49:         void SetSmaller(WordNode* target)  {mySmaller = target; }
    50:         void SetBigger(WordNode* target)  {myBigger = target; }
    51:         void SetParent(WordNode* target)  {myParent = target; }
    52:
    53:         int operator<(const WordNode &rhs) const
    54:            {return *myWord < *(rhs.GetWord());}
    55:         int operator>(const WordNode &rhs) const
    56:            {return *myWord > *(rhs.GetWord());}
    57:         BOOL operator==(const WordNode &rhs) const
    58:            { return *myWord ==*(rhs.GetWord());}
    59:         BOOL operator==(const rWord& target) const
    60:            {return *myWord == target.GetText();}
    61:       private:
    62:         WordNode * mySmaller;
    63:         WordNode * myBigger;
    64:         WordNode * myParent;
    65:         rWord * myWord;
    66:      };
    67:
    68:      WordNode::WordNode(rWord* word):
    69:         mySmaller(0),
    70:         myBigger(0),
    71:         myParent(0),
    72:         myWord(word)
    73:      {  }
    74:
    75:      WordNode::WordNode(rWord &word):
    76:         mySmaller(0),
    77:         myBigger(0),
    78:         myParent(0),
    79:         myWord(&word)
    80:      {  }
    81:
    82:      WordNode::WordNode( WordNode& rhs)
    83:      {
    84:         mySmaller=rhs.GetSmaller();
    85:         myBigger=rhs.GetBigger();
    86:         myParent=rhs.GetParent();
    87:         myWord = rhs.GetWord();
    88:      }
    89:
    90:      void WordNode::InsertSmaller(WordNode* newNode)
    91:      {
    92:           newNode->SetSmaller(mySmaller);
    93:           newNode->SetParent(this);
    94:           mySmaller=newNode;
    95:      }
    96:
    97:      void WordNode::InsertBigger(WordNode* newNode)
    98:      {
    99:           newNode->SetBigger(myBigger);
    100:          newNode->SetParent(this);
    101:          myBigger=newNode;
    102:     }
    103:
    104:
    105:     void BinaryTree::Insert(rWord& rhs)
    106:     {
    107:        WordNode* newNode = new WordNode(&rhs);
    108:
    109:        if (myHead == 0)
    110:        {
    111:           myHead = newNode;
    112:           myHead->SetBigger(0);
    113:           myHead->SetSmaller(0);
    114:           return;
    115:        }
    116:
    117:           WordNode* node = myHead;
    118:           while (node)
    119:           {
    120:              // duplicate? replace
    121:              if (newNode == node)
    122:              {
    123:                 newNode->SetSmaller(node->GetSmaller());
    124:                 newNode->SetBigger(node->GetBigger());
    125:                 newNode->SetParent(node->GetParent());
    126:                 if (node == myHead)
    127:                    myHead = newNode;
    128:                 else
    129:                 {
    130:                    if ( node->GetParent()->GetSmaller() == node)
    131:                       node->GetParent()->SetSmaller(newNode);
    132:                    else
    133:                       node->GetParent()->SetBigger(newNode);
    134:                 }
    135:                 delete node;
    136:                 break;
    137:              }
    138:              if (*newNode < *node)
    139:              {
    140:                 if (!node->GetSmaller())
    141:                 {
    142:                    node->SetSmaller(newNode);
    143:                    newNode->SetParent(node);
    144:                    break;
    145:                 }
    146:                 else
    147:                    node = node->GetSmaller();
    148:              }
    149:              else
    150:              {
    151:                 if (!node->GetBigger())
    152:                 {
    153:                    node->SetBigger(newNode);
    154:                    newNode->SetParent(node);
    155:                    break;
    156:                 }
    157:                 else
    158:                    node = node->GetBigger();
    159:              }
    160:           }   // end while
    161:     }          // end function
    162:
    163:
    164:     WordNode* BinaryTree::FindObject( rWord& rhs)
    165:     {
    166:        WordNode* node = myHead;
    167:
    168:
    169:        while (node)
    170:        {
    171:           if (*node == rhs)
    172:              break;
    173:
    174:           if (*node < rhs)
    175:              node = node->GetBigger();
    176:           else
    177:              node = node->GetSmaller();
    178:        }
    179:        return node;
    180:     }
    181:
    182:     void BinaryTree::Delete (rWord & target)
    183:     {
    184:        WordNode* node = FindObject(target);
    185:
    186:        if (node)
    187:        {
    188:           if (!node->GetBigger())
    189:           {
    190:              if (!node->GetSmaller())
    191:              {
    192:                 if (node == myHead)
    193:                    myHead = 0;
    194:                 else
    195:                 {
    196:                    if (node->GetParent()->GetSmaller() == node)
    197:                       node->GetParent()->SetSmaller(0);
    198:                    else
    199:                       node->GetParent()->SetBigger(0);
    200:                 }
    201:              }
    202:              else // has a smaller
    203:              {
    204:                 if (node == myHead)
    205:                    myHead = node->GetSmaller();
    206:                 else
    207:                 {
    208:                    if (node->GetParent()->GetSmaller() == node)
    209:                       node->GetParent()->SetSmaller(node->GetSmaller());
    210:                    else
    211:                       node->GetParent()->SetBigger(node->GetSmaller());
    212:                 }
    213:              }
    214:           }
    215:           else // node does have a bigger
    216:           {
    217:              if (!node->GetSmaller())
    218:              {
    219:                 if (node == myHead)
    220:                    myHead = node->GetBigger();
    221:                 else
    222:                 {
    223:                  if (node->GetParent()->GetSmaller() == node)
    224:                    node->GetParent()->SetSmaller(node->GetBigger());
    225:                 else
    226:                    node->GetParent()->SetBigger(node->GetBigger());
    227:                 }
    228:              }
    229:              else // node has both!!
    230:              {
    231:                 WordNode * next = node->GetBigger();
    232:
    233:                 while (next->GetSmaller())
    234:                    next=next->GetSmaller();
    235:
    236:                 if (next->GetParent()->GetSmaller() == next)
    237:                    next->GetParent()->SetSmaller(next->GetBigger());
    238:                 else
    239:                    next->GetParent()->SetBigger(next->GetBigger());
    240:
    241:                 node->SetWord(next->GetWord());
    242:
    243:                 if (next->GetBigger())
    244:                    next->GetBigger()->SetParent(node);
    245:
    246:                 node = next;
    247:              }
    248:           } // end else does have bigger
    249:        } // end if node
    250:        delete node;
    251:     } // end function
    252:
    253:
    254:     void BinaryTree::PrintTree()
    255:     {
    256:        PrintNode(myHead,1);
    257:     }
    258:     void BinaryTree::PrintNode(WordNode* pNode, int indent)
    259:     {
    260:           WordNode* pWN = pNode->GetSmaller();
    261:           if (pWN)
    262:              PrintNode(pWN, indent+1);
    263:           DoPrint(pNode, indent);
    264:           pWN = pNode->GetBigger();
    265:           if (pWN)
    266:              PrintNode(pWN, indent+1);
    267:
    268:     }
    269:     void BinaryTree::DoPrint(WordNode* pNode, int indent)
    270:     {
    271:        for (int i = 0; i<2*indent; i++)
    272:           cout << " ";
    273:        cout << pNode->GetWord()->GetText() << "\n";
    274:     }
    275:
    276:
    277:     int main(int argc, char **argv)
    278:     {
    279:        BinaryTree tree;
    280:        for (int i = 1; i< argc; i++)
    281:        {
    282:           rWord* word = new rWord(argv[i]);
    283:           tree.Insert(*word);
    284:           tree.PrintTree();
    285:
    286:        }
    287:        tree.PrintTree();
    288:        return 0;
    289:     }
    290:
Output:
    d:\>0807 now is the time for all good men to come to the aid of their party
              aid
            all
              come
          for
            good
        is
          men
      now
          of
            party
        the
            the
              their
          time
            to
              to
Analysis:

This listing is much like the previous, except that the WNwrapper class no longer is needed, and the print methods have changed.

In line 254, the single line of PrintTree() calls PrintNode(), with the pointer to the first node in the tree, and the indentation level of 1.

In line 258 of PrintNode(), the current node provides its smaller pointer, and PrintNode() is called recursively in line 260. DoPrint() is called right after the recursive call returns, and then the larger child is obtained and PrintNode() is again called, on the larger child.

The DoPrint() mechanism is simple: it counts off two spaces for each level of indentation and then prints the word.

Using Balanced Binary Trees

The biggest single problem with binary trees is that they can become badly out of balance. Suppose that you need to add the words is hurry the how dog cat to a binary tree. The tree would look like this:

    cat
            dog
          how
        hurry
      is
        the

Finding the or hurry would be fairly quick, but finding cat would take many more searches than it should. It would be nice to be able to rebalance this tree so that there were fewer levels, like this:

    cat
        dog
           how
      hurry
        is
          the

This provides three levels overall, rather than five. With larger trees, of course, the results can be even more dramatic.

Using AVL Trees

AVL trees promise that they will be no more than one level out of balance. That is, while the left node may have a child when the right does not, it will never be true, at any point in the tree, that one side has two levels of descendants when the other side has none.

Writing the AVL Tree

The trick with writing the AVL tree is that after each insert and each deletion, the tree must be in balance, or must be rebalanced. Recursion makes this possible, although it is by no means easy.

In order to write the AVL tree, the Node class must be modified to account for the imbalance in the tree. Listing 8.8 includes the declarations for the new node and the AVL tree itself. Note that this first attempt has not yet been parameterized; again, it is easier to develop and debug a class like this using actual types, rather than templates. Later, when you are confident the class is working well, you always can parameterize it.

Listing 8.9 provides the implementation, and listing 8.10 shows a short driver program. The analysis following listing 8.10 explains how the AVL tree works.

Listing 8.8 Declaring the AVL Tree

    1:       // Listing 8.8 - Declaring the AVL Tree
    2:
    3:       #include "word.hpp"
    4:       enum Tilt { tNone, tLeft, tRight};
    5:
    6:       class WordNode;     // forward declaration
    7:
    8:       class AVLTree
    9:       {
    10:      public:
    11:         AVLTree():myHead(0),myCount(0),insertedOK(FALSE){}
    12:         ~AVLTree(){}
    13:
    14:         long GetCount() const { return myCount; }
    15:         void Insert (rWord &);
    16:         void Delete (rWord &);
    17:         void PrintTree();
    18:
    19:      protected:
    20:         void InsertNode(WordNode*, WordNode*&);
    21:         void DeleteBothChildren(WordNode*& target, WordNode* ptr, BOOL
                &deleteok);
    22:         void RemoveNode(WordNode*& target, WordNode* doomed, BOOL &deleteok);
    23:
    24:         void leftBalance(WordNode*& target, BOOL &deleteok);
    25:         void rightBalance(WordNode*& target,BOOL &deleteok);
    26:         void RightRotate(WordNode *&);
    27:         void LeftRotate(WordNode *&);
    28:
    29:         void PrintNode(WordNode* pNode, int indent);
    30:         void DoPrint(WordNode* pNode, int indent);
    31:
    32:      private:
    33:         WordNode* myHead;
    34:         long myCount;
    35:         BOOL insertedOK;
    36:      };
    37:
    38:      class WordNode
    39:      {
    40:      public:
    41:         // Constructors
    42:         WordNode(rWord* word);
    43:         WordNode(rWord& word);
    44:         WordNode( WordNode&);
    45:         ~WordNode() {}
    46:
    47:         // Accessors
    48:         rWord* GetWord()  { return myWord; }
    49:         const rWord* const GetWord() const  { return myWord; }
    50:         void SetWord(rWord* target) { myWord = target;}
    51:         WordNode*& GetSmaller()   { return mySmaller;  }
    52:         WordNode*& GetBigger()   { return myBigger; }
    53:         WordNode* GetParent() const { return myParent; }
    54:
    55:         void SetSmaller(WordNode* target)  {mySmaller = target; }
    56:         void SetBigger(WordNode* target)  {myBigger = target; }
    57:         void SetParent(WordNode* target)  {myParent = target; }
    58:
    59:         Tilt GetTilt() { return myTilt; }
    60:         void SetTilt (Tilt theTilt) { myTilt = theTilt; }
    61:
    62:         // Insertion
    63:         void InsertSmaller(WordNode* NewNode);
    64:         void InsertBigger(WordNode* NewNode);
    65:
    66:         // overloaded operators
    67:         int operator<(const WordNode &rhs) const
    68:            {return *myWord < *(rhs.GetWord());}
    69:         int operator<=(const WordNode &rhs) const
    70:            {return *myWord <= *(rhs.GetWord());}
    71:
    72:
    73:
    74:         int operator>=(const WordNode &rhs) const
    75:            {return *myWord >= *(rhs.GetWord());}
    76:         int operator>(const WordNode &rhs) const
    77:            {return *myWord > *(rhs.GetWord());}
    78:         BOOL operator==(const WordNode &rhs) const
    79:            { return *myWord ==*(rhs.GetWord());}
    80:         BOOL operator==(const rWord& target) const
    81:            {return *myWord == target.GetText();}
    82:
    83:      private:
    84:         WordNode * mySmaller;
    85:         WordNode * myBigger;
    86:         WordNode * myParent;
    87:         Tilt myTilt;
    88:         rWord * myWord;
    89:      };
    90:

Listing 8.9 Implementing the AVL Tree

    1:      // Implementing the AVL tree
    2:     #include "avl.hpp"
    3:
    4:      WordNode::WordNode(rWord* word):
    5:         mySmaller(0),
    6:         myBigger(0),
    7:         myParent(0),
    8:         myTilt(tNone),
    9:         myWord(word)
    10:     {  }
    11:
    12:     WordNode::WordNode(rWord &word):
    13:        mySmaller(0),
    14:        myBigger(0),
    15:        myParent(0),
    16:        myWord(&word)
    17:     {  }
    18:
    19:     WordNode::WordNode(WordNode& rhs)
    20:     {
    21:        mySmaller=rhs.GetSmaller();
    22:        myBigger=rhs.GetBigger();
    23:        myParent=rhs.GetParent();
    24:        myWord = rhs.GetWord();
    25:     }
    26:
    27:     void WordNode::InsertSmaller(WordNode* newNode)
    28:     {
    29:          newNode->SetSmaller(mySmaller);
    30:          newNode->SetParent(this);
    31:          mySmaller=newNode;
    32:     }
    33:
    34:     void WordNode::InsertBigger(WordNode* newNode)
    35:     {
    36:          newNode->SetBigger(myBigger);
    37:          newNode->SetParent(this);
    38:          myBigger=newNode;
    39:     }
    40:
    41:
    42:     void AVLTree::RightRotate(WordNode*& target)
    43:     {
    44:        WordNode *p2, *p3;
    45:        p2 = target->GetBigger();
    46:        if(p2->GetTilt()==tRight)  // single rotation
    47:        {
    48:           target->SetBigger(p2->GetSmaller());
    49:           p2->SetSmaller(target);
    50:           target->SetTilt(tNone);
    51:           target = p2;
    52:        }
    53:        else      // double rotation
    54:        {
    55:           p3 = p2->GetSmaller();
    56:           p2->SetSmaller(p3->GetBigger());
    57:           p3->SetBigger(p2);
    58:           target->SetBigger(p3->GetSmaller());
    59:           p3->SetSmaller(target);
    60:           p2->SetTilt(p3->GetTilt() == tLeft? tRight : tNone);
    61:           target->SetTilt(p3->GetTilt() == tRight? tLeft: tNone);
    62:           target = p3;
    63:        }
    64:        target->SetTilt(tNone);
    65:     }
    66:
    67:     void AVLTree::LeftRotate(WordNode*& target)
    68:     {
    69:        WordNode *p2, *p3;
    70:        p2 = target->GetSmaller();
    71:        if(p2->GetTilt()==tLeft)      // single rotation
    72:        {
    73:           target->SetSmaller(p2->GetBigger());
    74:           p2->SetBigger(target);
    75:           target->SetTilt(tNone);
    76:           target = p2;
    77:        }
    78:        else                        // double rotation
    79:        {
    80:           p3 = p2->GetBigger();
    81:           p2->SetBigger(p3->GetSmaller());
    82:           p3->SetSmaller(p2);
    83:           target->SetSmaller(p3->GetBigger());
    84:           p3->SetBigger(target);
    85:           p2->SetTilt(p3->GetTilt() == tRight? tLeft : tNone);
    86:           target->SetTilt(p3->GetTilt() == tLeft? tRight: tNone);
    87:           target = p3;
    88:        }
    89:        target->SetTilt(tNone);
    90:     }
    91:
    92:     void AVLTree::Delete(rWord& rhs)
    93:     {
    94:        BOOL deleteok = FALSE;
    95:        WordNode* doomed = new WordNode(&rhs);
    96:        RemoveNode(myHead,doomed,deleteok);
    97:     }
    98:
    99:     void AVLTree::RemoveNode(WordNode*& target, WordNode* doomed, BOOL
            &deleteok)
    100:    {
    101:       WordNode* p;
    102:       if (!target)
    103:          deleteok = FALSE;
    104:       else
    105:       {
    106:          if (*target == *doomed)
    107:          {
    108:             p = target;
    109:             if (!target->GetBigger())
    110:             {
    111:                target = target->GetSmaller();
    112:                deleteok = TRUE;
    113:                delete p;
    114:                p = 0;
    115:             }
    116:             else if (!target->GetSmaller())
    117:             {
    118:                target = target->GetBigger();
    119:                deleteok = TRUE;
    120:                delete p;
    121:                p = 0;
    122:             }
    123:             else
    124:             {
    125:                DeleteBothChildren(target, target->GetSmaller(), deleteok);
    126:                if (deleteok)
    127:                   rightBalance(target,deleteok);
    128:             }
    129:          }
    130:          else if (*doomed < *target)
    131:          {
    132:             RemoveNode(target->GetSmaller(), doomed, deleteok);
    133:             if (deleteok)
    134:                rightBalance(target,deleteok);
    135:          }
    136:          else if (*doomed > *target)
    137:          {
    138:             RemoveNode(target->GetBigger(),doomed, deleteok);
    139:             if (deleteok)
    140:                leftBalance(target,deleteok);
    141:          }
    142:       }
    143:    }
    144:
    145:    void AVLTree::DeleteBothChildren(WordNode*& target, WordNode* ptr, BOOL
            &deleteok)
    146:    {
    147:       if (!ptr->GetBigger())
    148:       {
    149:          target->SetWord(ptr->GetWord());
    150:          ptr=ptr->GetSmaller();
    151:          deleteok = TRUE;
    152:       }
    153:       else
    154:       {
    155:          DeleteBothChildren(target, ptr->GetBigger(),deleteok);
    156:          if (deleteok)
    157:             leftBalance(ptr,deleteok);
    158:       }
    159:    }
    160:
    161:
    162:    void AVLTree::leftBalance(WordNode*& target,BOOL &deleteok)
    163:    {
    164:       WordNode *p2, *p3;
    165:       Tilt tilt2, tilt3;
    166:
    167:       switch (target->GetTilt())
    168:       {
    169:          case tRight:
    170:             target->SetTilt(tNone);
    171:             break;
    172:          case tNone:
    173:             target->SetTilt(tLeft);
    174:             deleteok = FALSE;
    175:             break;
    176:          case tLeft:
    177:             p2 = target->GetSmaller();
    178:             tilt2 = p2->GetTilt();
    179:             if (tilt2 != tRight)
    180:             {
    181:                target->SetSmaller(p2->GetBigger());
    182:                p2->SetBigger(target);
    183:                if (tilt2 == tNone)
    184:                {
    185:                   target->SetTilt(tLeft);
    186:                   p2->SetTilt(tRight);
    187:                   deleteok = FALSE;
    188:                }
    189:                else
    190:                {
    191:                   target->SetTilt(tNone);
    192:                   p2->SetTilt(tNone);
    193:                }
    194:                target = p2;
    195:             }
    196:             else
    197:             {
    198:                p3=p2->GetBigger();
    199:                tilt3=p3->GetTilt();
    200:                p2->SetBigger(p3->GetSmaller());
    201:                p3->SetSmaller(p2);
    202:                target->SetSmaller(p3->GetBigger());
    203:                p3->SetBigger(target);
    204:                p2->SetTilt(tilt3 == tRight? tLeft : tNone);
    205:                target->SetTilt(tilt3 == tLeft?tRight:tNone);
    206:                target=p3;
    207:                p3->SetTilt(tNone);
    208:             }
    209:             break;
    210:        }
    211:    }
    212:
    213:    void AVLTree::rightBalance(WordNode*& target,BOOL &deleteok)
    214:    {
    215:       WordNode *p2, *p3;
    216:       Tilt tilt2, tilt3;
    217:
    218:       switch (target->GetTilt())
    219:       {
    220:          case tLeft:
    221:             target->SetTilt(tNone);
    222:             break;
    223:          case tNone:
    224:             target->SetTilt(tRight);
    225:             deleteok = FALSE;
    226:             break;
    227:          case tRight:
    228:             p2 = target->GetBigger();
    229:             tilt2 = p2->GetTilt();
    230:             if (tilt2 != tLeft)
    231:             {
    232:                target->SetBigger(p2->GetSmaller());
    233:                p2->SetSmaller(target);
    234:                if (tilt2 == tNone)
    235:                {
    236:                   target->SetTilt(tRight);
    237:                   p2->SetTilt(tLeft);
    238:                   deleteok = FALSE;
    239:                }
    240:                else
    241:                {
    242:                   target->SetTilt(tNone);
    243:                   p2->SetTilt(tNone);
    244:                }
    245:                target = p2;
    246:             }
    247:             else
    248:             {
    249:                p3=p2->GetSmaller();
    250:                tilt3=p3->GetTilt();
    251:                p2->SetSmaller(p3->GetBigger());
    252:                p3->SetBigger(p2);
    253:                target->SetBigger(p3->GetSmaller());
    254:                p3->SetSmaller(target);
    255:                p2->SetTilt(tilt3 == tLeft?tRight:tNone);
    256:                target->SetTilt(tilt3 == tRight?tLeft:tNone);
    257:                target=p3;
    258:                p3->SetTilt(tNone);
    259:             }
    260:             break;
    261:        }
    262:    }
    263:
    264:
    265:    void AVLTree::Insert(rWord& rhs)
    266:    {
    267:       WordNode* newNode = new WordNode(&rhs);
    268:       InsertNode(newNode, myHead);
    269:    }
    270:
    271:    void AVLTree::InsertNode(WordNode* newNode, WordNode*& target)
    272:    {
    273:       if (!target)
    274:       {
    275:          target = newNode;
    276:          target->SetSmaller(0);
    277:          target->SetBigger(0);
    278:          target->SetTilt(tNone);
    279:          insertedOK=TRUE;
    280:          myCount++;
    281:       }
    282:       else if ((*newNode) <= (*target))
    283:       {
    284:          InsertNode(newNode,target->GetSmaller());
    285:          switch(target->GetTilt())
    286:          {
    287:             case tLeft:
    288:                LeftRotate(target);
    289:                insertedOK=FALSE;
    290:                break;
    291:             case tRight:
    292:                target->SetTilt(tNone);
    293:                insertedOK=FALSE;
    294:                break;
    295:             case tNone:
    296:                target->SetTilt(tLeft);
    297:          }
    298:       }
    299:       else
    300:       {
    301:          InsertNode(newNode,target->GetBigger());
    302:          switch(target->GetTilt())
    303:          {
    304:             case tRight:
    305:                RightRotate(target);
    306:                insertedOK=FALSE;
    307:                break;
    308:             case tLeft:
    309:                target->SetTilt(tNone);
    310:                insertedOK=FALSE;
    311:                break;
    312:             case tNone:
    313:                target->SetTilt(tRight);
    314:          }
    315:       }
    316:    }
    317:
    318:    void AVLTree::PrintTree()
    319:    {
    320:       PrintNode(myHead,1);
    321:    }
    322:    void AVLTree::PrintNode(WordNode* pNode, int indent)
    323:    {
    324:          WordNode* pWN = pNode->GetSmaller();
    325:          if (pWN)
    326:             PrintNode(pWN, indent+1);
    327:          DoPrint(pNode, indent);
    328:          pWN = pNode->GetBigger();
    329:          if (pWN)
    330:             PrintNode(pWN, indent+1);
    331:
    332:    }
    333:
    334:    void AVLTree::DoPrint(WordNode* pNode, int indent)
    335:    {
    336:       for (int i = 0; i<2*indent; i++)
    337:          cout << " ";
    338:       cout << pNode->GetWord()->GetText() << "\n";
    339:    }
    340:

Listing 8.10 Driver Program for the AVL Tree

    1:     // Listing 8.10 - Driver Program for the AVL Tree
    2:
    3:     int main(int argc, char **argv)
    4:     {
    5:        AVLTree tree;
    6:
    7:        // populate the tree
    8:        for (int i = 1; i< argc; i++)
    9:        {
    10:          rWord* word = new rWord(argv[i]);
    11:          tree.Insert(*word);
    12:       }
    13:
    14:       tree.PrintTree();
    15:
    16:       // decimate the tree
    17:       for (i = argc-1; i>=1; i-=3)
    18:       {
    19:          cout << "!! deleting " << argv[i] << endl;
    20:          rWord *word = new rWord(argv[i]);
    21:          tree.Delete(*word);
    22:       }
    23:
    24:       tree.PrintTree();
    25:       return 0;
    26:    }
Output:
    d:\>0810 now is the time for all good men to

            all
          for
            good
        is
          men
      now
          the
        time
          to

    !! deleting to
    !! deleting good
    !! deleting for
    !! deleting the

          for
            good
        is
      now
          the
        the
          to
Analysis:

In line 4 of listing 8.8, an enumerated constant Tilt is created, with three potential values: tNone, tLeft, and tRight. Every node tilts to the left or to the right, or has no tilt. Tilting to the left means that the left node has one depth greater than the right node.

The WordNode is declared in lines 42 through 89. In line 87, a new data member, myTilt, is declared to hold the individual node's tilt. Two new comparison operators are added: greater-than-or-equals in lines 74 and 75, and less-than-or-equals in lines 69 and 71.

Accessors for the Tilt are provided in lines 62 and 63; and GetSmaller() and GetBigger(), in lines 51 and 52, are modified to return a reference to the WordNode pointers, thus allowing the pointer to be changed by the calling program.

The AVL tree itself is declared in lines 8 through 40. A local variable, insertedOK, is created to keep track of when the tree needs balancing.

There is a simple set of public accessors, such as Insert() and Delete(), that call a suite of protected implementation methods. leftRotate() and rightRotate() are used by the insertion methods, and leftBalance() and rightBalance() are used by the deletion methods.

Take careful note that the left... and right... methods are complementary mirror images of each other.

Examine the Insert() method in lines 264 through 268. The object, in this case an rWord, is passed in and a node is created. InsertNode() is called with the new node and the pointer to the head of the list.

InsertNode() repeatedly tries to find a place for the new object, recursing into itself each time it descends a level. When objects are added, the parent object has its tilt set appropriately, and when the tilt becomes too great, the node is rotated.

In line 287, for example, if the node already has a left tilt, and another node is added to the left, then LeftRotate() is called.

Examine LeftRotate() in lines 66 through 89. The node is passed in, and its left node is examined. If the left node also has a left tilt, a single rotation is performed; otherwise a double rotation is performed.

Tip: When examining AVL trees and related binary trees, it is best to write down the structure on paper and manually walk through how nodes will be added and deleted.

Looking At Other Binary Trees

There are a host of other binary trees, but the truth is the only important remaining structure is the one you almost always will use when creating a program for storing items on disk: the B-tree. This structure is explained in detail on day 10 when Collection classes are covered.

Summary

Today you learned what a FIFO queue is and how to use it to print a binary tree. You also learned how to print a binary tree recursively, and what the primary limitations are in using binary trees. You learned what balanced trees are, and how to build one of the most powerful balanced trees: the AVL tree.

Q&A

Q: Why is an unsorted linked list the easiest way to implement a FIFO queue?

A: The alternative is to use an array, either of the objects or of pointers. Either you have to shuffle the array forward for each element removed from the list (or for each one added to the list, depending on which side you considered to be the front), or you have to use two pointers that represent the front and back, and they rotate around the list as if it were circular. Neither of these methods is terribly hard to implement, but the first one is inefficient and they both have the problem of the list filling up. Using a linked list is efficient and it never is full, because you always can add another node.

Q: Is there any way to traverse a binary tree breadth-first without using a FIFO queue?

A: Well, anything is possible, but it is very hard. You end up doing partial depth-first traversals many times, always keeping track of what depth you are supposed to be "traversing" in a breadth-first manner.

Q: Why are there only three values in the enum Tilt, used in an AVL tree? Don't we need to know how much it tilts?

A: Because an AVL tree is going to be kept nearly balanced, no one node will ever be left tilting by more than one in either direction.

Q: How does the AVL tree perform in the case that is the nemesis of the regular binary treethat is, where the elements being inserted already are sorted (or reverse sorted)?

A: The AVL tree has no problem. Work it out (on paper, or using the program from the chapter) for inserting the letters A through O, in order. Although it does a fair bit of rebalancing, it ends up with a perfectly bushy tree.

Q: When would an AVL tree be a good choice?

A: An AVL tree is a great choice when the data structure is small enough to remain all in memory, but big enough and accessed commonly enough that performance is still an issue. If the data structure is too big for all of it to be in memory at once, then it is better to use a technique that is optimized for disk access (which you see on day 10). If the data structure will never get very big, or if it is not accessed frequently, then you shouldn't bother with the extra code and extra complication of a AVL tree over a binary tree.

Workshop

The Workshop provides quiz questions to help you solidify your understanding of the material covered, and exercises to provide you with experience in using what you have learned. Try to answer the quiz and exercise questions before checking the answers in Appendix A, and make sure that you understand the answers before continuing to the next chapter.

Quiz

  1. What do FIFO and LIFO stand for? What is the more common name for a LIFO queue?

  2. Describe the algorithm to traverse a binary tree in breadth-first order (using a FIFO queue).

  3. How does an AVL tree avoid the worst cases that plague the binary tree?

[Click here for Answers]

Exercises

  1. Modify the FIFO queue template so that it keeps track of the tail of the queue, rather than hunting down the list for it. Test it using the test program.

  2. Make a generic queue template, which offers the methods InsertFront, InsertBack, RemoveFront, and RemoveBack.

  3. Write a test program that makes a queue of strings, using the queue you created in exercise 2. Be sure that you test all the forms of insert and remove on empty and nonempty queues.

  4. Write a test program that makes a queue of floats, using the queue template from exercise 2. If the queue template needed to be modified so that it could accept floats, then go back and redo exercise 3 to make sure that you didn't break it for strings.

  5. Write a template for a stack, using the queue template you created in exercises 2 and 4 as its parent class. Use private inheritance, so the interface of queue is not visible to the clients of stack.

Go to: Table of Contents | Next Page