Lec#10
August 2 1995

Quick sort:
similar to merge sort, list is split in to two parts and the quick sort
algorithm is applied to each part. However, as you will see there is no merge
that is needed when you finish with each part. This algorithm works based on
the use of a pivot, the content of the first cell is identified as the pivot
and we shift all values smaller to the left of that value.  When this process
ends, all values smaller than the pivot value are to its left and all values
larger are to its right.  Even without any mathematical proof of its NlogN
behavior, you get the feeling that data items are shifted to the correct area 
that they belong during each pass that puts the pivot value in its proper
position within the list.  If you consider that in insertion sort after
each pass only one value actually moves, you will see that quick sort is a 
much more powerful approach to sorting lists.

Algorithm:
   
    quick sort:

	if list length > 1 then
	    use first cell value as pivot and split list in to two
	    apply quick sort to the sublist to left of pivot 
	    apply quick sort to the sublist to right of pivot 
	end if

Trees
-----
conceptually they represent data in the form of a tree.  Basically, a tree
has a root node with some offsprings.  Each offspring may intern have 
offspring of its own.  The great thing about the whole thing is that since 
the offsprings themselves form a tree we are able apply a variety of algorithms
to the tree recursively.  Technically, tree are just liked lists that have
more than one pointer comming out of each node.

Binary search Tree
------------------
This is a special type of tree in that you have two branches comming out of
each node and all offsprings to the left of the root are smaller than the root
and all nodes to the right of the root are larger. As discussed for trees in
general, each offspring is also a binary search tree.

Lets insert some value in to a binary search tree: 10 12 30 8 7 9 11

insert 10: tree only has one node in it

                10

insert 12: tree has two nodes in it

                10
		  \
		   12

insert 30: tree has three nodes in it

                10
		  \
		   12
		     \
		      30

insert 8: tree has four nodes in it

                10
	       /  \
	      8    12
	             \
		      30

insert 7: tree has five nodes in it

                10
	       /  \
	      8	   12
             /       \
            7	      30

insert 9: tree has six nodes in it

                10
	       /  \
	      8	   12
             / \     \
            7	9      30

insert 11: tree has seven nodes in it

                10
	       /  \
	     8	    12
            / \    /  \
           7   9 11    30

When discussing trees we care about their hight or levels of the tree.
In the tree above the hight of it is 3 and that means that with only 3 
comaprisons we can determine if a value is in the tree or not.  So, searches
work very similarly to binary searches on sorted arrays, except for the
fact that we don't have to sort an array we just put our elements in the right
position of the tree.  

It is possible for binary search trees to grow like a linked list, how?  well,
think about it, if values shown above came in ascending order, what would 
happen? the list will look like ...

7
 \
  8
   \
    9
     \
      10
       \
        11
         \
          12
           \
            30

Studies have shown that binary search trees populate just fine if their
data comes in random order.  There are types of trees that ensure that what
you see above does not happen, however, their algorithm is much more complex.

Lets look at the node structure, and insertion and find algorithm for a 
binary search tree:

struct Node
{
   int val;
   Node * left;
   Node * right;
};

// precondition:  root is NULL or points the root of a tree
// postcondition: val is added to the tree and 1 is returned or
//                no insertion takes place iff val is already in list and
//                return 0
int insert (Node *& root, int val)
{
  if (root == NULL) 
  {
    root = new Node;
    root->val = val;
    root->left=root->right=NULL;
    return 1;
  }
  if (root->val == val)
  {
    cout << "Duplicate keys, no insertion\n';
    return 0;
  }
  if (root->val > val)
    return insert (root->left,val);
  else
    return insert (root->right,val);
}

// precondition:  root is NULL or points the root of a tree
// postcondition: val is found in the tree and 1 is returned otherwise
//                return 0
void find (Node * root, int val)
{
  if (root == NULL) return 0; 
  if (root->val == val) return 1;
  if (root->val > val) return find (root->left,val);
  if (root->val < val) return find (root->right,val);
}

Lets write a function that would output data in ascending order.  It
requires an inorder traversal.

void traverse (Node * root)
{
   if (root != NULL)
   {
      traverse (root->left);
      cout << root->val <<'\n';
      traverse (root->right);
   }
}