Data Structures – Trees, Graphs

- Implementing data structures requires the consideration of the speed of operations on the objects
- Objects must be inserted, deleted, and searched as fast as possible for acceptable performance
- Object traversal on data structures such as linked lists proceed in a sequential, linear fashion
- Processing objects sequentially is good for only small collections of objects
- The number of comparisons required in a sequential search is, on average, half the size of the list
- For large lists, a more efficient organizational structure is required for faster operations


Binary Trees
- Data structure that organizes objects in a non-linear, two-dimensional fashion
- Each node in a binary tree contains two links (pointers) to other nodes (binary nodes)
- Contains a link to the first node in the binary tree referred to as the root node
- Branching out from the root node on both sides are links to the left subtree and right subtree

- The STL makes extensive use of binary search trees (e.g. Associative container: set)



Binary Tree Operations
- Insertion, deletion, and lookup (or search) operations require that tree nodes be visited
- Visiting tree nodes in order to perform an operation is also referred to as traversing the tree
- Hence, one of the most common tasks performed on a binary tree is traversal
- Ideally, we wish to visit the least amount of nodes possible to perform our operation (i.e. search)
- The fewer the number of visits, the faster the operation
- By positioning tree nodes according to their data value, we can minimize the number of visits
- The data value of a tree node is also referred to as a key
- By organizing nodes in this manner, we construct a binary search tree

Binary Search Tree
- In a binary search tree, the data value in each node is

- Larger (or greater than) the data value in its left child

- Smaller (or less than) the data value in its right child


- Node data values decrease down left branches and increase down right branches
- Every node has a unique data value - no nodes are duplicated in a binary search tree
- With this structure, searching traversal algorithms can be significantly enhanced
- In fact, every time we move down to a child during a search, we eliminate an entire subtree!

- To traverse binary search trees, we can call a method from within the same method

- Known as recursion, this process lends itself well as a method of searching in trees



- An algorithm is recursive if it refers to itself to complete the operation
- Recursive methods are methods that call itself
- Recursive methods must have some test for termination

Recursive traversals
- Let's consider the design and implementation of binary search tree traversals
- As we traverse the tree, we process (or visit) a node performing some operation
- We continue traversing nodes until we get to a node with no further links (an empty node)
- In our examples, "visiting a node" will refer to simply outputting the data value
- Consider the binary tree example below


- To traverse such a tree, we have two choices:

- Visit the node first

- Visit the subtrees first


- In addition, we also have an option to traverse either the left or the right subtree
- These various options lead to three different methods of recursive traversal

- Let’s look at each one using this tree as an example


Inorder traversal
- Traverse left subtree in an inorder traversal manner
- After left subtree is traversed, visit (process) the node
- After node is visited, traverse right subtree in an inorder traversal manner
- Node values are not processed until values in left subtree are processed

- The tree nodes in our example are processed in the following order under this method

6, 13, 17, 27, 33, 42, 48


Preorder traversal
- Visit (process) the node
- After node is visited, traverse left subtree in a preorder traversal manner
- After left subtree is traversed, traverse right subtree in a preorder traversal manner
- Node values are processed before traversal

- The tree nodes in our example are processed in the following order under this method

27, 13, 6, 17, 42, 33, 48


Postorder traversal
- Traverse left subtree in a postorder traversal manner
- After left subtree is traversed, traverse right subtree in a postorder traversal manner
- Visit (process) the node
- Node values are not processed until all its children

- The tree nodes in our example are processed in the following order under this method

6, 17, 13, 33, 48, 42, 27



Binary Tree Design

- Using recursion, let's see how we can design and implement one of the traversal methods above

- We design our class(es) to encapsulate the behavior of a binary tree using templates
- As with stacks, queues, and linked lists, a binary tree class should be able to contain any data type
- We can create a class to encapsulate both a tree node as well as the tree itself
- In our design below, we'll consider the inorder traversal method for a binary search tree

// Binary Tree class

#include <iostream>
#include "BinaryTreeNode.h"

template <class T>
class BinaryTree
  BinaryTreeNode<T> *root;      // root of tree


  // constructor

  // add node (pass data value of node)
  void insert( const T & );

  // inorder traversal
  void inorder()
      inorderHelper( root );
      cout << endl;


  // inorder method - recursive
  void inorderHelper( BinaryTreeNode<T> * );


// Constructor
template <class T>
  root = NULL;

// Add (insert) node, binary search tree
template <class T>
void BinaryTree<T>::insert( const T &value )
  BinaryTreeNode<T> *current;
  BinaryTreeNode<T> *trailer;
  BinaryTreeNode<T> *newNode;

  // Create node to store value
  newNode = new BinaryTreeNode<T>;
  newNode->data = value;

  // Insert according to binary search tree criteria
  if( root == NULL )
    root = newNode;
    current = root;
    while( current != NULL )
        trailer = current;
        if( current->data == value )
            cout << "Duplicates not allowed" << endl;
          if( current->data > value )
            current = current->lPtr;
            current = current->rPtr;
    if( trailer->data > value )
      trailer->lPtr = newNode;
      trailer->rPtr = newNode;


// Inorder traversal - recursive
template <class T>
void BinaryTree<T>::inorderHelper( BinaryTreeNode<T> *p )
  if( p != NULL )
      inorderHelper( p->lPtr );
      cout << p->data << " ";
      inorderHelper( p->rPtr );

// Binary Tree Node class
#include <iostream>

// Forward declaration of a templated class
template<class T> class BinaryTree;

template <class T>
class BinaryTreeNode
  friend class BinaryTree<T>;   // so tree can access node info

  T data;                       // data value in node
  BinaryTreeNode<T> *lPtr;      // pointer to left node
  BinaryTreeNode<T> *rPtr;      // pointer to right node


  // constructor


// Constructor
template <class T>
  lPtr = rPtr = NULL;

- Note the following line in the BinaryTreeNode header file

// Forward declaration of a templated class
template<class T> class BinaryTree;

- This takes the place of an #include declaration for a specific purpose

- Note that our BinaryTree header file includes the BinaryTreeNode header file

- We have a situation where two classes refer to each other in their class definitions

- If we include the BinaryTree header in an application, it includes the BinaryTreeNode

- If an include were used above, the BinaryTreeNode would include the BinaryTree

- This process would continue before either class is fully declared

- To avoid such a situation, we simply inform the compiler that a class will be defined later

- This is known as a forward declaration

- Using our classes, let's create a binary tree and traverse using inorder traversal

#include "BinaryTree.h"

  BinaryTree<int> iTree;
  iTree.insert( 12 );
  iTree.insert( 10 );
  iTree.insert( 14 );

10 12 14


- Many simulations and applications involve finding a shortest route or path
- Applications include electrical circuits, highway maps, project planning, etc...
- The study and analysis of such problems is referred to as graph theory
- These problems can be simulated programmatically to find solutions
- Graphs can be represented as an array of linked lists termed adjacency lists
- Similar to other data structures, operations on graphs can include

- Create the graph (i.e. using adjacency lists)

- Clear the graph

- Print the graph

- Determine whether graph is empty

- Traverse the graph