0% found this document useful (0 votes)
14 views73 pages

CIS 265 Notes Part 2

CIS 265 class notes - part 2 of 3

Uploaded by

fastalan392
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views73 pages

CIS 265 Notes Part 2

CIS 265 class notes - part 2 of 3

Uploaded by

fastalan392
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 73

1

Chapter 5: Linked Lists

Linked List

A Linked List is a series of objects connected together linearly by a


reference to another of their kind. The actual list is usually
composed of data objects called nodes which may contain the actual
data, or just serve as references to other objects that hold the
data.

Things to Know About Lists

 Dynamic Allocation is the hallmark of lists. We only create nodes


for each element of data so we use essentially only the memory we
need to store a data set of a given size. No need for array resizing
or what have you

 Traversal is the process of moving from one node to another.


Unlike arrays, we must examine every node from one end to our
destination when performing many of the list operations. We can not
merely calculate an offset and access that location in memory

 Cache Locality is not a linked list feature. Unlike arrays, the


actual nodes can be anywhere in memory and the data they store may be
one or two references away as well. This means that access to linked
lists is generally slower than arrays before allowing for linked list
traversal

 A Swap operation can be harder to do with lists, but only if we are


not careful. We should take care to make sure the data is copied
between objects but the pointers left alone. Most data types will be
okay with this, and a list of objects will just require a reference
swap if it is designed correctly
2
 Sorted or Unsorted just like arrays, linked lists may be sorted or
unsorted. Note that sorting requires

 Head and Tail nodes greatly easy the logic behind the linked list
operations. Every list has a head and tail dummy node to start with
and this reduces the number of possible list states by 3. All for
the cost of two extra data nodes in memory utilization

 Wrapper Class the node of a list can be the data item itself
modified to have next (and maybe prev) references. Or, the node can
be a class itself designed to be nothing more than a few references.
In this case a private class to the data structure with just exposed
field variables and no methods is acceptable

Types of Linked Lists

 Standard linked list has a next reference

 Double Ended List is one where the data structure itself maintains
a reference to the fist and the last list elements. Therefore,
insertion can occur at either end.

 Circular Where the last element points back to the first, like a
snake swallowing its own tail. Note that a circular list may need
only a head (or tail) and not both, depending on how it is coded

 Doubly Linked has links going both directions. So there is a next


reference as well as previous that must be maintained
3
Basic Operations on a List

 Add which adds an item to the list. When adding a node B, you
have to be sure that A -> C turns into A -> B -> C. Adding at either
end can be O(1), but adding in the middle needs a traversal and is
thus O(n)

 Remove which deletes an item from the list. When removing a node
B, you have to be sure that A -> B -> C turns into A -> B -> C.
Note, that since you must be on B to know what to delete, you will
need a trailing reference as well if the list is not doubly linked.
This requires a traversal in the same situations as add

 Find which looks though the list to find if the item exists or not.
Find always requires a traversal so is O(n) regardless of list set up

Code Examples)

// linkList2.java
// demonstrates linked list
// to run this program: C>java LinkList2App
////////////////////////////////////////////////////////////////
class Link
{
public int iData; // data item (key)
public double dData; // data item
public Link next; // next link in list

public Link(int id, double dd) // constructor


{
iData = id;
dData = dd;
}

public void displayLink() // display ourself


{
System.out.print("{" + iData + ", " + dData + "} ");
}
} // end class Link

////////////////////////////////////////////////////////////////
4
class LinkList
{
private Link first; // ref to first link on list

public LinkList() // constructor


{
first = null; // no links on list yet
}

public void insertFirst(int id, double dd)


{ // make new link
Link newLink = new Link(id, dd);
newLink.next = first; // it points to old first link
first = newLink; // now first points to this
}

public Link find(int key) // find link with given key


{ // (assumes non-empty list)
Link current = first; // start at 'first'
while(current.iData != key) // while no match,
{
if(current.next == null) // if end of list,
{
return null; // didn't find it
}
else // not end of list,
{
current = current.next; // go to next link
}
}
return current; // found it
}

public Link delete(int key) // delete link with given key


{ // (assumes non-empty list)
Link current = first; // search for link
Link previous = first;
while(current.iData != key)
{
if(current.next == null)
{
return null; // didn't find it
}
else
{
previous = current; // go to next link
current = current.next;
}
5
} // found it
if(current == first) // if first link,
{
first = first.next; // change first
}
else // otherwise,
{
previous.next = current.next; // bypass it
}
return current;
}

public void displayList() // display the list


{
System.out.print("List (first-->last): ");
Link current = first; // start at beginning of list
while(current != null) // until end of list,
{
current.displayLink(); // print data
current = current.next; // move to next link
}
System.out.println("");
}
} // end class LinkList

////////////////////////////////////////////////////////////////
class LinkList2App
{
public static void main(String[] args)
{
LinkList theList = new LinkList(); // make list

theList.insertFirst(22, 2.99); // insert 4 items


theList.insertFirst(44, 4.99);
theList.insertFirst(66, 6.99);
theList.insertFirst(88, 8.99);

theList.displayList(); // display list

Link f = theList.find(44); // find item


if( f != null)
{
System.out.println("Found link with key " + f.iData);
}
else
{
6
System.out.println("Can't find link");
}

Link d = theList.delete(66); // delete item


if( d != null )
{
System.out.println("Deleted link with key " + d.iData);
}
else
{
System.out.println("Can't delete link");
}

theList.displayList(); // display list


} // end main()
} // end class LinkList2App

Lists and Data Structures

 An Array presents difficulty because we can not directly address


each element in the array with out allowing for a traversal O(N) of
the list

 A Stack is easy if we in add at the beginning O(1) and remove from


the beginning O(1) as well

 A Queue implementation is similar to an array as well. If we in


add at the beginning O(1) then we remove at the end O(n). However, a
doubly linked list lets us reduce this to O(1) remove as well

Code Example)

/ linkStack.java
// demonstrates a stack implemented as a list
// to run this program: C>java LinkStackApp

////////////////////////////////////////////////////////////////
class Link
7
{
public long dData; // data item
public Link next; // next link in list

public Link(long d) // constructor


{
dData = d;
}

public void displayLink() // display this link


{
System.out.print(dData + " ");
}
} // end class Link

////////////////////////////////////////////////////////////////
class LinkList
{
private Link first; // ref to first item on list

public LinkList() // constructor


{
first = null; // no items on list yet
}

public boolean isEmpty() // true if list is empty


{
return (first==null);
}

public void insertFirst(long dd) // insert at start of list


{ // make new link
Link newLink = new Link(dd);
newLink.next = first; // newLink --> old first
first = newLink; // first --> newLink
}

public long deleteFirst() // delete first item


{ // (assumes list not empty)
Link temp = first; // save reference to link
first = first.next; // delete it: first-->old next
return temp.dData; // return deleted link
}

public void displayList()


{
8
Link current = first; // start at beginning of list
while(current != null) // until end of list,
{
current.displayLink(); // print data
current = current.next; // move to next link
}
System.out.println("");
}

} // end class LinkList

////////////////////////////////////////////////////////////////
class LinkStack
{
private LinkList theList;

public LinkStack() // constructor


{
theList = new LinkList();
}

public void push(long j) // put item on top of stack


{
theList.insertFirst(j);
}

public long pop() // take item from top of stack


{
return theList.deleteFirst();
}

public boolean isEmpty() // true if stack is empty


{
return ( theList.isEmpty() );
}

public void displayStack()


{
System.out.print("Stack (top-->bottom): ");
theList.displayList();
}
} // end class LinkStack

////////////////////////////////////////////////////////////////
class LinkStackApp
9
{
public static void main(String[] args)
{
LinkStack theStack = new LinkStack(); // make stack

theStack.push(20); // push items


theStack.push(40);

theStack.displayStack(); // display stack

theStack.push(60); // push items


theStack.push(80);

theStack.displayStack(); // display stack

theStack.pop(); // pop items


theStack.pop();

theStack.displayStack(); // display stack


} // end main()
} // end class LinkStackApp

Code Example)

// linkQueue.java
// demonstrates queue implemented as double-ended list
// to run this program: C>java LinkQueueApp

////////////////////////////////////////////////////////////////
class Link
{
public long dData; // data item
public Link next; // next link in list

public Link(long d) // constructor


{
dData = d;
}

public void displayLink() // display this link


{
System.out.print(dData + " ");
}
} // end class Link

////////////////////////////////////////////////////////////////
10
class FirstLastList
{
private Link first; // ref to first item
private Link last; // ref to last item

public FirstLastList() // constructor


{
first = null; // no items on list yet
last = null;
}

public boolean isEmpty() // true if no links


{
return first==null;
}

public void insertLast(long dd) // insert at end of list


{
Link newLink = new Link(dd); // make new link
if( isEmpty() ) // if empty list,
{
first = newLink; // first --> newLink
}
else
{
last.next = newLink; // old last --> newLink
}
last = newLink; // newLink <-- last
}

public long deleteFirst() // delete first link


{ // (assumes non-empty list)
long temp = first.dData;
if(first.next == null) // if only one item
{
last = null; // null <-- last
}
first = first.next; // first --> old next
return temp;
}

public void displayList()


{
11
Link current = first; // start at beginning
while(current != null) // until end of list,
{
current.displayLink(); // print data
current = current.next; // move to next link
}
System.out.println("");
}
} // end class FirstLastList

////////////////////////////////////////////////////////////////
class LinkQueue
{
private FirstLastList theList;

public LinkQueue() // constructor


{
theList = new FirstLastList();
} // make a 2-ended list

public boolean isEmpty() // true if queue is empty


{
return theList.isEmpty();
}

public void insert(long j) // insert, rear of queue


{
theList.insertLast(j);
}

public long remove() // remove, front of queue


{
return theList.deleteFirst();
}

public void displayQueue()


{
System.out.print("Queue (front-->rear): ");
theList.displayList();
}
} // end class LinkQueue

////////////////////////////////////////////////////////////////
class LinkQueueApp
12
{
public static void main(String[] args)
{
LinkQueue theQueue = new LinkQueue();
theQueue.insert(20); // insert items
theQueue.insert(40);

theQueue.displayQueue(); // display queue

theQueue.insert(60); // insert items


theQueue.insert(80);

theQueue.displayQueue(); // display queue

theQueue.remove(); // remove items


theQueue.remove();

theQueue.displayQueue(); // display queue


} // end main()
} // end class LinkQueueApp

An Iterator is a concept using a separate reference to maintain a


current bookmark into the list. Care must be taken when manipulating
a list by other methods that the iterator reference not be left
pointing at a removed node

 reset set the pointer back to the start of the list

 nextLink moves the pointer ahead one

 getCurrent returns the current reference that pointer is looking at

 atEnd returns true if at end of list

 insertAfter adds a node after current pointer


13

 insertBefore adds a node before current pointer. May require a


trailing pointer or doubly linked list

 deleteCurrent deletes current node, works like delete operation,


except it may not have to find a node first

Code Example)

// interIterator.java
// demonstrates iterators on a linked listListIterator
// to run this program: C>java InterIterApp import java.io.*;
// for I/O

////////////////////////////////////////////////////////////////
class Link
{
public long dData; // data item
public Link next; // next link in list

public Link(long d) // constructor


{
dData = d;
}

public void displayLink() // display this link


{
System.out.print(dData + " ");
}
} // end class Link

////////////////////////////////////////////////////////////////
class LinkList
{
private Link first; // ref to first item on list

public LinkList() // constructor


{
first = null; // no items on list yet
}
public Link getFirst() // get value of first
{
return first;
14
}

public void setFirst(Link f) // set first to new link


{
first = f;
}

public boolean isEmpty() // true if list is empty


{
return first == null;
}

public ListIterator getIterator() // return iterator


{
return new ListIterator(this); // initialized with
} // this list

public void displayList()


{
Link current = first; // start at beginning of list
while(current != null) // until end of list,
{
current.displayLink(); // print data
current = current.next; // move to next link
}

System.out.println("");
}
} // end class LinkList

////////////////////////////////////////////////////////////////
class ListIterator
{
private Link current; // current link
private Link previous; // previous link
private LinkList ourList; // our linked list

public ListIterator(LinkList list) // constructor


{
ourList = list;
reset();
}

public void reset() // start at 'first'


{
current = ourList.getFirst();
15
previous = null;
}

public boolean atEnd() // true if last link


{
return (current.next==null);
}

public void nextLink() // go to next link


{
previous = current;
current = current.next;
}

public Link getCurrent() // get current link


{
return current;
}

public void insertAfter(long dd) // insert after


{ // current link
Link newLink = new Link(dd);

if( ourList.isEmpty() ) // empty list


{
ourList.setFirst(newLink);
current = newLink;
}
else // not empty
{
newLink.next = current.next;
current.next = newLink;
nextLink(); // point to new link
}
}

public void insertBefore(long dd) // insert before


{ // current link
Link newLink = new Link(dd);

if(previous == null) // beginning of list


{ // (or empty list)
newLink.next = ourList.getFirst();
ourList.setFirst(newLink);
reset();
}
else // not beginning
{
16
newLink.next = previous.next;
previous.next = newLink;
current = newLink;
}
}

public long deleteCurrent() // delete item at current


{
long value = current.dData;
if(previous == null) // beginning of list
{
ourList.setFirst(current.next);
reset();
}
else // not beginning
{
previous.next = current.next;
if( atEnd() )
{
reset();
}
else
{
current = current.next;
}
}
return value;
}
} // end class ListIterator

////////////////////////////////////////////////////////////////
class InterIterApp
{
public static void main(String[] args) throws IOException
{
LinkList theList = new LinkList(); // new list
ListIterator iter1 = theList.getIterator(); // new iter
long value;

iter1.insertAfter(20); // insert items


iter1.insertAfter(40);
iter1.insertAfter(80);
iter1.insertBefore(60);

while(true)
{
17
System.out.print("Enter first letter of show, reset, ");
System.out.print("next, get, before, after, delete: ");
System.out.flush();
int choice = getChar(); // get user's option

switch(choice)
{
case 's': // show list
if( !theList.isEmpty() )
theList.displayList();
else
System.out.println("List is empty");
break;
case 'r': // reset (to first)
iter1.reset();
break;
case 'n': // advance to next item
if( !theList.isEmpty() && !iter1.atEnd() )
iter1.nextLink();
else
System.out.println("Can't go to next link");
break;
case 'g': // get current item
if( !theList.isEmpty() )
{
value = iter1.getCurrent().dData;
System.out.println("Returned " + value);
}
else
System.out.println("List is empty");
break;
case 'b': // insert before current
System.out.print("Enter value to insert: ");
System.out.flush();
value = getInt();
iter1.insertBefore(value);
break;
case 'a': // insert after current
System.out.print("Enter value to insert: ");
System.out.flush();
value = getInt();
iter1.insertAfter(value);
break;
case 'd': // delete current item
if( !theList.isEmpty() )
{
value = iter1.deleteCurrent();
System.out.println("Deleted " + value);
18
}
else
System.out.println("Can't delete");
break;
default:
System.out.println("Invalid entry");
} // end switch
} // end while
} // end main()

public static String getString() throws IOException


{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}

public static char getChar() throws IOException


{
String s = getString();
return s.charAt(0);
}

public static int getInt() throws IOException


{
String s = getString();
return Integer.parseInt(s);
}
} // end class InterIterApp

Code Example)

// doublyLinked.java
// demonstrates doubly-linked list
// to run this program: C>java DoublyLinkedApp

////////////////////////////////////////////////////////////////
class Link
{
public long dData; // data item
public Link next; // next link in list
public Link previous; // previous link in list
19
public Link(long d) // constructor
{
dData = d;
}

public void displayLink() // display this link


{
System.out.print(dData + " ");
}
} // end class Link

////////////////////////////////////////////////////////////////
class DoublyLinkedList
{
private Link first; // ref to first item
private Link last; // ref to last item

public DoublyLinkedList() // constructor


{
first = null; // no items on list yet
last = null;
}

public boolean isEmpty() // true if no links


{
return first == null;
}

public void insertFirst(long dd) // insert at front of list


{
Link newLink = new Link(dd); // make new link

if( isEmpty() ) // if empty list,


{
last = newLink; // newLink <-- last
}
else
{
first.previous = newLink; // newLink <-- old first
}
newLink.next = first; // newLink --> old first
first = newLink; // first --> newLink
}

public void insertLast(long dd) // insert at end of list


{
20
Link newLink = new Link(dd); // make new link
if( isEmpty() ) // if empty list,
{
first = newLink; // first --> newLink
}
else
{
last.next = newLink; // old last --> newLink
newLink.previous = last; // old last <-- newLink
}
last = newLink; // newLink <-- last
}

public Link deleteFirst() // delete first link


{ // (assumes non-empty list)
Link temp = first;
if(first.next == null) // if only one item
{
last = null; // null <-- last
}
else
{
first.next.previous = null; // null <-- old next
}
first = first.next; // first --> old next
return temp;
}

public Link deleteLast() // delete last link


{ // (assumes non-empty list)
Link temp = last;
if(first.next == null) // if only one item
{
first = null; // first --> null
}
else
{
last.previous.next = null; // old previous --> null
}
last = last.previous; // old previous <-- last
return temp;
}

public boolean insertAfter(long key, long dd)


{ // (assumes non-empty list)
Link current = first; // start at beginning
21

while(current.dData != key) // until match is found,


{
current = current.next; // move to next link
}

if(current == null)
{
return false; // didn't find it
}
Link newLink = new Link(dd); // make new link

if(current==last) // if last link,


{
newLink.next = null; // newLink --> null
last = newLink; // newLink <-- last
}
else // not last link,
{
newLink.next = current.next; // newLink --> old next
// newLink <-- old next
current.next.previous = newLink;
}
newLink.previous = current; // old current <-- newLink
current.next = newLink; // old current --> newLink
return true; // found it, did insertion
}

public Link deleteKey(long key) // delete item w/ given key


{ // (assumes non-empty list)
Link current = first; // start at beginning
while(current.dData != key) // until match is found,
{
current = current.next; // move to next link
if(current == null)
{
return null; // didn't find it
}
} // end while
if(current==first) // found it; first item?
{
first = current.next; // first --> old next
}
else // not first
{ // old previous --> old next
current.previous.next = current.next;
}
22
if(current==last) // last item?
{
last = current.previous; // old previous <-- last
}
else // not last
{ // old previous <-- old next
current.next.previous = current.previous;
}

return current; // return value


}

public void displayForward()


{
System.out.print("List (first-->last): ");
Link current = first; // start at beginning
while(current != null) // until end of list,
{
current.displayLink(); // display data
current = current.next; // move to next link
}
System.out.println("");
}

public void displayBackward()


{
System.out.print("List (last-->first): ");
Link current = last; // start at end
while(current != null) // until start of list,
{
current.displayLink(); // display data
current = current.previous; // move to previous link
}
System.out.println("");
}
} // end class DoublyLinkedList

////////////////////////////////////////////////////////////////
class DoublyLinkedApp
{
public static void main(String[] args)
{ // make a new list
DoublyLinkedList theList = new DoublyLinkedList();

theList.insertFirst(22); // insert at front


theList.insertFirst(44);
23
theList.insertFirst(66);

theList.insertLast(11); // insert at rear


theList.insertLast(33);
theList.insertLast(55);

theList.displayForward(); // display list forward


theList.displayBackward(); // display list backward

theList.deleteFirst(); // delete first item


theList.deleteLast(); // delete last item
theList.deleteKey(11); // delete item with key 11

theList.displayForward(); // display list forward

theList.insertAfter(22, 77); // insert 77 after 22


theList.insertAfter(33, 88); // insert 88 after 33

theList.displayForward(); // display list forward


} // end main()
} // end class DoublyLinkedApp

Chapter 6: Recursion, Removing Recursion, Merge Sort


24

Recursion

Recursion is a process by which methods call themselves to complete a


problem. For a recursive algorithm to work correctly it needs three
key parts

 The Smaller Caller each recursive method call must result in


smaller possible number of total future recursive calls. I.e. Called
with decreasing numbers if the base case is (see below) stops o n
small numbers, etc.

 A Base Case each recursive algorithm must support a stopping case


that returns and does not call the method (smaller caller) again.
This case must be something reachable by all calls / arguments passed
to the algorithm

 General Case the normal smaller caller case where the function
calls a “slightly smaller version” of itself, rather than return

Mathematically speaking it is a function, such as factorial, defined


as n! = / 1, if n = 0
\ n*(n-1), if n > 0

In this case the base case is 1 when n is 0, the general case is a


call to n*(n-1) when n >0. The smaller caller is the (n-1) recursive
call. Note that factorial only works for positive integers greater
than or equal to 0

Inner Workings of Recursion Every time a program calls a method a new


activation record is added to the stack. When the last recursive
call is finished (base case), then the stack unwinds returning
successive pieces of the answer until the original call is reached
and the final answer is produced / result achieved

Code Example)
25
public static int Factorial(int number)
{
if ( number == 0 )
{
return 1;
}
else
{
return( number * Factorial(number –1) );
}
}

Code Example)

// triangle.java
// evaluates triangular numbers
// to run this program: C>java TriangleApp
import java.io.*;

////////////////////////////////////////////////////////////////
class TriangleApp
{
static int theNumber;

public static void main(String[] args) throws IOException


{
System.out.print("Enter a number: ");
theNumber = getInt();
int theAnswer = triangle(theNumber);
System.out.println("Triangle="+theAnswer);
} // end main()

public static int triangle(int n)


{
if(n == 1)
{
return 1;
}
else
{
return ( n + triangle(n-1) );
}
}

public static String getString() throws IOException


{
26
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}

public static int getInt() throws IOException


{
String s = getString();
return Integer.parseInt(s);
}
} // end class TriangleApp

Code Example)

// anagram.java
// creates anagrams
// to run this program: C>java AnagramApp
import java.io.*;

////////////////////////////////////////////////////////////////
class AnagramApp
{
static int size;
static int count;
static char[] arrChar = new char[100];

public static void main(String[] args) throws IOException


{
System.out.print("Enter a word: "); // get word
String input = getString();
size = input.length(); // find its size
count = 0;
for(int j=0; j<size; j++) // put it in array
{
arrChar[j] = input.charAt(j);
}
doAnagram(size); // anagram it
} // end main()

public static void doAnagram(int newSize)


{
27
int limit;
if(newSize == 1) // if too small,
{
return; // go no further
}

for(int j=0; j<newSize; j++) // for each position,


{
doAnagram(newSize-1); // anagram remaining
if(newSize==2) // if innermost,
{
displayWord(); // display it
}
rotate(newSize); // rotate word
}
}

// rotate left all chars from position to end


public static void rotate(int newSize)
{
int j;
int position = size - newSize;
char temp = arrChar[position]; // save first letter
for( j = position + 1; j < size; j++) // shift others left
{
arrChar[j-1] = arrChar[j];
}
arrChar[j-1] = temp; // put first on right
}

public static void displayWord()


{
if(count < 99)
{
System.out.print(" ");
}
if(count < 9)
{
System.out.print(" ");
}
System.out.print(++count + " ");
for(int j = 0; j < size; j++ )
{
System.out.print( arrChar[j] );
}
System.out.print(" ");
System.out.flush();
if( count % 6 == 0 )
28
{
System.out.println("");
}
}

public static String getString() throws IOException


{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
}

How to Debug Recursion you can hand walk you code, and/or using
System.out.println or some other method is usually helpful. Note
that where you place the System.out.println will make a big
difference in the data you receive back

public int Factorial(int intNumber)


{
int intAnswer;
if ( intNumber == 0 )
{
// System.out.println here will happen in call order
return 1;
// a System.out.println here won’t happen at all
}
else
{
// a System.out.println here will happen in call order
intAnswer = number * Factorial(number –1);
// a System.out.println here will happen in reverse order
return intAnswer;
}
}

Problems with Recursion


29
 Memory utilization from stack’s activation records

 Speed loss from repeated method calls that require state to be


saved and new activation records to be created o moved from the stack

 The recursive algorithm’s order may be worse than the non-recursive


version. In general a pretty good recursive algorithm is equal to its
iterative brethren. Howve4r, many recursive algorithms can end up one
order worse than their equivalent iterative solutions

When to use Recursion?

 When we are prototyping methods

 When speed or space do not matter much. Generally, when the


recursive solution is not considered much slower than the iterative
one or the data set is small enough

 When removing recursion results in an algorithm that is essentially


the same speed as the non-recursive version. Usually occurs if the
algorithms are the same order but the non-recursive version requires
a stack to implement it

 When coding time is extremely important

 When you can not figure out how to remove it

Removing Recursion
30
Removing Recursion In general recursion can be removed by a
combination of two means

 Iteration to replace calls with loops. In general a loop may need


a few more tests than the base case of the recursive algorithm

 A Stack to store / retrieve intermediate results as we progress in


the loop

Code Examples)

class ordArray
{
private long[] a; // ref to array a
private int nElems; // number of data items

public ordArray(int max) // constructor


{
a = new long[max]; // create array
nElems = 0;
}

public int size()


{
return nElems;
}

public int find(long searchKey)


{
return recFind(searchKey, 0, nElems-1);
}

private int recFind(long searchKey, int lowerBound,


int upperBound)
{
int curIn;

curIn = (lowerBound + upperBound ) / 2;


if( a[curIn] == searchKey )
{
return curIn; // found it
}
else if(lowerBound > upperBound)
{
return nElems; // can't find it
31
}
else // divide range
{
if( a[curIn] < searchKey ) // it's in upper half
{
return recFind(searchKey, curIn+1, upperBound);
}
else // it's in lower half
{
return recFind(searchKey, lowerBound, curIn-1);
}
} // end else divide range
} // end recFind()

public void insert(long value) // put element into array


{
int j;
for( j = 0; j < nElems; j++ ) // find where it goes
{
if(a[j] > value) // (linear search)
{
break;
}
}

for(int k = nElems; k > j; k-- ) // move bigger ones up


{
a[k] = a[k-1];
}
a[j] = value; // insert it
nElems++; // increment size
} // end insert()

public void display() // displays array contents


{
for(int j=0; j<nElems; j++) // for each element,
{
System.out.print(a[j] + " "); // display it
}
System.out.println("");
}

} // end class ordArray

////////////////////////////////////////////////////////////////
32
class BinarySearchApp
{
public static void main(String[] args)
{
int maxSize = 100; // array size
ordArray arr; // reference to array
arr = new ordArray(maxSize); // create the array

arr.insert(72); // insert items


arr.insert(90);
arr.insert(45);
arr.insert(126);
arr.insert(54);
arr.insert(99);
arr.insert(144);
arr.insert(27);
arr.insert(135);
arr.insert(81);
arr.insert(18);
arr.insert(108);
arr.insert(9);
arr.insert(117);
arr.insert(63);
arr.insert(36);

arr.display(); // display array

int searchKey = 27; // search for item

if( arr.find(searchKey) != arr.size() )


{
System.out.println("Found " + searchKey);
}
else
{
System.out.println("Can't find " + searchKey);
}
} // end main()
} // end class BinarySearchApp

Code Example)

private int Find(long searchKey)


33
{
int lowerBound = 0;
int upperBound = NElems -1;
int curIn;

while (true)
{
curIn = (lowerBound + upperBound ) / 2;

if( a[curIn] == searchKey )


{
return curIn; // found it
}
else
{
if( a[curIn] < searchKey ) // it's in upper half
{
lowerBound = curIn + 1;
}
else // it's in lower half
{
upperBound = curIn - 1;
} // end which way to go
} end if not found
} // end loop
} // end Find()

Code Example)

// stackTriangle.java
// evaluates triangular numbers, stack replaces recursion
// to run this program: C>java StackTriangleApp
import java.io.*; // for I/O

////////////////////////////////////////////////////////////////
class Params // parameters to save on stack
{
public int n;
public int returnAddress;

public Params(int nn, int ra)


{
n=nn;
returnAddress=ra;
}
} // end class Params
34

////////////////////////////////////////////////////////////////
class StackX
{
private int maxSize; // size of StackX array
private Params[] stackArray;
private int top; // top of stack

public StackX(int s) // constructor


{
maxSize = s; // set array size
stackArray = new Params[maxSize]; // create array
top = -1; // no items yet
}

public void push(Params p) // put item on top of stack


{
stackArray[++top] = p; // increment top, insert item
}

public Params pop() // take item from top of stack


{
return stackArray[top--]; // access item, decrement top
}

public Params peek() // peek at top of stack


{
return stackArray[top];
}
} // end class StackX

////////////////////////////////////////////////////////////////
class StackTriangleApp
{
static int theNumber;
static int theAnswer;
static StackX theStack;
static int codePart;
static Params theseParams;

public static void main(String[] args) throws IOException


{
System.out.print("Enter a number: ");
theNumber = getInt();
35
recTriangle();
System.out.println("Triangle="+theAnswer);
} // end main()

public static void recTriangle()


{
theStack = new StackX(10000);
codePart = 1;
while( step() == false) // call step() until it's true
{
} // null loop
}

public static boolean step()


{
switch(codePart)
{
case 1: // initial call
theseParams = new Params(theNumber, 6);
theStack.push(theseParams);
codePart = 2;
break;
case 2: // method entry
theseParams = theStack.peek();
if(theseParams.n == 1) // test
{
theAnswer = 1;
codePart = 5; // exit
}
else
{
codePart = 3; // recursive call
}
break;
case 3: // method call
Params newParams = new Params(theseParams.n - 1, 4);
theStack.push(newParams);
codePart = 2; // go enter method
break;
case 4: // calculation
theseParams = theStack.peek();
theAnswer = theAnswer + theseParams.n;
codePart = 5;
break;
case 5: // method exit
theseParams = theStack.peek();
codePart = theseParams.returnAddress; // (4 or 6)
theStack.pop();
36
break;
case 6: // return point
return true;
} // end switch
return false;
} // end triangle

public static String getString() throws IOException


{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}

public static int getInt() throws IOException


{
String s = getString();
return Integer.parseInt(s);
}
} // end class StackTriangleApp

Merge Sort

Divide and Conquer is an approach that tries to cut our data in half
at each step. If you can achieve a divide and conquer that is worth
O(log N) instead of O(N), so it is a worthwhile goal. We have
already seen binary search as our first divide and conquer algorithm.
Now lets look at Mergesort

Mergesort works by breaking the data down into sections of two and
then sorting each section. Then it works its way back up again
sorting sections of 4… then 8… etcetera, until there is only the
original section of data left.

 Not In Place mergesort uses extra memory to work with in the form o
the temporary array we write to before copying back to the original
array

 Evaluation O(N log N ) overall. We know that an algorithm that


divides data in half at each step is O(log N). So, the intial
descent of mergesort is O(log N). On the way back up, at each of
37
these levels it compares and swaps at most O(N) elements. Thus O(N
log N). Comparisons and swaps are both at most N at each level.
However, we also copy the temp array back to the main one after each
level so it is technically O(2N) swaps.

 Optimizations We can speed this process up some by removing


recursion. We can also embed the merge routine into the mergesort
routine. Finally we can play a trick by alternating which array we
consider the main one and not copying data back to the original until
we are finished sorting (if necessary)

Code Example)

// mergeSort.java
// demonstrates recursive merge sort
// to run this program: C>java MergeSortApp

////////////////////////////////////////////////////////////////
class DArray
{
private long[] theArray; // ref to array theArray
private int nElems; // number of data items

public DArray(int max) // constructor


{
theArray = new long[max]; // create array
nElems = 0;
}

public void insert(long value) // put element into array


{
theArray[nElems] = value; // insert it
nElems++; // increment size
}

public void display() // displays array contents


{
for(int j=0; j<nElems; j++) // for each element,
{
System.out.print(theArray[j] + " "); // display it
}
System.out.println("");
}
38
public void mergeSort() // called by main()
{ // provides workspace
long[] workSpace = new long[nElems];
recMergeSort(workSpace, 0, nElems-1);
}

private void recMergeSort(long[] workSpace, int lowerBound, int


upperBound)
{
if(lowerBound == upperBound) // if range is 1,
{
return; // no use sorting
}
else
{ // find midpoint
int mid = (lowerBound+upperBound) / 2;
// sort low half
recMergeSort(workSpace, lowerBound, mid);
// sort high half
recMergeSort(workSpace, mid+1, upperBound);
// merge them
merge(workSpace, lowerBound, mid+1, upperBound);
} // end else
} // end recMergeSort()

private void merge(long[] workSpace, int lowPtr, int highPtr, int


upperBound)
{
int j = 0; // workspace index
int lowerBound = lowPtr;
int mid = highPtr-1;
int n = upperBound-lowerBound+1; // # of items

while(lowPtr <= mid && highPtr <= upperBound)


{
if( theArray[lowPtr] < theArray[highPtr] )
{
workSpace[j++] = theArray[lowPtr++];
}
else
{
workSpace[j++] = theArray[highPtr++];
}

while(lowPtr <= mid)


{
workSpace[j++] = theArray[lowPtr++];
}
39

while(highPtr <= upperBound)


{
workSpace[j++] = theArray[highPtr++];
}

for(j = 0; j < n; j++)


{
theArray[lowerBound+j] = workSpace[j];
}
} // end merge()
} // end class DArray

////////////////////////////////////////////////////////////////
class MergeSortApp
{
public static void main(String[] args)
{
int maxSize = 100; // array size
DArray arr; // reference to array
arr = new DArray(maxSize); // create the array

arr.insert(64); // insert items


arr.insert(21);
arr.insert(33);
arr.insert(70);
arr.insert(12);
arr.insert(85);
arr.insert(44);
arr.insert(3);
arr.insert(99);
arr.insert(0);
arr.insert(108);
arr.insert(36);

arr.display(); // display items

arr.mergeSort(); // merge sort the array

arr.display(); // display items again


} // end main()
} // end class MergeSortApp

Chapter 7: Shell Sort, Partitioning, Quick Sort, Radix Sort


40

Shell Sort

Shell Sort works very much like insertion sort except for an
optimization in how we choose which values to sort. Essentially, we
sort on intervals instead of all at once. So we might sort all
elements separated by 5 giving sorts on: {0,5,10,…}, {1,6,11,…},
{2,7,12,…}, {3,8,13,…}, {4,9,14,…}. Then sort by 3s and then finally
by 1s. The idea is that if we mix up the numbers so that they are
approximately sorted, then we will save on our swaps and compares
because the numbers do not have to move very far

 Interval Sequence is quite important to an effective shell sort.


One suggested formula is to use h = 3*h + 1. So the intervals in
reverse order would be 1, 4, 13, 40, 121, 364, etc. The inverse of
this formula is h = (h – 1) / 3

 In place as new extra storage is needed

 Not in Order as the intervals should well and truly shake things up
with the mini-sorts moving all relatively similar values around
unpredictably

 Evaluation O(N log N2) overall, with worst cases of O(N2). The
outer loop sill needs loops summing N total iterations, composed of
the mini–interval loops. The inner loop is reduced on average
because less swapping and comparison is occurring since numbers are
roughly sorted already

 Optimizations As per insertion sort. Also the pattern of selecting


intervals is quite important. You may gain a minor speed up by
computing the intervals and then storing them in an array, but you
lose flexibility if you do so

Source Code)

// shellSort.java
41
// demonstrates shell sort
// to run this program: C>java ShellSortApp
//--------------------------------------------------------------
class ArraySh
{
private long[] theArray; // ref to array theArray
private int nElems; // number of data items

//--------------------------------------------------------------
public ArraySh(int max) // constructor
{
theArray = new long[max]; // create the array
nElems = 0; // no items yet
}

//--------------------------------------------------------------
public void insert(long value) // put element into array
{
theArray[nElems] = value; // insert it
nElems++; // increment size
}

//--------------------------------------------------------------
public void display() // displays array contents
{
System.out.print("A=");
for(int j=0; j<nElems; j++) // for each element,
{
System.out.print(theArray[j] + " "); // display it
}
System.out.println("");
}

//--------------------------------------------------------------
public void shellSort()
{
int inner, outer;
long temp;

int h = 1; // find initial value of h


while(h <= nElems/3)
{
h = h*3 + 1; // (1, 4, 13, 40, 121, ...)
}

while(h>0) // decreasing h, until h=1


{
// h-sort the file
42
for(outer=h; outer<nElems; outer++)
{
temp = theArray[outer];
inner = outer;

// one subpass (eg 0, 4, 8)


while(inner > h-1 && theArray[inner-h] >= temp)
{
theArray[inner] = theArray[inner-h];
inner -= h;
}
theArray[inner] = temp;
} // end for

h = (h-1) / 3; // decrease h
} // end while(h>0)
} // end shellSort()

} // end class ArraySh

////////////////////////////////////////////////////////////////
class ShellSortApp
{
public static void main(String[] args)
{
int maxSize = 10; // array size
ArraySh arr;
arr = new ArraySh(maxSize); // create the array

for(int j=0; j<maxSize; j++) // fill array with


{ // random numbers
long n = (int)(java.lang.Math.random()*99);
arr.insert(n);
}
arr.display(); // display unsorted array
arr.shellSort(); // shell sort the array
arr.display(); // display sorted array
} // end main()
} // end class ShellSortApp

Partitioning
43
Partitioning is an algorithm that quicksort will use to help sort its
data. The idea behind a partitioning to divide data into halves
based on some pivot value. Ideally, we end up with an array with
approximately half the data representing values less than the pivot
value, and the other half the values greater than the pivot. The
partition uses two pointers that pincher in from either end of the
data and swaps between them when the left pointer finds a value
greater than pivot and the right value finds one less than pivot. The
partition routine is complete when these two pointers meet

 Pivot choice is important. It must be fast O(1) and it had better


try to make sure that the data ends up approximately split in half on
average. If a pivot does not do both of these, then the quicksort
algorithm that uses it will tend toward O(N2) versus O(N log N). Here
are a few possibilities

Mid Point, choose the midpoint of the array and use the
value stored there as the pivot

Random, chose a random element for our pivot value

Average, there are numerous ways of taking an average, but


The easiest is sum the left value and right value and
divide by 2

 Not In Order pivot is not in order as the way the pincher moves in
word from both ends means that an early value that is swapped from eh
left will end up later in the later half of the array than the next
equal value on the left.

 In Place pivot swaps values inside the data structure only, so I in


place.

 Optimizations Embedding the swap routine comes right to mind to


eliminate a method call. Other than that, pivot itself is pretty
optimized, but it depends on the method of choosing a pivot we wish
44
to use. We can play tricks like using shift left, instead of divide
by two, if we want to choose a mid point for example. But that is a
relatively minimal speed up. Embedding a random number generator may
pay some more speed up in saving a method call. But perhaps not as
much as you think, given that the system routine is pre compiled
versus an interpreted routine that you would right

Source Code)

// assumes pivot value chosen already


public int partitionIt(int left, int right, long pivot)
{
int leftPtr = left - 1; // right of first elem
int rightPtr = right + 1; // left of pivot

while(true)
{
while( leftPtr < right && theArray[++leftPtr] < pivot )
{ // empty loop
}

while( rightPtr > left && theArray[--rightPtr] > pivot )


{ // empty loop
}

if( leftPtr >= rightPtr ) // if pointers cross,


{
break; // partition done
}
else // not crossed, so
{
swap(leftPtr, rightPtr); // swap elements
}
} // end while(true)

return leftPtr; // return partition


} // end partitionIt()

Quick Sort
45
Quick Sort is arguably the fastest general purpose sort. It uses
partition to divide up the data and then Quicksorts each of the two
resulting sections of data. Quicksort is a very sensitive algorithm
in that both very small changes to the code can cause it to break in
odd circumstances and in that the data order and the partition method
need to work well together

 In Place since Quicksort is essentially “loop” around the partition


routine, it is also in place.

 Not In Order as above, with the same reasoning as the partition


routine.

 Evaluation O(N log N) overall on average data. The inner loop


partition routine is O(N). And, we know that an algorithm that
divides data in half at each step is O(log N) which is the concept
behind divide and conquer. However, what if partition does not
divide the data in half, we can get O(N) for inverse sorted data,
etc. Hence, Quicksort can be a complex O(N2) sort if are data and
our partition are poorly matched. Also any of the partition
optimizations can have a positive effect as well

 Optimizations Quicksort has numerous possible optimizations. One


of the biggest is to remove the recursion and turn it into an
iterative algorithm. Another is to switch to insertion sort or some
other sort on small partition sizes. We can always embed code so as
to remove method calls as well.

Code Example)

// quickSort1.java
// demonstrates simple version of quick sort
// to run this program: C>java QuickSort1App
////////////////////////////////////////////////////////////////
class ArrayIns
{
private long[] theArray; // ref to array theArray
private int nElems; // number of data items

//--------------------------------------------------------------
46
public ArrayIns(int max) // constructor
{
theArray = new long[max]; // create the array
nElems = 0; // no items yet
}

//--------------------------------------------------------------
public void insert(long value) // put element into array
{
theArray[nElems] = value; // insert it
nElems++; // increment size
}

//--------------------------------------------------------------
public void display() // displays array contents
{
System.out.print("A=");
for(int j=0; j<nElems; j++) // for each element,
{
System.out.print(theArray[j] + " "); // display it
System.out.println("");
}
}

//--------------------------------------------------------------
public void quickSort()
{
recQuickSort(0, nElems-1);
}

//--------------------------------------------------------------
public void recQuickSort(int left, int right)
{
if(right-left <= 0) // if size <= 1,
{
return; // already sorted
}
else // size is 2 or larger
{
long pivot = theArray[right]; // rightmost item
}
// partition range
int partition = partitionIt(left, right, pivot);
recQuickSort(left, partition-1); // sort left side
recQuickSort(partition+1, right); // sort right side
}
} // end recQuickSort()
47
//--------------------------------------------------------------
public int partitionIt(int left, int right, long pivot)
{
int leftPtr = left - 1; // right of first elem
int rightPtr = right + 1; // left of pivot

while(true)
{
while( leftPtr < right && theArray[++leftPtr] < pivot )
{ // empty loop
}

while( rightPtr > left && theArray[--rightPtr] > pivot )


{ // empty loop
}

if( leftPtr >= rightPtr ) // if pointers cross,


{
break; // partition done
}
else // not crossed, so
{
swap(leftPtr, rightPtr); // swap elements
}
} // end while(true)

return leftPtr; // return partition


} // end partitionIt()

//--------------------------------------------------------------
public void swap(int dex1, int dex2) // swap two elements
{
long temp = theArray[dex1]; // A into temp
theArray[dex1] = theArray[dex2]; // B into A
theArray[dex2] = temp; // temp into B
} // end swap(

} // end class ArrayIns

////////////////////////////////////////////////////////////////
class QuickSort1App
{
public static void main(String[] args)
{
int maxSize = 16; // array size
ArrayIns arr;
48
arr = new ArrayIns(maxSize); // create array

for(int j=0; j<maxSize; j++) // fill array with


{ // random numbers
long n = (int)(java.lang.Math.random()*99);
arr.insert(n);
}
arr.display(); // display items
arr.quickSort(); // quicksort them
arr.display(); // display them again
} // end main()
} // end class QuickSort1App

Radix Sort

A Radix sort is unusual because it is doubly dependant upon its data


size to determine how it performs. A Radix sort works by sorting
each item based on one digit at a time. So the outer loop loops once
for each of the M maximum characters or digits in the data from back
to front. The inner loop goes through each N items sorting each item
by the appropriate digit. Please note that you have to go back to
front (or least to greatest) for a radix sort to work right

 Not in Place because it moves data into collections based on digits

 In Order the radix sort should also preserve the order of equal
values, but you have to be careful because you can code it otherwise

 Evaluation O(MN) overall. We know that the outer loop runs once
for each digit/character. If the M is equal to the N, then this is
an N squared algorithm. However, M is usually much less than N, so
much so that the algorithm can behave like O(N) for larger Ns with
non-unique keys. Comparisons and swaps are both at most N at each M
level

 Optimizations We can speed this process up by exchanging a linked


queue for fixed size array one. This will also increase memory
utilization by a factor of N however
49

Source Code)

/*
* @!RadixSortAlgorithm.java
* By: Alvin Raj
* Date: 12 August 2002
* Algorithm adapted from: C++ Data Structures, Nell Dale
* Additional Comments added by Jason Harrison <[email protected]>
*
* Algorithm comments from
* http://ciips.ee.uwa.edu.au/~morris/Year2/PLDS210/radixsort.html
*
*/

public class RadixSortAlgorithm extends SortAlgorithm


{

// for each of the 10 digits


private LinkedQueue[] Q =
{
new LinkedQueue(), //0
new LinkedQueue(), //1
new LinkedQueue(), //2
new LinkedQueue(), //3
new LinkedQueue(), //4
new LinkedQueue(), //5
new LinkedQueue(), //6
new LinkedQueue(), //7
new LinkedQueue(), //8
new LinkedQueue()
}; //9

// Assumes all positive integers


// numbDigits is the number of digits in the largest number
void sort(int a[], int numDigits) throws Exception
{
int arrayPos;
// i is the radix
for (int i = 1; i <= numDigits; i++)
{
if (stopRequested)
{
return;
}

arrayPos = 0;
// Put values into queues according to radix
50
// least significant digit first, then second,...
// first pass sorts on least significant digit
for (int j = 0; j < a.length; j++)
{
Q[getRadix(a[j],i)].enqueue(a[j]);
pause(-1,j); // JH
}

// Collect the queues and put them back into the array
// queues contain partially sorted lists after first
// pass -- moving to next significant digit will
// maintain this ordering.
for (int j = 0; j < Q.length; j++)
{
while(!Q[j].isEmpty())
{
a[arrayPos] = Q[j].dequeue();
arrayPos++;
}
pause(-1,arrayPos);
}
}
}
}

public class intNode extends Object


{
intNode(int a)
{
value = a;
next = null;
prev = null;
}

intNode()
{
next = null;
prev = null;
value = 0; //sets value to zero if no value is explicitly given
}

public int value;

public intNode next;


public intNode prev;
}
51

public class LinkedQueue extends Object


{
private intNode start;
private intNode end;

private int length; //number of elements

//Constructor
LinkedQueue()
{
start = null;
end = null;
length = 0;
}

//Add an item to the end of the queue


public void enqueue(int num)
{
length++; //increment the number of objects
//Create new node with given info
intNode temp = new intNode(num);

//If this is the first time, then do this


if (start == null)
{
start = temp; //make start = to this
end = start; //start and end point to the same node
}
else
{
//make the new node's next pointer
//point to the end
end.next = temp;
//make start point to the new object
end = temp;
}
temp = null;
}

//Dequeue -- returns the integer in the intNode


//From the front of the queue
//Assumes that the queue is not empty
public int dequeue()
{
length--;
int temp = start.value;
52
intNode tempNode;
tempNode = start;
start = start.next; //let start point to the next node
tempNode = null; //clear the first object
return temp;
}

//returns true if its empty


public boolean isEmpty()
{
return (length == 0);
}
}

A Radix Exchange Sort is the same order, but much faster because it
uses a bit level partitioning scheme to exchange values in the array.
In essence, the inner loop it looks at a given bit of the numbers
with two pointers moving towards the center exactly like pivot. Each
1 in the bottom half is marked to swap with each 0 from the top half.
When a swap occurs, the whole numbers are moved. The outer loop
simply partitions the two sections and then calls radix sort(lower
half), radix sort(upper half) just like quick sort did. Stpign case
is when we sort on the least significant bit.

 Bit Manipulation in Java uses several operators which you can


examine in detail here at
http://www.comp.lancs.ac.uk/computing/users/johnstlr/210/BitManip.htm
But, we just need the & operator to make our algorithm work. Two
ints with the & operator gives a result with 1s in only positions
where both operands have 1s. So we can use this to test if a number
has a one in a position as follows

int iTestBits[] = {1,2,4,8 }; // etc.


int iValue = 5;
int iResult = iTestBits[0] & iValue;
if ( iResult == iTestBits[0] )
{
// a 1 exists in the one’s position
}
iResult = iTestBits[1] & iValue;
if ( iResult == iTestBits[1] )
{
// a 1 exists in the two’s position
}
53

 Not in Order because it works like a pincher partitioning in from


both directions it is not necessarily in order

 In Place the radix exchange is in place because swaps occur in the


data itself

 Evaluation O(MN) overall for the same reasons as the basic radix
exchange. Here M is the bit depth of the numbers to sort which is
larger than the decimal radii, but everything is much, much faster
with no instantiations, in place, fewer method calls, etc

Source Code)

TBD In Class

Chapter 8: Binary Trees, Binary Search Trees, Huffman Code

Binary Trees
54

Binary Trees in all their forms present a very useful data structure
that allows O(log N) searches, insertions and deletions, while being
fully dynamic in nature. To discuss the tree structure we need to
understand the terminology associated with a tree

Tree Terminology

· Node is an item on the tree. Typically a node consists of data (or


a reference to data) and at least two pointers to possible children
(usually left and right)

· Root the original parent node of all other child nodes. Or the
parent node of a subtree

· Child a node that is referenced from another node. The parent is


the ancestor of the child

· Parent the node that a child node directly comes from, the
child(ren) is a descendant of the parent

· Leaf a node with no descendents

· Depth or Height of a tree is the maximum number of nodes that can


be traversed (in one direction) starting from the parent, 0 to n

· Level is the current number of nodes that have to be traversed to


reach a node. In general each level can hold up to 2depth nodes, where
depth starts at zero for the root
55
· Descendents the set of nodes that can trace a path back up to a
given node are its descendents

· Ancestors the set of nodes that lay between a node and the root

· Sub Tree a given node (root or otherwise) and all its descendents

· Binary Tree is a root node with up to two children. Each child can
have up to two children, etcetera

· Balanced Tree a tree with approximately the same depth for all of
its leaves. Maintains the “logarithmic” speed of the tree, without
this trees could turn into a linear access structure

· Full Binary Tree a tree with all possible nodes filled at each
level. Such a tree is considered balanced

· Complete Tree is a tree that is either full or only has missing


nodes from the bottom level. Such a tree is also considered balanced

Tree Operations There are several bog standard tree operations

· Find If there is no special order to the tree than this is an O(N)


process and possibly one with a somewhat expensive constant.
However, trees usually have some special properties that make
searching at least O(log N)
· Insert similar to search in that a generic tree insert might be
O(N), but they usually are O(log N) for any type of tree we would
use. We must create a new node and attach it to its new parent
56
· Delete ditto, but deleting a node involves more complexity. For
example, a node with children will involve re-arranging how they are
placed on the tree

· Traversing a tree for output is an O(N) process regardless of how a


tree is arranged or what type of tree it is. This is expected as a
tree with N nodes will require at least visiting N nodes. It may be
some constant of N as we may have to go through a node more than once
to get to all its children

Binary Search Trees

A Binary Search Tree is a binary tree with the additional property


that the left child (if any) of a node contains a value smaller than
the current nodes value. The right child of a node (if any) must
store only a greater value.
Because of this property a Binary Search Tree makes searching,
sorted insertion and deletion all O(log N) if the tree is near
balanced. Of course, an unbalanced tree approaches O(N), just like a
list

Binary Search Tree Operations

· Find use the binary search tree property (children to the left are
less / right are greater) to search for a node in at most O(log N)
time - in a balanced tree. If you end up at a node that does not
contain the value you are looking for, and has no applicable children
to traverse, then the tree does not contain the value

· Insert use the same approach as above to fall to the lowest level
of the tree and insert the new leaf node attached to this node

· Delete must ensure that the tree maintains its’ binary search tree
status. First we find a node as above. This algorithm can be
recursive or non-recursive, although non-recursive is actually not
any harder to code (and may not even need a stack). There are three
cases for deleting a node
57

A leaf node, simplest case where we set the parent’s node


link to null for that leaf

A node with two children, requires finding the in order


successor. It is this node that we want to use to replace
the node to delete. Such a node is found by going right
once and then left as far as possible. Note that upon
finding this node it may be necessary to also handle its’
right child as well by moving it up

A node with a single child, whether right or left, can be


deleted by simply replacing its’ parent’s link with its’
child link

· Tree Traversal means walking the tree or visiting each node in some
order. Note that you may well visit each node more than once, but
usually you will only ‘process’ each node once, such as is typically
done for output of the tree. There are three standard ways to walk a
tree

Pre Order print the current node. Then try to go left and
print if able. If not, try and go right and print if able.
Failing that back up a level and try those again until all
nodes have been visited at least once (but only
processed/printed once)

In Order is essentially viewing the nodes in sorted order.


Go left as far as possible and then print, go right if able
and repeat the above process. If you have to back up then
print if you have not already done so

Post Order go left as far as possible and then right as far


as possible. Print and then backup a level. As usual,
don’t reprint a node you have already visited
58

Code Example)

// tree.java
// demonstrates binary tree
// to run this program: C>java TreeApp
import java.io.*;
import java.util.*; // for Stack class

////////////////////////////////////////////////////////////////
class Node
{
public int iData; // data item (key)
public double dData; // data item
public Node leftChild; // this node's left child
public Node rightChild; // this node's right child

public void displayNode() // display ourself


{
System.out.print('{');
System.out.print(iData);
System.out.print(", ");
System.out.print(dData);
System.out.print("} ");
}
} // end class Node

////////////////////////////////////////////////////////////////
class Tree
{
private Node root; // first node of tree

// -------------------------------------------------------------
public Tree() // constructor
{
root = null; // no nodes in tree yet
}
// -------------------------------------------------------------
public Node find(int key) // find node with given key
{ // (assumes non-empty tree)
Node current = root; // start at root
while(current.iData != key) // while no match,
{
if(key < current.iData) // go left?
59
current = current.leftChild;
else // or go right?
current = current.rightChild;
if(current == null) // if no child,
return null; // didn't find it
}
return current; // found it
} // end find()

// -------------------------------------------------------------
public void insert(int id, double dd)
{
Node newNode = new Node(); // make new node
newNode.iData = id; // insert data
newNode.dData = dd;
if(root==null) // no node in root
root = newNode;
else // root occupied
{
Node current = root; // start at root
Node parent;
while(true) // (exits internally)
{
parent = current;
if(id < current.iData) // go left?
{
current = current.leftChild;
if(current == null) // if end of the line,
{ // insert on left
parent.leftChild = newNode;
return;
}
} // end if go left
else // or go right?
{
current = current.rightChild;
if(current == null) // if end of the line
{ // insert on right
parent.rightChild = newNode;
return;
}
} // end else go right
} // end while
} // end else not root
} // end insert()

// -------------------------------------------------------------
public boolean delete(int key) // delete node with given key
60
{ // (assumes non-empty list)
Node current = root;
Node parent = root;
boolean isLeftChild = true;

while(current.iData != key) // search for node


{
parent = current;
if(key < current.iData) // go left?
{
isLeftChild = true;
current = current.leftChild;
}
else // or go right?
{
isLeftChild = false;
current = current.rightChild;
}
if(current == null) // end of the line,
return false; // didn't find it
} // end while
// found node to delete

// if no children, simply delete it


if(current.leftChild==null &&
current.rightChild==null)
{
if(current == root) // if root,
root = null; // tree is empty
else if(isLeftChild)
parent.leftChild = null; // disconnect
else // from parent
parent.rightChild = null;
}

// if no right child, replace with left subtree


else if(current.rightChild==null)
if(current == root)
root = current.leftChild;
else if(isLeftChild)
parent.leftChild = current.leftChild;
else
parent.rightChild = current.leftChild;

// if no left child, replace with right subtree


else if(current.leftChild==null)
if(current == root)
root = current.rightChild;
61
else if(isLeftChild)
parent.leftChild = current.rightChild;
else
parent.rightChild = current.rightChild;

else // two children, so replace with inorder successor


{
// get successor of node to delete (current)
Node successor = getSuccessor(current);

// connect parent of current to successor instead


if(current == root)
root = successor;
else if(isLeftChild)
parent.leftChild = successor;
else
parent.rightChild = successor;

// connect successor to current's left child


successor.leftChild = current.leftChild;
} // end else two children
// (successor cannot have a left child)
return true; // success
} // end delete()

// -------------------------------------------------------------
// returns node with next-highest value after delNode
// goes to right child, then right child's left descendents
private Node getSuccessor(Node delNode)
{
Node successorParent = delNode;
Node successor = delNode;
Node current = delNode.rightChild; // go to right child
while(current != null) // until no more
{ // left children,
successorParent = successor;
successor = current;
current = current.leftChild; // go to left child
}
// if successor not
if(successor != delNode.rightChild) // right child,
{ // make connections
successorParent.leftChild = successor.rightChild;
successor.rightChild = delNode.rightChild;
}
return successor;
}
62
// -------------------------------------------------------------
public void traverse(int traverseType)
{
switch(traverseType)
{
case 1: System.out.print("\nPreorder traversal: ");
preOrder(root);
break;
case 2: System.out.print("\nInorder traversal: ");
inOrder(root);
break;
case 3: System.out.print("\nPostorder traversal: ");
postOrder(root);
break;
}
System.out.println();
}

// -------------------------------------------------------------
private void preOrder(Node localRoot)
{
if(localRoot != null)
{
System.out.print(localRoot.iData + " ");
preOrder(localRoot.leftChild);
preOrder(localRoot.rightChild);
}
}

// -------------------------------------------------------------
private void inOrder(Node localRoot)
{
if(localRoot != null)
{
inOrder(localRoot.leftChild);
System.out.print(localRoot.iData + " ");
inOrder(localRoot.rightChild);
}
}

// -------------------------------------------------------------
private void postOrder(Node localRoot)
{
if(localRoot != null)
{
postOrder(localRoot.leftChild);
postOrder(localRoot.rightChild);
63
System.out.print(localRoot.iData + " ");
}
}

// -------------------------------------------------------------
public void displayTree()
{
Stack globalStack = new Stack();
globalStack.push(root);
int nBlanks = 32;
boolean isRowEmpty = false;
System.out.println(
"......................................................");
while(isRowEmpty==false)
{
Stack localStack = new Stack();
isRowEmpty = true;

for(int j=0; j<nBlanks; j++)


System.out.print(' ');

while(globalStack.isEmpty()==false)
{
Node temp = (Node)globalStack.pop();
if(temp != null)
{
System.out.print(temp.iData);
localStack.push(temp.leftChild);
localStack.push(temp.rightChild);

if(temp.leftChild != null ||
temp.rightChild != null)
isRowEmpty = false;
}
else
{
System.out.print("--");
localStack.push(null);
localStack.push(null);
}
for(int j=0; j<nBlanks*2-2; j++)
System.out.print(' ');
} // end while globalStack not empty
System.out.println();
nBlanks /= 2;
while(localStack.isEmpty()==false)
globalStack.push( localStack.pop() );
} // end while isRowEmpty is false
64
System.out.println(
"......................................................");
} // end displayTree()

} // end class Tree

////////////////////////////////////////////////////////////////
class TreeApp
{
public static void main(String[] args) throws IOException
{
int value;
Tree theTree = new Tree();

theTree.insert(50, 1.5);
theTree.insert(25, 1.2);
theTree.insert(75, 1.7);
theTree.insert(12, 1.5);
theTree.insert(37, 1.2);
theTree.insert(43, 1.7);
theTree.insert(30, 1.5);
theTree.insert(33, 1.2);
theTree.insert(87, 1.7);
theTree.insert(93, 1.5);
theTree.insert(97, 1.5);

while(true)
{
System.out.print("Enter first letter of show, ");
System.out.print("insert, find, delete, or traverse: ");
int choice = getChar();
switch(choice)
{
case 's':
theTree.displayTree();
break;
case 'i':
System.out.print("Enter value to insert: ");
value = getInt();
theTree.insert(value, value + 0.9);
break;
case 'f':
System.out.print("Enter value to find: ");
value = getInt();
Node found = theTree.find(value);
if(found != null)
65
{
System.out.print("Found: ");
found.displayNode();
System.out.print("\n");
}
else
System.out.print("Could not find ");
System.out.print(value + '\n');
break;
case 'd':
System.out.print("Enter value to delete: ");
value = getInt();
boolean didDelete = theTree.delete(value);
if(didDelete)
System.out.print("Deleted " + value + '\n');
else
System.out.print("Could not delete ");
System.out.print(value + '\n');
break;
case 't':
System.out.print("Enter type 1, 2 or 3: ");
value = getInt();
theTree.traverse(value);
break;
default:
System.out.print("Invalid entry\n");
} // end switch
} // end while
} // end main()

// -------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}

// -------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
66
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}

} // end class TreeApp

Optimizing Binary Trees for the tree operations can take several
approaches, from removing recursion to implementing the tree with an
array

· Removing Recursion is one approach to speeding up some tree


operations. To do this you will need

Doubly Linked nodes so that you can traverse them backwards


without using a stack to store previous nodes

Incrementing Mark some int or long that marks if you have


been to a node before or not for a given tree operation.
To keep this from requiring a clear operation to reset this
mark, simply increment the mark value you will use for each
operation, and use that new value to check / mark the path
of your operation

· Saving Deleted Node whenever you need to inset anode, first check
the list of deleted nodes. If one does exist, then reuse it. This
saves two costs, first no garbage collection for a deleted node
occurs. Second, no instantiation of the new node occurs in this
case. Obviously this a fairly good affect on trees that are actively
being added to or deleted from

· Trees Using Arrays are another speedup. In this case we trade the
extra storage for a full tree. This means we possibly pay array
resizing costs as well. In turn, we can traverse or search the tree
very quickly compared to linked lists and may gain some cache
locality benefits as well

A node’s parent is found by taking its index and computing


67
( index – 1 ) / 2

A node’s children are 2 * index + 1 and 2 * index + 2

Huffman Code

A Huffman Code is a means of compressing data by coming up with a


binary tree representation of the characters where the most
frequently used characters are close to the root. Instead of using
the normal 8+ bit representation for characters we traverse the tree
with a left traverse being a 0 and a right traverse being a 1. In
this way we can build a new non-fixed length binary representation
for each character in a message

Constructing a Tree is a several step process

· Build the Character Frequency Array by counting each character in


the message. We want to place these characters into an array (or
list) of trees where the least frequently occurring character is
placed first. The trees will each have a parent node that is the
character in question

· Combine the Character Frequency Tree Array as follows. Take the


first two trees and combine them as children into a new tree with a
new parent. Place this tree back into the array in the position
where the total frequency of all leaf node’s characters would
dictate. Repeat this process until only a single tree remains

· Build the Message character by character using a tree search to


determine its’ encoding (left = 0, right = 1). Both sender and
receiver have to agree as to how the encoding works so the encoding
must be sent in some way as well
68

Chapter 9: Red-Black Trees

Red-Black Trees

Red-Black Trees in present a very useful data structure that allows


O(log N) searches, insertions and deletions regardless of how data is
entered. This is because they self balanced dynamically on insertion
and deletion of nodes.
69
Two basic types of trees exist. Those that insert top down and
change the tree data structure as they insert. And the somewhat less
efficient variety that first traverses to find the insert spot and
then propagates tree structure changes upward. Both types require
bottom up for deletion.

Key Red-Black Tree Concepts

· Each node in the tree is ‘colored’. There are a few rules about
this coloration process

1) Every node is colored red or black

2) The root is always black

3) If a node is red its children must be black. But the


converse does not have to be true

4) Every path from root to any actual (or possible) leaf


must contain the same number of black nodes (or black
height). Possible children are also known as null
children

5) Duplicate items are either not allowed (easy) or some


how caused to be distributed evenly along either child
branch of the first such value (harder)

· Default to a Red Insert, it is simpler and faster

· Color Change can sometimes be used to rebalance a tree. That is


changing the color of the nodes in the tree to match the above rules
· Rotation of nodes may occur in either direction. Note that we can
rotate any node and its sub tree, not just the head

1) A right rotate means the current node moves right and


its left child moves into its old position. Any inner
grandchild node also moves from its grandparent to be
connected to eh parent after the rotation

2) A left rotate is similar except the rotation is to the


left of course
70

Searching a Red-Black tree is just like searching a binary tree. We


do not have to pay attention to any special Re-Black Tree rules since
we are not touching the data

Simplified Pseudo Code Examples for deletes and inserts in a bottom


up manner. This insert approach requires another rule -- Null
children are counted as black.

T is the tree

p is parent

x is a the current node pointer

color() returns the color of a node or sets it if assigned


to

root() returns if the node is a root or not

left() returns the left most child of a node

right() ditto but the right most node

RotateLeft and RotateRight work as descried above

TreeInsert() uses a standard search to find where the node


should be inserted

Inserting into a Red-Black Tree is more complex than a search due to


the rules listed above

Example Code)

RedBlackInsert(T, x)
{
TreeInsert(T,x)
color(x) = Red
while( x != root(T) && color(p(x)) == Red )
{
if ( p(x) == left(p(p(x)) )
71
{
y <- right(p(p(x)))
if ( color(y) == Red )
{
color(p(x)) = Black
color(y) = Black
color(p(p(x))) = Red
x = p(p(x))
}
else
{
if ( x == right(p(x)) )
{
x = p(x)
RotateLeft(T,x)
}
color(p(x)) = Black
color(p(p(x))) = Red
RotateRight(T, p(p(x)))
}
}
else // p(x) != left(p(p(x))
{
// this is the same as above but swap right and left
}
} // end while

color(root(T)) = Black
}

Deleting from a Red-Black Tree is even more complex than inserting.

Example Code)
RedBlackDelete(T,z)
{
if ( left(z) == nil(T) || right(z) == nil(T) )
{
y = z
}
else
{
y = TreeSuccessor(z)
}

if ( left(y) != nil(T) )
72
{
x = left(y)
}
else
{
x = right(y)
}

p(x) = p(y)

if ( p(y) == nil(T) )
{
root(T) = x
}
else
{
if ( y == left(p(y)) )
{
left(p(x)) = x
}
else
{
right(p(y)) = x
}
}

if ( y != z )
{
key(z) = key(y)
} // note if y has other fields, copy them too

if ( color(y) == Black )
{
RBDeleteFixup(T,x)
}
}
RBDeleteFixup(T,x)
{
while ( x != root(T) && color(x) == Black )
{
if ( x == left(p(x)) )
{
w = right(p(x))
if ( color(w) = Black )
{
color(p(x)) = Red
RotateLeft(T,p(x))
w = right(p(x))
73
}
if ( color(left(w)) == Black && color (right(w)) == Black )
{
color(w) = Red
x = parent(x)
}
else
{
if ( color(right(w)) == Black )
{
color(left(w)) = Black
color(w) = Red
RotateRight(T,w)
w = right(p(x))
}
color(w) = color(p(x))
color(p(x)) = Black
color (right(w)) = Black
RotateLeft(T,p(x))
x = root(T)
}
}
else // x != left(p(x))
{
// same code as if portion, but switch right and left
} // end ifs about
} // end while

color(x) <--Black
}

You might also like