1
Chapter 10: 2-3-4 Trees, Storage and B-Trees
2-3-4 Trees
2-3-4 Trees are a slightly less efficient than red-black trees but a
whole lot easier to code and understand. As in all self-balanced
trees, they allow O(log N) searches, insertions and deletions
regardless of how data is entered.
2-3-4 Tree Concepts
· Each node has 2-4 links off of it. This means that there are 0-3
data items in a node. The number of links is referred to as the
order of the tree
· Data items in the node are stored in sorted order. Which means the
links may have to move as well depending on insertions or deletions
to the data in the node
· Links refer to children that are between the data items. End links
just compare to the nearest data item (lesser or greater). There
will always be one more link than the number of data items
· A Split refer to the process where a full node is broken into two
and a value is propagated up to its’ parent (or a new parent is
made). The details of the process are as follows
1) A new node is created that will be the sibling of the
node to be split
2) Move the last item into the new node
3) Item A is left as is
4) The rightmost two children are attached to the new node
5) Item B is moved up one level
2
Searching a 2-3-4 tree is just like searching a binary tree so long
as order is 2. Otherwise, we need to search each of the data items
in the node until we find one greater than the value we are looking
at or reach the end. If the former occurs, take the previous link.
Else, take the last link
Inserting into a 2-3-4 Tree can be fairly easy or hard, depending on
the condition of the nodes on the way to this node
1) If all the nodes on the path are not full, we just need
to traverse the tree and insert the data at the leaf
2) If the nodes on the way are full, we split the node and
continue until the leaf
3) if the leaf is full then we split that and move the
middle value up
Deleting, will not be covered
Example Code)
// demonstrates 234 tree
// to run this program: C>java Tree234App
import [Link].*;
////////////////////////////////////////////////////////////////
class DataItem
{
public long dData; // one data item
public DataItem(long dd) // constructor
{
dData = dd;
}
public void displayItem() // display item, format "/27"
{
[Link]("/"+dData);
}
} // end class DataItem
3
////////////////////////////////////////////////////////////////
class Node
{
private static final int ORDER = 4;
private int numItems;
private Node parent;
private Node childArray[] = new Node[ORDER];
private DataItem itemArray[] = new DataItem[ORDER-1];
// connect child to this node
public void connectChild(int childNum, Node child)
{
childArray[childNum] = child;
if(child != null)
{
[Link] = this;
}
}
// disconnect child from this node, return it
public Node disconnectChild(int childNum)
{
Node tempNode = childArray[childNum];
childArray[childNum] = null;
return tempNode;
}
public Node getChild(int childNum)
{
return childArray[childNum];
}
public Node getParent()
{
return parent;
}
public boolean isLeaf()
{
return ( childArray[0] == null) ? true : false;
}
public int getNumItems()
{
return numItems;
}
4
public DataItem getItem(int index) // get DataItem at index
{
return itemArray[index];
}
public boolean isFull()
{
return (numItems == ORDER - 1) ? true : false;
}
public int findItem(long key) // return index of
{ // item (within node)
for(int j=0; j<ORDER-1; j++) // if found,
{ // otherwise,
if( itemArray[j] == null ) // return -1
{
break;
}
else if( itemArray[j].dData == key )
{
return j;
}
}
return -1;
} // end findItem
public int insertItem(DataItem newItem)
{
// assumes node is not full
numItems++; // will add new item
long newKey = [Link]; // key of new item
for(int j=ORDER-2; j>=0; j--) // start on right,
{ // examine items
if(itemArray[j] == null) // if item null,
{
continue; // go left one cell
}
else // not null,
{ // get its key
long itsKey = itemArray[j].dData;
if(newKey < itsKey) // if it's bigger
{
itemArray[j+1] = itemArray[j]; // shift it right
}
else
{
itemArray[j+1] = newItem; // insert new item
5
return j+1; // return index to
} // new item
} // end else (not null)
} // end for // shifted all items,
itemArray[0] = newItem; // insert new item
return 0;
} // end insertItem()
public DataItem removeItem() // remove largest item
{
// assumes node not empty
DataItem temp = itemArray[numItems-1]; // save item
itemArray[numItems-1] = null; // disconnect it
numItems--; // one less item
return temp; // return item
}
public void displayNode() // format "/24/56/74/"
{
for(int j=0; j<numItems; j++)
itemArray[j].displayItem(); // "/56"
[Link]("/"); // final "/"
}
} // end class Node
class Tree234
{
private Node root = new Node(); // make root node
public int find(long key)
{
Node curNode = root;
int childNumber;
while(true)
{
if(( childNumber=[Link](key) ) != -1)
{
return childNumber; // found it
}
else if( [Link]() )
{
return -1; // can't find it
}
else // search deeper
6
{
curNode = getNextChild(curNode, key);
}
} // end while
}
// insert a DataItem
public void insert(long dValue)
{
Node curNode = root;
DataItem tempItem = new DataItem(dValue);
while(true)
{
if( [Link]() ) // if node full,
{
split(curNode); // split it
curNode = [Link](); // back up
// search once
curNode = getNextChild(curNode, dValue);
} // end if(node is full)
else if( [Link]() ) // if node is leaf,
{
break; // go insert
}
// node is not full, not a leaf; so go to lower level
else
{
curNode = getNextChild(curNode, dValue);
}
} // end while
[Link](tempItem); // insert new DataItem
} // end insert()
public void split(Node thisNode) // split the node
{
// assumes node is full
DataItem itemB, itemC;
Node parent, child2, child3;
int itemIndex;
itemC = [Link](); // remove items from
itemB = [Link](); // this node
child2 = [Link](2); // remove children
child3 = [Link](3); // from this node
7
Node newRight = new Node(); // make new node
if(thisNode==root) // if this is the root,
{
root = new Node(); // make new root
parent = root; // root is our parent
[Link](0, thisNode); // connect to parent
}
else // this node not the root
{
parent = [Link](); // get parent
}
// deal with parent
itemIndex = [Link](itemB); // item B to parent
int n = [Link](); // total items?
for(int j=n-1; j>itemIndex; j--) // move parent's
{ // connections
Node temp = [Link](j); // one child
[Link](j+1, temp); // to the right
}
// connect newRight to parent
[Link](itemIndex+1, newRight);
// deal with newRight
[Link](itemC); // item C to newRight
[Link](0, child2); // connect to 0 and 1
[Link](1, child3); // on newRight
} // end split()
// gets appropriate child of node during search for value
public Node getNextChild(Node theNode, long theValue)
{
int j;
// assumes node is not empty, not full, not a leaf
int numItems = [Link]();
for(j=0; j<numItems; j++) // for each item in node
{ // are we less?
if( theValue < [Link](j).dData )
{
return [Link](j); // return left child
}
} // end for // we're greater, so
return [Link](j); // return right child
}
8
public void displayTree()
{
recDisplayTree(root, 0, 0);
}
private void recDisplayTree(Node thisNode, int level,
int childNumber)
{
[Link]("level="+level+" child="+childNumber+" ");
[Link](); // display this node
// call ourselves for each child of this node
int numItems = [Link]();
for(int j=0; j<numItems+1; j++)
{
Node nextNode = [Link](j);
if(nextNode != null)
{
recDisplayTree(nextNode, level+1, j);
}
else
{
return;
}
}
} // end recDisplayTree()
} // end class Tree234
class Tree234App
{
public static void main(String[] args) throws IOException
{
long value;
Tree234 theTree = new Tree234();
[Link](50);
[Link](40);
[Link](60);
[Link](30);
[Link](70);
while(true)
{
[Link]("Enter first letter of ");
9
[Link]("show, insert, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
[Link]();
break;
case 'i':
[Link]("Enter value to insert: ");
value = getInt();
[Link](value);
break;
case 'f':
[Link]("Enter value to find: ");
value = getInt();
int found = [Link](value);
if(found != -1)
[Link]("Found "+value);
else
[Link]("Could not find "+value);
break;
default:
[Link]("Invalid entry\n");
} // end switch
} // end while
} // end main()
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader([Link]);
BufferedReader br = new BufferedReader(isr);
String s = [Link]();
return s;
}
public static char getChar() throws IOException
{
String s = getString();
return [Link](0);
}
public static int getInt() throws IOException
{
String s = getString();
return [Link](s);
}
} // end class Tree234App
10
Storage Basics
Hard Disks are come in several interfaces and formats. In any case
some of the basic descriptions are
Storage Capacity is measured in Gigabytes (1,000,000,000 bytes)
Bandwidth determines how fast data can be moved to or from storage.
It is measured in MB/Sec with both sustained and burst rates for read
and write. Be aware that your I/O subsystem needs to handle the
sustained amount, along with any other traffic, or you will have a
bottle neck
Access Time is in ms and consist of seek time (the head moving
across the platter), rotation latency (time it takes for the drive to
rotate to correct position) and Block Transfer Time (time to
read/write a block). In general higher RPMS, smaller platter size
and more numerous platters all make for faster access
Mean Time Between Failure (MBTF) usually the number of hours of
operation before a drive will fail (on avaerage). Look for at least
500,000+ hours.
Interface is the protocol that the drive uses to communicate with
the PC. In general SCSI is much better than IDE for servers because
it requires fewer CPU cycles to do its job
HD Terminology
Heads consists of the number of read/write ‘needles’ that can
access your drive. In general 2 per platter
Spindle what the drive platters spin on
11
Platter is a magnetically coated disk that resembles a record and
stores numerous 0s or 1s. May have multiple platters stacked on top
of one another in a disk (typically 20 GB a platter for IDE and 18GB
a platter for SCSI)
Tracks and Cylinder (multi-platter tracks) positional descriptor
assigned to each “ring” of a disk
Sector another positional descriptor of the disk. A pie shaped
pie slice of the disk that contains many sectors
Blocks are the combined position of sector and track numbers and
typically store 512 to 4096 Bytes each. Blocks are separated by
Inter Block Gaps which serve as “speed bumps” so that the
drive knows where blocks begin and end. Blocks can be
combined into contiguous, logically addressable units
called clusters
Hardware Address consists of block, sector and track numbers
12
Efficient Use of the Hard Drive involves filling each block as full
as possible, and reading as many blocks in order on a track as
possible. I.e. seek and rotation latency are used once, then read
all the blocks you need. Additionally files placed near the edge of
a drive tend to be accessed faster. In essence this is what a disk
defragmenter does for you
Why does it matter? Hard drive performance is measured in
milliseconds (ms) while your computer processes information in
nanoseconds (ns). So, hard drives are usually 1000’s of times slower
than your CPU. Hence any speedup in hard drive access yields a
serious speedup in machine performance. Which leads us to B-Trees and
Merge Sorts.
13
B-Trees
B-Trees are a slightly less efficient than top down 2-34 trees or
red-black trees, but easier to code of course. Like all the balanced
trees we have seen, they are O(log N) for searches, insertions and
deletions
B-Tree Concepts
· A B-Tree is ordered like the others. Typically we see an order
that ends up filling the block size of the hard drive
· The simple B-Trees are bottom up for insertion. And, for our
purposes this means doubly linked pointers so we can backtrack up the
tree
· There are more advanced versions called B+ trees, but these will
not be covered here
· A Split refer to the process where a full node is broken into two
and a value is propagated up to its’ parent(s) (or a new parent is
made). The details of the process are as follows
1) We split the node into two with half the ordered data
staying in palce and the other half – 1 going to a new
child node
2) The half + 1 item ends up moving up the tree and is
inserted into the parent
3) If the parent is full we split again on the parent, etc
14
· A Merge refers to the process where two children are brought up to
the parent’s level. This is occurs when the current node + both
children’s node’s have fewer than values then a node can hold. In
this case we test for this after a deletion
1) Read in values and pointers from each child and insert
them into the current node
2) Delete both children
3) Strictly speaking we might need to rotate the tree at
this point, but we will leave that as an exercise for
the readers imagination
Searching a B-Tree is just like searching a 2-3-4 tree
while not found and not leaf do
traverse tree using value to search
if value in node found
return true
return false
Inserting into a B-tree means going to the root (bottom up)
while not leaf do
traverse tree using value to insert
if leaf not full
insert
else
split leaf as above
15
Deleting this is a simple delete routine for a B-Tree. There are
better, but it will work for us
while value not found and not leaf do
traverse tree using value to delete
if value in node not found
return
if node is a leaf
remove value
if no values left
delete node
else
if number of values in node and children is > values node can store
return
else
merge
16
Chapter 11: Hash, Chaining, Special Hashes
Hash Tables
A Hash is an algorithm that uses a calculation to inset and then to
find the location of a piece of data in (ideally) one step. This
makes hashes quite fast. However, a hash table is usually fixed in
size (R items) and needs about 50% more storage than the number of
hashed keys (M) it will hold. A good choice is to pick M as a power
of two, or alternatively as a prime number
Hash Table is the base structure that holds the data. A hash table
typically about 50% larger than the number of values it could hold.
However since this is usually implemented as an array of references
the actual excess memory consumption is less than expected
Hash Slot/Bucket a slot in the hash table. Typically hold a link
to an element of data, but can also hold the data itself in some
algorithms. Buckets n eh same slot can be chained together as a
linked list, a tree, etc.
Hash Key is the variable that is used in the hash function to
determine which bucket an item should fall into
Hash Function is a consistent formula that will map between the
received key variable and a specific slot/bucket. It should ideally
uniformly distribute data. This can be difficult to accomplish if
you do no tknow much about your data to begin with
// hash example
int hashtable[8] = {0, 0, 0, 0, 0, 0, 0, 0};
slot = intInsertMe % 8;
hashtable[slot] = intInsertMe;
17
Collisions occur when a second value hashes to the same bucket.
Assuming a basic hash algorithm, we can deal with this in one of
three ways.
Wrap the data around to the next bucket. This is also
known as linear probing and is generally considered
inefficient
Re-hash with another function. Possible, but may still
cause performance issues
Chain another bucket off of the current one usually in
some sorted order. As mentioned above this chaining can be
of any data structure. This is typically the best solution
Hash Benefits and Limitations a hash is ideally an O(1) data
structure for searches, inserts and deletions. So what are the
issues?
Collisions can turn this into an O(N) or O(log N) algorithm
depending on how chaining is implemented.
The extra memory utilization with expensive operations to resize,
just as in arrays.
Usually no easy quick way to sequentially access values in sorted
order. So hashes are almost always considered an un-sorted data
structure
18
Chaining
Chaining means adding buckets off of our hash slots. This can be
done via nay data structure you wish. From an array to a tree of
whatever sort you wish. In general, we are assuming that our depth
of chaining is not much, or else our hash is in trouble anyway.
Therefore, we tend to implement nothing more advanced than a binary
tree to support out chaining
Code Example)
// [Link]
// demonstrates hash table with separate chaining
// to run this program: C:>java HashChainApp
import [Link].*;
////////////////////////////////////////////////////////////////
class Link
{ // (could be other items)
private int iData; // data item
public Link next; // next link in list
public Link(int it) // constructor
{
iData = it;
}
public int getKey()
{
return iData;
}
public void displayLink() // display this link
{
[Link](iData + " ");
}
} // end class Link
class SortedList
{
private Link first; // ref to first list item
public void SortedList() // constructor
{
first = null;
19
}
public void insert(Link theLink) // insert link, in order
{
int key = [Link]();
Link previous = null; // start at first
Link current = first;
// until end of list,
while( current != null && key > [Link]() )
{ // or current > key,
previous = current;
current = [Link]; // go to next item
}
if(previous==null) // if beginning of list,
{
first = theLink; // first --> new link
}
else // not at beginning,
{
[Link] = theLink; // prev --> new link
}
[Link] = current; // new link --> current
} // end insert()
public void delete(int key) // delete link
{ // (assumes non-empty list)
Link previous = null; // start at first
Link current = first;
// until end of list,
while( current != null && key != [Link]() )
{ // or key == current,
previous = current;
current = [Link]; // go to next link
}
// disconnect link
if(previous==null) // if beginning of list
{
first = [Link]; // delete first link
}
else // not at beginning
{
[Link] = [Link]; // delete current link
}
} // end delete()
20
public Link find(int key) // find link
{
Link current = first; // start at first
// until end of list,
while(current != null && [Link]() <= key)
{ // or key too small,
if([Link]() == key) // is this the link?
{
return current; // found it, return link
}
current = [Link]; // go to next item
}
return null; // didn't find it
} // end find()
public void displayList()
{
[Link]("List (first-->last): ");
Link current = first; // start at beginning of list
while(current != null) // until end of list,
{
[Link](); // print data
current = [Link]; // move to next link
}
[Link]("");
}
} // end class SortedList
class HashTable
{
private SortedList[] hashArray; // array of lists
private int arraySize;
public HashTable(int size) // constructor
{
arraySize = size;
hashArray = new SortedList[arraySize]; // create array
for(int j=0; j<arraySize; j++) // fill array
{
hashArray[j] = new SortedList(); // with lists
}
}
21
public void displayTable()
{
for(int j=0; j<arraySize; j++) // for each cell,
{
[Link](j + ". "); // display cell number
hashArray[j].displayList(); // display list
}
}
public int hashFunc(int key) // hash function
{
return key % arraySize;
}
public void insert(Link theLink) // insert a link
{
int key = [Link]();
int hashVal = hashFunc(key); // hash the key
hashArray[hashVal].insert(theLink); // insert at hashVal
} // end insert()
public void delete(int key) // delete a link
{
int hashVal = hashFunc(key); // hash the key
hashArray[hashVal].delete(key); // delete link
} // end delete()
public Link find(int key) // find link
{
int hashVal = hashFunc(key); // hash the key
Link theLink = hashArray[hashVal].find(key); // get link
return theLink; // return link
}
} // end class HashTable
class HashChainApp
{
public static void main(String[] args) throws IOException
{
int aKey;
Link aDataItem;
int size, n, keysPerCell = 100;
22
// get sizes
[Link]("Enter size of hash table: ");
size = getInt();
[Link]("Enter initial number of items: ");
n = getInt();
// make table
HashTable theHashTable = new HashTable(size);
for(int j=0; j<n; j++) // insert data
{
aKey = (int)([Link]() *
keysPerCell * size);
aDataItem = new Link(aKey);
[Link](aDataItem);
}
while(true) // interact with user
{
[Link]("Enter first letter of ");
[Link]("show, insert, delete, or find: ");
char choice = getChar();
switch(choice)
{
case 's':
[Link]();
break;
case 'i':
[Link]("Enter key value to insert: ");
aKey = getInt();
aDataItem = new Link(aKey);
[Link](aDataItem);
break;
case 'd':
[Link]("Enter key value to delete: ");
aKey = getInt();
[Link](aKey);
break;
case 'f':
[Link]("Enter key value to find: ");
aKey = getInt();
aDataItem = [Link](aKey);
if(aDataItem != null)
{
[Link]("Found " + aKey);
}
else
{
[Link]("Could not find " + aKey);
}
23
break;
default:
[Link]("Invalid entry\n");
} // end switch
} // end while
} // end main()
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader([Link]);
BufferedReader br = new BufferedReader(isr);
String s = [Link]();
return s;
}
public static char getChar() throws IOException
{
String s = getString();
return [Link](0);
}
public static int getInt() throws IOException
{
String s = getString();
return [Link](s);
}
} // end class HashChainApp
Special Hashes
Special Hashes are typically used for hard drive access, but can also
be adopted to in memory hashing for
Extensible Hashing is a modification of the general hash scheme and
can expand and contract its’ table size via a directory that uses the
first d bits (local depth) of the hashed key to determine which
bucket to use. We start with 1 bit and expand from there when we
have an overflow (buckets may have N items in them). Flexible in
memory performance, but can suffer from odd sequences of data causing
frequent expansions
24
The directory needs to be accessed before we go to the appropriate
bucket. Since the directory is always 2d bits long we can always
find the right bucket in O(1) time
When a bucket overflow occurs, the directory gains one bit in depth
which causes a re-organization that is somewhat expensive. Note that
only the bucket that over flowed gets split into two (or more) new
buckets, all other buckets stay the same
Buckets can be merged as well if data continues to shrink. However
this is not that common of an operation – perhaps run once in a while
just to check if directory can be shrunk
25
Linear Hashing is another approach to dynamically sizing the hash
table, but this time without the need for a directory. When the
first overflow occurs in any bucket, the 0th bucket is split in two
with half of its’ data remaining and half being placed in the new
last bucket M + 1 (regardless of where the overflows occurred). This
process repeats until there are 2M buckets.
Multiple Hash Functions are needed because we will have buckets
that are “more split” than other buckets as the number of total
buckets grows toward 2M. Note that this process repeats with new
hash functions when we pass 2M
26
Chapter 12: Heap, Heap Sort
Heap
A Heap is a binary tree where in each parent has the greatest
possible value of the sub tree of consisting of it’s descendents. So
the root will always have the greatest value.
Heaps support fixed O(log N) insertion and removal time because a
heap can maintain a balanced tree state, regardless of data
inserted / deleted
Heaps are the preferred method of creating a priority queue because
they maintain O(log N) performance regardless of data
Heaps can be used to sort an un-sorted array in O(N log N) time
regardless of data
Heaps are usually implemented as an array for speed, but can be
based on linked lists
Heap Properties
Each node of a heap must satisfy the following condition. All its
descendents are greater than it. This is also referred to as weakly
ordered as it is less rigid than a binary search tree
A heap is always a complete “tree.” As a matter of fact only the
end leaves of a given level may be missing
27
Heap Operations most heap operations are fairly trivial, except for
enqueue and dequeue. Note that we typically avoid fully swapping
elements when we percolate, instead we simply shuffle each node
around and then insert the final value when it reaches the
appropriate level
Enqueue adds an element to the first empty leaf node and then runs
a percolate up. This operation swaps the element higher and higher
in the tree until it is either the root, or until the next ancestor
node has a higher value than it does
Dequeue removes the leading element from the heap and replaces it
with the last child’s value and then percolates that value downward.
In this way the highest value will swap up into the root and lower
values will move down the tree
Code Example)
// [Link]
// demonstrates heaps
// to run this program: C>java HeapApp
import [Link].*;
////////////////////////////////////////////////////////////////
class Node
{
private int iData; // data item (key)
public Node(int key) // constructor
{
iData = key;
}
public int getKey()
{
return iData;
}
public void setKey(int id)
{
iData = id;
}
} // end class Node
28
class Heap
{
private Node[] heapArray;
private int maxSize; // size of array
private int currentSize; // number of nodes in array
public Heap(int mx) // constructor
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize]; // create array
}
public boolean isEmpty()
{
return currentSize==0;
}
public boolean insert(int key)
{
if(currentSize==maxSize)
{
return false;
}
Node newNode = new Node(key);
heapArray[currentSize] = newNode;
trickleUp(currentSize++);
return true;
} // end insert()
public void trickleUp(int index)
{
int parent = (index-1) / 2;
Node bottom = heapArray[index];
while( index > 0 && heapArray[parent].getKey() <
[Link]() )
{
heapArray[index] = heapArray[parent]; // move it down
index = parent;
parent = (parent-1) / 2;
} // end while
heapArray[index] = bottom;
} // end trickleUp()
29
public Node remove() // delete item with max key
{ // (assumes non-empty list)
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
} // end remove()
public void trickleDown(int index)
{
int largerChild;
Node top = heapArray[index]; // save root
while(index < currentSize/2) // while node has at
{ // least one child,
int leftChild = 2*index+1;
int rightChild = leftChild+1;
// find larger child
if ( rightChild < currentSize &&
heapArray[leftChild].getKey() <
heapArray[rightChild].getKey())
{
largerChild = rightChild;
}
else
{
largerChild = leftChild;
} // top >= largerChild?
if( [Link]() >= heapArray[largerChild].getKey() )
{
break;
} // shift child up
heapArray[index] = heapArray[largerChild];
index = largerChild; // go down
} // end while
heapArray[index] = top; // root to index
} // end trickleDown()
public boolean change(int index, int newValue)
{
if(index<0 || index>=currentSize)
{
return false;
}
int oldValue = heapArray[index].getKey(); // remember old
heapArray[index].setKey(newValue); // change to new
if(oldValue < newValue) // if raised,
{
30
trickleUp(index); // trickle it up
}
else // if lowered,
{
trickleDown(index); // trickle it down
}
return true;
} // end change()
public void displayHeap()
{
[Link]("heapArray: "); // array format
for(int m = 0; m < currentSize; m++)
{
if(heapArray[m] != null)
{
[Link]( heapArray[m].getKey() + " ");
}
else
{
[Link]( "-- ");
}
}
[Link]();
// heap format
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0; // current item
String dots = "...............................";
[Link](dots+dots); // dotted top line
while(currentSize > 0) // for each heap item
{
if(column == 0) // first item in row?
{
for(int k=0; k<nBlanks; k++) // preceding blanks
{
[Link](' ');
} // display item
}
[Link](heapArray[j].getKey());
if(++j == currentSize) // done?
{
break;
}
31
if(++column==itemsPerRow) // end of row?
{
nBlanks /= 2; // half the blanks
itemsPerRow *= 2; // twice the items
column = 0; // start over on
[Link](); // new row
}
else // next item on row
{
for(int k=0; k<nBlanks*2-2; k++)
{
[Link](' '); // interim blanks
}
}
} // end for
[Link]("\n"+dots+dots); // dotted bottom line
} // end displayHeap()
} // end class Heap
class HeapApp
{
public static void main(String[] args) throws IOException
{
int value, value2;
Heap theHeap = new Heap(31); // make a Heap; max size 31
boolean success;
[Link](70); // insert 10 items
[Link](40);
[Link](50);
[Link](20);
[Link](60);
[Link](100);
[Link](80);
[Link](30);
[Link](10);
[Link](90);
while(true) // until [Ctrl]-[C]
{
[Link]("Enter first letter of ");
[Link]("show, insert, remove, change: ");
int choice = getChar();
switch(choice)
{
32
case 's': // show
[Link]();
break;
case 'i': // insert
[Link]("Enter value to insert: ");
value = getInt();
success = [Link](value);
if( !success )
[Link]("Can't insert; heap full");
break;
case 'r': // remove
if( ![Link]() )
[Link]();
else
[Link]("Can't remove; heap empty");
break;
case 'c': // change
[Link]("Enter current index of item: ");
value = getInt();
[Link]("Enter new key: ");
value2 = getInt();
success = [Link](value, value2);
if( !success )
[Link]("Invalid index");
break;
default:
[Link]("Invalid entry\n");
} // end switch
} // end while
} // end main()
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader([Link]);
BufferedReader br = new BufferedReader(isr);
String s = [Link]();
return s;
}
public static char getChar() throws IOException
{
String s = getString();
return [Link](0);
}
public static int getInt() throws IOException
{
String s = getString();
33
return [Link](s);
}
} // end class HeapApp
Tree Based Heaps are the same order as there array based brethren.
However they are usually harder to navigate. One trick is to
Store the number of the node as it is created. Then you can use the
binary representation of that node to determine the path you need to
take (left or right) from the root to get back to that node. Another
is to doubly link your nodes
Heap Sort first converts data into a heap and then removes items on
at a time. The conversion process can be accomplished in O(N log N)
time and we already know an item can be removed in O(log N) time so
removing all items sorted takes O(N log N) as well
Code Example)
// [Link]
// demonstrates heap sort
// to run this program: C>java HeapSortApp
import [Link].*;
class Node
{
private int iData; // data item (key)
public Node(int key) // constructor
{
iData = key;
}
public int getKey()
{
return iData;
}
} // end class Node
class Heap
34
{
private Node[] heapArray;
private int maxSize; // size of array
private int currentSize; // number of items in array
public Heap(int mx) // constructor
{
maxSize = mx;
currentSize = 0;
heapArray = new Node[maxSize];
}
public Node remove() // delete item with max key
{ // (assumes non-empty list)
Node root = heapArray[0];
heapArray[0] = heapArray[--currentSize];
trickleDown(0);
return root;
} // end remove()
public void trickleDown(int index)
{
int largerChild;
Node top = heapArray[index]; // save root
while(index < currentSize/2) // not on bottom row
{
int leftChild = 2*index+1;
int rightChild = leftChild+1;
// find larger child
if(rightChild < currentSize &&
heapArray[leftChild].getKey() <
heapArray[rightChild].getKey())
{
largerChild = rightChild;
}
else
{
largerChild = leftChild;
} // top >= largerChild?
if([Link]() >= heapArray[largerChild].getKey())
{
break;
} // shift child up
heapArray[index] = heapArray[largerChild];
index = largerChild; // go down
} // end while
heapArray[index] = top; // root to index
} // end trickleDown()
35
public void displayHeap()
{
int nBlanks = 32;
int itemsPerRow = 1;
int column = 0;
int j = 0; // current item
String dots = "...............................";
[Link](dots+dots); // dotted top line
while(currentSize > 0) // for each heap item
{
if(column == 0) // first item in row?
{
for(int k=0; k<nBlanks; k++) // preceding blanks
{
[Link](' ');
}
} // display item
[Link](heapArray[j].getKey());
if(++j == currentSize) // done?
{
break;
}
if(++column==itemsPerRow) // end of row?
{
nBlanks /= 2; // half the blanks
itemsPerRow *= 2; // twice the items
column = 0; // start over on
[Link](); // new row
}
else
{ // next item on row
for(int k=0; k<nBlanks*2-2; k++)
{
[Link](' '); // interim blanks
}
}
} // end for
[Link]("\n"+dots+dots); // dotted bottom line
} // end displayHeap()
36
public void displayArray()
{
for(int j=0; j<maxSize; j++)
{
[Link](heapArray[j].getKey() + " ");
}
[Link]("");
}
public void insertAt(int index, Node newNode)
{
heapArray[index] = newNode;
}
public void incrementSize()
{
currentSize++;
}
} // end class Heap
class HeapSortApp
{
public static void main(String[] args) throws IOException
{
int size, j;
[Link]("Enter number of items: ");
size = getInt();
Heap theHeap = new Heap(size);
for(j=0; j<size; j++) // fill array with
{ // random nodes
int random = (int)([Link]()*100);
Node newNode = new Node(random);
[Link](j, newNode);
[Link]();
}
[Link]("Random: ");
[Link](); // display random array
for(j=size/2-1; j>=0; j--) // make random array into heap
{
[Link](j);
}
37
[Link]("Heap: ");
[Link](); // dislay heap array
[Link](); // display heap
for(j=size-1; j>=0; j--) // remove from heap and
{ // store at array end
Node biggestNode = [Link]();
[Link](j, biggestNode);
}
[Link]("Sorted: ");
[Link](); // display sorted array
} // end main()
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader([Link]);
BufferedReader br = new BufferedReader(isr);
String s = [Link]();
return s;
}
public static int getInt() throws IOException
{
String s = getString();
return [Link](s);
}
} // end class HeapSortApp
38
Chapter 13: Graphs, Searches, Spanning Trees, Sorting
Graphs
A graph consists of nodes and edges that connect nodes. It is very
much like a tree except that it does not need to be limited to a
hierarchical form. Nodes can have edges that loop back around, and
can even allow travel in along edges in both directions (or only one
direction if you so wish)
Vertex a node in the graph. In many cases we also track whether
that node has been visited or not yet for a given algorithm with a
Boolean
Edge (Arc) a path connecting two nodes together
Undirected Graph a graph in which edges can be traversed in wither
direction
Directed Graph (Digraph) a graph in which edges can be traversed in
wither direction
Acyclic a graph that has NO loops in it where I can start at any
node and end up at the same node by following any combination of
edges
Cyclic a graph that has at least one way of looping
Weight (Weighted Edge) A value given to an edge to make it
more/less easily traveled in relation to other edges
Adjacent Vertices are vertices connected by an edge
39
Path A series of edges that connect any two vertices
Shortest Path the least weight (fewest edges) between any two
vertices
Minimal Spanning Tree the series of edges with least total weight
(or fewest total edges) that connect every vertex in the graph
Keeping Track of Vertices and Edges is typically done with either an
adjacency matrix or and adjacency graph. They both have pros and
cons depending on what your requirements are
An Adjacency Matrix is an N by N matrix showing which N vertices
are connected to which of N vertices. Each entry in the graph is
either a 1 or 0, true or false, or a weight for a weighted graph.
Note that the vertices serve as indices into the two dimensional
graph
Benefits include generally faster access for both looking
up and modifying edge data
Drawbacks include N2 memory usage and lack of inexpensive
Dynamic growth should you wish to change your graph on
the fly
Example)
A B C
A - 1 1
B 1 - -
C - 1 -
An Adjacency List is an N array (or list) with chaining nodes that
represent the vertices that can be reached from a given vertex
40
Benefits include N memory usage and possibility of being
fully dynamic.
Drawbacks generally slower access for both looking
up and modifying edge data. Even putting a tree on the
links will be likely not help too much unless the graph is
fairly big and close to fully connected
Example)
A ->B->C
B ->A
C ->B
Vertices are typically composed of a name and some visited
Boolean value that indicates if we have seen this node before
Example Code)
class Vertex
{
public char label;
public boolean wasVisited;
public Vertex(char cLabel)
{
label = cLabel;
wasVisited = false;
}
}
Searches
41
Searches are typically done in one of two ways. Breadth first
search, or depth first search. They both end up visiting all
vertices of a graph, but the order in which they do so is
dramatically different
A Depth First Search (DFS) uses a stack to roam the graph. By
using stack we ensure that the most recent additions are the first
ones to be dealt with thus the most recently pushed vertex will be
the first to be processed by a DFS. It is an O(N) algorithm that
generally works as follows
1) Prime the pump by pushing the starting node (pick one)
onto the stack
2) While the stack is not empty pop a node
3) mark it visited
4) push its children on the stack if they are not visited
Code Example)
// [Link]
// demonstrates depth-first search
// to run this program: C>java DFSApp
////////////////////////////////////////////////////////////////
class StackX
{
private final int SIZE = 20;
private int[] st;
private int top;
public StackX() // constructor
{
st = new int[SIZE]; // make array
top = -1;
}
public void push(int j) // put item on stack
{
st[++top] = j;
}
public int pop() // take item off stack
{
return st[top--];
42
}
public int peek() // peek at top of stack
{
return st[top];
}
public boolean isEmpty() // true if nothing on stack
{
return (top == -1);
}
} // end class StackX
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private StackX theStack;
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int y=0; y<MAX_VERTS; y++) // set adjacency
{
for(int x=0; x<MAX_VERTS; x++) // matrix to 0
{
43
adjMat[x][y] = 0;
}
}
theStack = new StackX();
} // end constructor
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
public void displayVertex(int v)
{
[Link](vertexList[v].label);
}
public void dfs() // depth-first search
{ // begin at vertex 0
vertexList[0].wasVisited = true; // mark it
displayVertex(0); // display it
[Link](0); // push it
while( ![Link]() ) // until stack empty,
{
// get an unvisited vertex adjacent to stack top
int v = getAdjUnvisitedVertex( [Link]() );
if(v == -1) // if no such vertex,
{
[Link]();
}
else // if it exists,
{
vertexList[v].wasVisited = true; // mark it
displayVertex(v); // display it
[Link](v); // push it
}
} // end while
// stack is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
{
vertexList[j].wasVisited = false;
44
}
} // end dfs
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
{
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
{
return j;
}
}
return -1;
} // end getAdjUnvisitedVertex()
} // end class Graph
class DFSApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
[Link]('A'); // 0 (start for dfs)
[Link]('B'); // 1
[Link]('C'); // 2
[Link]('D'); // 3
[Link]('E'); // 4
[Link](0, 1); // AB
[Link](1, 2); // BC
[Link](0, 3); // AD
[Link](3, 4); // DE
[Link]("Visits: ");
[Link](); // depth-first search
[Link]();
} // end main()
} // end class DFSApp
A DBreadth First Search (BFS) uses a queue to roam the graph. By
using a queue we ensure that the most recent additions are the first
ones to be dealt with thus the most recently pushed vertex will be
45
the first to be processed by a DFS. It is an O(N) algorithm that
generally works as follows
1) Prime the pump by enqueing the starting node (pick one)
onto the queue
2) While the queue is not empty dequeue a node
3) Mark it visited
4) Enqueue its children on the stack if they are not
visited
Code Example)
// [Link]
// demonstrates breadth-first search
// to run this program: C>java BFSApp
////////////////////////////////////////////////////////////////
class Queue
{
private final int SIZE = 20;
private int[] queArray;
private int front;
private int rear;
public Queue() // constructor
{
queArray = new int[SIZE];
front = 0;
rear = -1;
}
public void insert(int j) // put item at rear of queue
{
if(rear == SIZE-1)
{
rear = -1;
}
queArray[++rear] = j;
}
public int remove() // take item from front of queue
{
int temp = queArray[front++];
46
if(front == SIZE)
{
front = 0;
}
return temp;
}
public boolean isEmpty() // true if queue is empty
{
return ( rear+1==front || (front+SIZE-1==rear) );
}
} // end class Queue
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private Queue theQueue;
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int j=0; j<MAX_VERTS; j++) // set adjacency
{
for(int k=0; k<MAX_VERTS; k++) // matrix to 0
47
{
adjMat[j][k] = 0;
}
}
theQueue = new Queue();
} // end constructor
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
public void displayVertex(int v)
{
[Link](vertexList[v].label);
}
public void bfs() // breadth-first search
{ // begin at vertex 0
vertexList[0].wasVisited = true; // mark it
displayVertex(0); // display it
[Link](0); // insert at tail
int v2;
while( ![Link]() ) // until queue empty,
{
int v1 = [Link](); // remove vertex at head
// until it has no unvisited neighbors
while( (v2=getAdjUnvisitedVertex(v1)) != -1 )
{ // get one,
vertexList[v2].wasVisited = true; // mark it
displayVertex(v2); // display it
[Link](v2); // insert it
} // end while
} // end while(queue not empty)
// queue is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
{
vertexList[j].wasVisited = false;
48
}
} // end bfs()
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
{
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
{
return j;
}
}
return -1;
} // end getAdjUnvisitedVertex()
} // end class Graph
class BFSApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
[Link]('A'); // 0 (start for bfs)
[Link]('B'); // 1
[Link]('C'); // 2
[Link]('D'); // 3
[Link]('E'); // 4
[Link](0, 1); // AB
[Link](1, 2); // BC
[Link](0, 3); // AD
[Link](3, 4); // DE
[Link]("Visits: ");
[Link](); // breadth-first search
[Link]();
} // end main()
} // end class BFSApp
Spanning Trees
49
A Spanning Tree is a list of edges that include all vertices of a
graph. Typically we are concerned with the Minimal Spanning Tree
(MST) which is a minimal set of edges that connect all the vertices
of the graph. There are usually several such possibilities, but any
one answer is as good as another MST answer
The Minimal Spanning Tree Algorithm can be either based upon a depth
first search or the breadth first search. In either case it is an
O(N) algorithm. The primary difference is in how we output the
answer, because a MST produces an edge list instead of a vertex list
Code Exmaple)
// [Link]
// demonstrates depth-first search
// to run this program: C>java DFSApp
////////////////////////////////////////////////////////////////
class StackX
{
private final int SIZE = 20;
private int[] st;
private int top;
public StackX() // constructor
{
st = new int[SIZE]; // make array
top = -1;
}
public void push(int j) // put item on stack
{
st[++top] = j;
}
public int pop() // take item off stack
{
return st[top--];
}
public int peek() // peek at top of stack
{
50
return st[top];
}
public boolean isEmpty() // true if nothing on stack
{
return (top == -1);
}
} // end class StackX
class Vertex
{
public char label; // label (e.g. 'A')
public boolean wasVisited;
public Vertex(char lab) // constructor
{
label = lab;
wasVisited = false;
}
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private StackX theStack;
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int y=0; y<MAX_VERTS; y++) // set adjacency
{
for(int x=0; x<MAX_VERTS; x++) // matrix to 0
{
adjMat[x][y] = 0;
}
}
theStack = new StackX();
51
} // end constructor
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
public void displayVertex(int v)
{
[Link](vertexList[v].label);
}
public void mst() // minimum spanning tree (depth first)
{ // start at 0
vertexList[0].wasVisited = true; // mark it
[Link](0); // push it
while( ![Link]() ) // until stack empty
{ // get stack top
int currentVertex = [Link]();
// get next unvisited neighbor
int v = getAdjUnvisitedVertex(currentVertex);
if(v == -1) // if no more neighbors
{
[Link](); // pop it away
}
else // got a neighbor
{
vertexList[v].wasVisited = true; // mark it
[Link](v); // push it
// display edge
displayVertex(currentVertex); // from currentV
displayVertex(v); // to v
[Link](" ");
}
} // end while(stack not empty)
// stack is empty, so we're done
for(int j=0; j<nVerts; j++) // reset flags
{
vertexList[j].wasVisited = false;
}
52
} // end mst()
// returns an unvisited vertex adj to v
public int getAdjUnvisitedVertex(int v)
{
for(int j=0; j<nVerts; j++)
{
if(adjMat[v][j]==1 && vertexList[j].wasVisited==false)
{
return j;
}
}
return -1;
} // end getAdjUnvisitedVertex()
} // end class Graph
class MSTApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
[Link]('A'); // 0 (start for mst)
[Link]('B'); // 1
[Link]('C'); // 2
[Link]('D'); // 3
[Link]('E'); // 4
[Link](0, 1); // AB
[Link](0, 2); // AC
[Link](0, 3); // AD
[Link](0, 4); // AE
[Link](1, 2); // BC
[Link](1, 3); // BD
[Link](1, 4); // BE
[Link](2, 3); // CD
[Link](2, 4); // CE
[Link](3, 4); // DE
[Link]("Minimum spanning tree: ");
[Link](); // minimum spanning tree
[Link]();
} // end main()
} // end class MSTApp
Topological Sorting
53
Topological Sorting (Critical Path Analysis) is the process of
determining which order things should be done in given an acyclic
graph. The algorithm is destructive in that it will end up
eliminating the graph to get an answer the runs in O(N2) time and
works as follows
So long as any vertices remain in the graph, find a vertex with no
outgoing edges (no successors)
Add this vertex to the beginning of the answer list
Remove this vertex from the graph, go back to step one
Code Example)
// [Link]
// demonstrates topological sorting
// to run this program: C>java TopoApp
////////////////////////////////////////////////////////////////
class Vertex
{
public char label; // label (e.g. 'A')
public Vertex(char lab) // constructor
{
label = lab;
}
} // end class Vertex
class Graph
{
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // list of vertices
private int adjMat[][]; // adjacency matrix
private int nVerts; // current number of vertices
private char sortedArray[];
54
public Graph() // constructor
{
vertexList = new Vertex[MAX_VERTS];
// adjacency matrix
adjMat = new int[MAX_VERTS][MAX_VERTS];
nVerts = 0;
for(int j=0; j<MAX_VERTS; j++) // set adjacency
{
for(int k=0; k<MAX_VERTS; k++) // matrix to 0
{
adjMat[j][k] = 0;
}
}
sortedArray = new char[MAX_VERTS]; // sorted vert labels
} // end constructor
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
}
public void displayVertex(int v)
{
[Link](vertexList[v].label);
}
public void topo() // toplogical sort
{
int orig_nVerts = nVerts; // remember how many verts
while(nVerts > 0) // while vertices remain,
{
// get a vertex with no successors, or -1
int currentVertex = noSuccessors();
if(currentVertex == -1) // must be a cycle
{
[Link]("ERROR: Graph has cycles");
return;
}
// insert vertex label in sorted array (start at end)
sortedArray[nVerts-1] = vertexList[currentVertex].label;
deleteVertex(currentVertex); // delete vertex
55
} // end while
// vertices all gone; display sortedArray
[Link]("Topologically sorted order: ");
for(int j=0; j<orig_nVerts; j++)
{
[Link]( sortedArray[j] );
}
[Link]("");
} // end topo
public int noSuccessors() // returns vert with no successors
{ // (or -1 if no such verts)
boolean isEdge; // edge from row to column in adjMat
for(int row=0; row<nVerts; row++) // for each vertex,
{
isEdge = false; // check edges
for(int col=0; col<nVerts; col++)
{
if( adjMat[row][col] > 0 ) // if edge to
{ // another,
isEdge = true;
break; // this vertex
} // has a successor
} // try another
if( !isEdge ) // if no edges,
{
return row; // has no successors
}
}
return -1; // no such vertex
} // end noSuccessors()
public void deleteVertex(int delVert)
{
if(delVert != nVerts-1) // if not last vertex,
{ // delete from vertexList
for(int j=delVert; j<nVerts-1; j++)
{
vertexList[j] = vertexList[j+1];
} // delete row from adjMat
for(int row=delVert; row<nVerts-1; row++)
{
moveRowUp(row, nVerts);
} // delete col from adjMat
for(int col=delVert; col<nVerts-1; col++)
{
56
moveColLeft(col, nVerts-1);
}
}
nVerts--; // one less vertex
} // end deleteVertex
private void moveRowUp(int row, int length)
{
for(int col=0; col<length; col++)
{
adjMat[row][col] = adjMat[row+1][col];
}
}
private void moveColLeft(int col, int length)
{
for(int row=0; row<length; row++)
{
adjMat[row][col] = adjMat[row][col+1];
}
}
} // end class Graph
class TopoApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
[Link]('A'); // 0
[Link]('B'); // 1
[Link]('C'); // 2
[Link]('D'); // 3
[Link]('E'); // 4
[Link]('F'); // 5
[Link]('G'); // 6
[Link]('H'); // 7
[Link](0, 3); // AD
[Link](0, 4); // AE
[Link](1, 4); // BE
[Link](2, 5); // CF
[Link](3, 6); // DG
[Link](4, 6); // EG
[Link](5, 7); // FH
57
[Link](6, 7); // GH
[Link](); // do the sort
} // end main()
} // end class TopoApp
58
Chapter 14: Java 1.5 Features and Issues
Generics
A Generic (Template) type allows you to create classes that accept
objects or variables of arbitrary type. The type is set at
instantiation time where you are required to set them. You can have
as many templated types as you wish in class, although frequently one
is enough
The Benefits of Generics primarily focus on code reuse. By
allowing for any type I could create a stack that would hold any data
type (primitive or object) without having to change one line of code.
This will obviously greatly expand code reuse as you will now only
have to write one binary tree, one red black tree, etc. Or, at least
that is the theory
The Big Issue with generics is that any operations you perform on
you data must be supported by all data types that you use your
generic on. This can be problematic in Java as (for example) ==
works one way for primitives and another for objects.
The Differences from Collections are that a generic is an actual
type you can specify. Useful anywhere a type is used. A collection
on the other hand, is a data structure that can hold any type(s) of
objects. The problem with collections is that you have to test and
cast them back and forth, which is slow with primitives at best as
you have to boxing them up with wrapper objects and then un-box them.
Code Example)
class MyList<Type>
{
//...
59
Value insert(Type item)
{
//...
}
Value remove(Type item)
{
//...
}
}
class Test
{
public static void main(String[] args)
{
MyList<String> list = new MyList<String>();
[Link](“Hi”);
//...
}
}
Autoboxing and Unboxing
Autoboxing means that a primitive will be wrapped by a wrapper object
when you provide one to a template type or collection that requires
objects. Such values will be unboxed automatically when they are
removed from such a structure as well.
The Good News is that you can now instantiate generics with
primitives for the generic types automatically as well as use them in
collections
The Bad News is that Java must instantiate and garbage collect each
of these wrapper objects. That means a serious performance hit from
‘hidden’ instantiations and GC activity. While I can not say how
efficiently they will implement this, I know it is an issue under C#
as they have done it
60
Operator Overloading (currently missing in action)
Operator Overloading allows you to specify how a given mathematical
or logical operator will work on your class. In this way you could
add the + operator for fractions so that you could type C = A + B; as
code instead of [Link](B,C) or some such. There are two advantages to
this former approach
By using the standard operators there will never be any doubt or
confusion as to what or how to handle operations on your class. For
example, Implement +, and everyone will know how to use it because
the notation is standard. Otherwsie some one may implement LT,
another lessthan another LessThanm, etc. This results in compatible
code
Integration of primitives and objects. I can now write a + method
in fraction that will take an int and a fraction so now I can write C
= 2+ A; if I so wish. This is of course very clean and intuitive to
use. This also would/will allow generics to work whether you want to
use primitives or objects. As the primitives MUST use the standard
operators, and the objects (previously) could never use the standard
operators. Now each could use the same code
Example Code)
NA at this time
61
Collections (part of Java 1.2+ actually)
Collections are a bit like generics in that a collection can store
any sort of object, including auto-boxed primitives (now). We have
avoid collections so far simply because they can be slow with boxing,
but they do provide a really easy way to work with a variety of data
types.
Notes on Collections
Collections stored things of type object. So you may need to down
cast to your type to access methods or field level variables of your
class. Otherwise only things that all objects can do are supported
Collections arrange the objects they store according to several
schemes. These include HashMap (sorted), HashSet (unsorted), List,
etc. You must specify this on instantiation.
Code Example)
import [Link].*;
public class CollectionTest
{
// Statics
public static void main( String [] args )
{
[Link]( "Collection Test" );
// Create a collection
HashSet collection = new HashSet();
// Adding
String dog1 = "Max", dog2 = "Bailey", dog3 = "Harriet";
[Link]( dog1 );
[Link]( dog2 );
[Link]( dog3 );
// Sizing
[Link]( "Collection created" + ", size=" +
[Link]() + ", isEmpty=" + [Link]() );
// Containment
[Link]( "Collection contains " + dog3 + ": " +
[Link]( dog3 ) );
62
// Iteration. Iterator supports hasNext, next, remove
[Link]( "Collection iteration (unsorted):" );
Iterator iterator = [Link]();
while ( [Link]() )
{
[Link]( " " + [Link]() );
}
// Removing
[Link]( dog1 );
[Link]();
}
}
Enhanced Iterators
An Iterator allows you to walk through a container of objects or an
array in an easy manner. For example
Current Source Code)
// Current for loop
for (Iterator i = [Link](); [Link](); )
{
String s = (String) [Link]();
//...
}
// Now, with generics:
for (Iterator<String> i = [Link](); [Link](); )
{
String s = [Link]();
// ...
}
// Now with the new iterator
int sum = 0;
for (int e : a) // read e is a item in a
{
sum += e;
}
63
Type Safe Enumerated
An Enumerated Type is one which you can define the range of values
that it can hold. This could be some numbers, strings, etc. It is
all up to you. The nice feature of Java is that these are type safe,
meaning they will not allow mis-assignments. Apparently they are
almost as quick as C/C++ implementation of enums that use of integer
constants and are not type safe in the least. Schweet.
Tentative Code Example)
public enum Suit { clubs, diamonds, hearts, spades }
Suit = 1 // error, type violation
Suit = clubs // okay
If ( Suit == clubs ) // okay
{
// …
}