Trees

Specifically binary trees such as Fig 9.1.

A binary tree is either empty or it consists of a root node along with two binary trees, a left subtree and a right subtree. A recursive definition!

Can define a binary search tree for search time.

  1. all keys (if any) in the left subtree of the root precede the key in the root.

  2. the key in the root procedes all keys (if any) in its right subtree.

  3. the left and right subtrees of the root are again search trees

Tree Search

Have a variable pTreeRoot, which points to the root node of the tree.

#define EQ(a,b) (strcmp(a,b) == 0)
#define LT(a,b) (strcmp(a,b) < 0)
#define GT(a,b) (strcmp(a,b) > 0)

struct TreeNodeType {
    char *sbName;
    TreeNodeType *pTreeLeft;
    TreeNodeType *pTreeRight;
};

/*
 * SearchTree -- return pointer to node if successful, NULL if not found
 */
TreeNodeType *SearchTree(TreeNodeType *pTree, char *sbTarget)
{
    if (pTree == NULL)
        return(NULL);
    else if (EQ(sbTarget, pTree->sbName))
        return(pTree);
    else if (LT(sbTarget, pTree->sbName))
        return(SearchTree(pTree->pTreeLeft, sbTarget));
    else
        return(SearchTree(pTree->pTreeRight, sbTarget));
}

Tree Traversal

Do not always want to find a specific node, but may want to visit all nodes.

Three types of traversals (show trees for each):

  1. Preorder (V L R)

  2. Inorder (L V R)

  3. Postorder (L R V)

void Preorder(TreeNodeType *pTree)
{
    if (pTree != NULL) {
        Visit(pTree);
        Preorder(pTree->pTreeLeft);
        Preorder(pTree->pTreeRight);
    }
}

void Inorder(TreeNodeType *pTree)
{
    if (pTree != NULL) {
        Inorder(pTree->pTreeLeft);
        Visit(pTree);
        Inorder(pTree->pTreeRight);
    }
}

void Postorder(TreeNodeType *pTree)
{
    if (pTree != NULL) {
        Postorder(pTree->pTreeLeft);
        Postorder(pTree->pTreeRight);
        Visit(pTree);
    }
}

void Visit(TreeNodeType *pTree)
{
    cout << pTree->sbName << "\n";
}

Examine different traversals on actual tree.

Expression Trees

Names for traversals are related to expression trees.

Examine the expression tree for ((x + y) * (x - y)) + 2

                   +
                  / 
                 *   2
                / \    
               +   -
              / \ / 
              x y x y

The leaves represent constants or variables, the non-leaves are for operators.

Insertion

Know how to traverse a tree, how do we build one? Successively insert nodes into the tree (in sorted order).

main()
{
    char sbValue[128];
    TreeNodeType *pTreeRoot;

    pTreeRoot = NULL;                // initialization

    cout << "Input some names\n";
    while (cin.getline(sbValue, 128) && !cin.eof()) {
        if (strcmp(sbValue, "quit") == 0)
            break;
        pTreeRoot = Insert(pTreeRoot, sbValue);
    }
    Inorder(pTreeRoot);
}

/*
 * Insert -- insert the string value in the tree with this root
 */
TreeNodeType *Insert(TreeNodeType *pTree, char *sbValue)
{
    if (pTree == NULL)
        pTree = MakeNode(sbValue);
    else if (LT(sbValue, pTree->sbName))
        pTree->pTreeLeft = Insert(pTree->pTreeLeft, sbValue);
    else if (GT(sbValue, pTree->sbName))
        pTree->pTreeRight = Insert(pTree->pTreeRight, sbValue);
    /* else duplicate key do nothing */
    return(pTree);
}

/*
 * MakeNode -- make a node
 */
TreeNodeType *MakeNode(char *sbValue)
{
    TreeNodeType *pTree;

    pTree = new TreeNodeType;
    if (pTree == NULL)
        return(NULL);
    pTree->pTreeLeft = pTree->pTreeRight = NULL;
    pTree->sbName = new char[strlen(sbValue)+1];
    if (pTree->sbName == NULL)
        return(NULL);
    strcpy(pTree->sbName, sbValue);
    return(pTree);
}

Deletion

Deletion from a search tree is a little trickier.

First we need to find the node to delete. Then we need to exam which case we have:

Look at new main program to insert and delete nodes:

main()
{
    char sbValue[128];
    TreeNodeType *pTreeRoot;

    pTreeRoot = NULL;                // initialization

    cout << "Input some names\n";
    while (cin.getline(sbValue, 128) && !cin.eof()) {
        if (strcmp(sbValue, "quit") == 0)
            break;
        pTreeRoot = Insert(pTreeRoot, sbValue);
    }
    Inorder(pTreeRoot);

    cout << "Now delete some names\n";
    while (cin.getline(sbValue, 128) && !cin.eof()) {
        if (strcmp(sbValue, "quit") == 0)
            break;
        (void)DeleteNode(pTreeRoot, sbValue);
    }
    Inorder(pTreeRoot);
}

Use example with strings foo bar hello world bat foul fool

/*
 * DeleteNode -- Delete node with the value, return 0 on success, -1 on failure
 *      Same idea as SearchTree to find the node, but must use call-by-reference
 *      on pointer so Delete() can make actual changes
 */
int DeleteNode(TreeNodeType*&pTree, char *sbTarget)
{
    if (pTree == NULL)
        return(-1);
    else if (EQ(sbTarget, pTree->sbName))
        return(Delete(pTree));                /* delete the node we found */
    else if (LT(sbTarget, pTree->sbName))
        return(DeleteNode((pTree->pTreeLeft), sbTarget));
    else
        return(DeleteNode((pTree->pTreeRight), sbTarget));
}

/*
 * ReturnSpace -- return space allocated for the given node
 */
ReturnSpace(TreeNodeType *pTree)
{
    delete [] pTree->sbName;
    delete pTree;
}

/*
 * Delete -- delete the node, reattach left and right subtrees.  From pg 318
 *      of Kruse text.
 */
int Delete(TreeNodeType * &pTree)
{
    TreeNodeType *pTreeTmp;

    if (pTree == NULL)
        return(-1);
    else if (pTree->pTreeRight == NULL) {
        pTreeTmp = pTree;
        pTree = pTree->pTreeLeft;        /* reattach left subtree */
        ReturnSpace(pTreeTmp);
    }
    else if (pTree->pTreeLeft == NULL) {
        pTreeTmp = pTree;
        pTree = pTree->pTreeRight; /* reattach right subtree */
        ReturnSpace(pTreeTmp);
    }
    else {
        for (pTreeTmp = pTree->pTreeRight; pTreeTmp->pTreeLeft != NULL;
             pTreeTmp = pTreeTmp->pTreeLeft)
            ;                        /* traverse until left tree is NULL */
        pTreeTmp->pTreeLeft = pTree->pTreeLeft;
        pTreeTmp = pTree;
        pTree = pTree->pTreeRight; /* reattach right subtree */
        ReturnSpace(pTreeTmp);
    }
    return(0);
}

Balanced Binary Search Trees

The trees that we have built and modified are guaranteed to be binary search trees, but not balanced binary search trees--starting with a sorted list we could get a linear line of nodes.

Building and maintaining a binary search tree is much more complex. Two approaches:

  1. building one from a sorted list

  2. building and maintaining one in general

AVL trees

Named after two Russian mathematicians, they are binary search trees such that the heights of the left and right subtrees of the root differ by no more than one and all subtrees have this same property.

More complex to manage. Details for another class.