Comp 271 Week 8
Midterm: Tuesday, October 20.
You will be given a simplified ArrayList class, to serve as examples of various things.
Demo: create a linkedlist and see it in action with the inspector. Note
that the head cell is dataless!
Separate demo: iterator + concurrent list modification = kablooey!
Can you make your iterator do that?
Also note I have List<E> li = new LinkedList<E>()
The data for an ArrayList iterator "is" a position-marker in the array (sometimes called a cursor).
The key data for a LinkedList iterator is a pointer to a specific cell.
The respective next() operations are cursor+=1 and p=p.next(), in
addition to returning the current array[cursor] / p.data(). The
respective hasNexts are cursor<array.length and p!=null.
Stacks and Queues
Bailey has these both implement Linear<E>. I think this is misleading: stacks and queues are supposed to be very spare, and not have extra methods. This way, you have class invariants
that are preserved; eg the only way you can access an element in the
middle of the stack is to pop (and thus remove) everything above it.
Basic stack operations: push, pop, empty
Bailey adds add(), same as pop(), and remove, same as push(), in order to meet the linear interface. Also peek(), which is not necessary but often convenient.
Stacks are LIFO.
classic lunch-tray model v picture on page 225
Stacks can be implemented as arraylists, or anything else. See java.util.Stack.
Stacks used to check parenthesis balance
Basic logic:
valid -> empty
valid -> valid valid
valid -> ( valid )
When we add [] and {}, we add
valid -> { valid }
valid -> [ valid ]
Writing a recursive balance-checker is moderately straightforward.
However, it helps to have a globally visible nextChar that represents
the next symbol.
Recursion simulation with a stack
Linear examples: kind of trivial: we just push a sequence of values!
Quicksort example starting on page 222
Ultimately, recursion is implemented using a stack.
Queues
FIFO: add() and remove() are now most commonly known as
enqueue() and dequeue(). But note the semantics differ! Usually, queues
are FIFO. Also, note that a direct implementation is just a wee easier
with, say, doubly-linked lists; with an array, the data creeps along.
Or else you do performance-robbing inserts or deletes at the front of
the array. The classic implementation of a queue using an arraylist and add/delete is O(n2) for n inserts/deletes!
Suppose we want to implement a queue using an array A that goes from 0 to MAX-1. We have two positions, head and tail.
- head is the position of the next element to be dequeued
- tail is the next open slot for enqueuing
When head==tail, the queue is empty. No elements will ever be shifted.
When we reach tail=MAX, we reset tail=0, and continue inserting there;
ditto for head.
public void enqueue(T val) {
if (isfull() ) return;
A[tail]=val;
// tail is position
of next open slot
tail++;
if (tail==MAX) tail=0;
}
public T dequeue() {
if (head==tail) return; // queue is empty
T val = A[head];
// head is position of next elt
to be dequeued
head++;
if (head==MAX) head=0;
return val;
}
There is one minor trickiness with determining whether the queue is
full: if we fill every cell, then head==tail again and we can't tell if
the queue is full or empty! So we will always leave one slot vacant,
and have the queue be full if head=tail+1, mod MAX (ie if head==tail+1
or head==0 and tail+1==MAX.
Note that java.util.Queue() is an interface. One reason is because, in the
real world, there are lots of non-FIFO queues. A queue is really a
place for things to wait, and waiting occurs only when you have concurrent programs. So there aren't a lot of simple problems solved with queues.
Thursday
Study guide
Review array-based queues
Queues example:
Coins puzzle
Finding the shortest way out of a maze
Stack version of maze puzzle
Sorted Lists and Binary Search
Binary array search: example code from my demo:
public int search(double x) {
int lo = 0, hi = A.length-1; /*# we know that either A[i]=x and lo<=i<=hi, or else x is not found.*/
int count = 0;
do {
count++;
int mid = (lo+hi)/2; // lo <=mid<hi;
if (A[mid] == x) {
System.out.println("number of iterations: " + count);
return mid;
}
if (A[mid]<x) lo = mid+1; // upper half; lo <=hi
else hi = mid-1; // could result in hi = lo-1!
} while (lo < hi);
if (lo==hi && A[lo]==x) return lo;
System.out.println("number of iterations: " + count);
return -1;
}
Note the use of assertions and invariants to help us figure out when to use < and when to use <=.
Here's where things stand in terms of finding an element and inserting
a new element, assuming binary search is used in the array version:
|
ArrayList
|
LinkedList
|
search
|
O(log n)
|
O(n)
|
insert
|
O(n)
|
O(1)
|
Our goal after the exam is to introduce tree structures that combine the best of both worlds.
Slightly simpler version of binary search with compareTo:
public int search(String x) {
int lo = 0, hi = A.size()-1; /*# we know that either A[i]=x and lo<=i<=hi, or else x is not found.*/
int count = 0;
do {
count++;
int mid = (lo+hi)/2; // lo <=mid<hi;
int res = x.compareTo(A.get(mid));
if (res== 0) { // found
System.out.println("number of iterations: " + count);
return mid;
}
else if (res > 0) lo = mid+1; // upper half; lo <=hi
else hi = mid-1; // could result in hi = lo-1!
} while (lo < hi);
if (lo==hi && A.get(lo).equals(x)) return lo;
System.out.println("number of iterations: " + count);
return -1;
}
The OrderedStructure interface, p 259
(This is Bailey's; there is no java.util version)
In my version of lab5 (labs/lab5/pldlist on my computer), there is
something that can be made into a sorted-list class. However, all the
mutators that allow non-sorted insertion would have to be removed, and insertInOrder() would be renamed simply insert() (or add()).
Array version: finding is O(log(n)); inserting is O(n).
Linked version: finding is O(n); inserting is O(1).
Can we do better?