19: ADT and Search, Reviewed
Announcements
Quiz in our next discussion (next Tuesday, which is a Monday schedule).
Twelve programming assignments are planned. The workload in this course is nothing if not consistent. Please don’t be mad at me because another instructor didn’t remember to tell you about an end-of-term paper until this week.
The first batch of ADH notices went out earlier today. More are almost certainly coming.
Specialized linear ADTs
The “standard” linear ADTs (in Java) are the array and the (generic) List
. Arrays are a simple type, with very fast random access but the limitation of fixed sizes. Lists are more flexible, and their underlying implementation is generally written in terms of (resizing) arrays or (sometimes) in terms of a linked list.
But as we’ve mentioned, there are other linear data structures that one might use; they are similar to lists but restrict themselves in various ways. We’re going to revisit them so you’re ready when you see them again (“for the first time”) in 187. Today, we’ll focus on behavior, not implementation, but we will do toy implementations soon.
Stacks
Stacks are a last-in, first-out data structure. They are like lists, but instead of allowing for random access (via get
, remove
, add
), they restrict a user to adding items on one end (the “top”) and removing from that same position. These operations are sometimes called push
(add an item), pop
(remove an item), and peek
(look at but do not remove the top item).
Modern Java suggests we use the Deque
interface, which is short for double-ended queue, and use the addFirst
, removeFirst
, and peekFirst
methods. In either case, though, the behavior is the same, LIFO.
s.push("a");
s.push("b");
s.pop();
s.push("c");
s.push("d");
s.peek();
(top on right)
- After the first operation, the stack contains [“a”]
- After the second, the stack containes [“a”, “b”].
- removes and returns “b”, then stack contains, [“a”]
- [“a”, “c”]
- [“a”, “c”, “d”]
peek
returns “d”
In class exercise 1
s.push(1);
s.push(2);
s.push(3);
s.peek()
s.pop();
s.push(1);
s.pop();
s.pop();
What are the contents of the stack after this code executes?
- [1]
- [2, 1]
- [3, 2, 1]
- [3, 2, 1]
- [2, 1]
- [1, 2, 1]
- [2, 1]
- [1]
Queues
Queues are a first-in, first-out data structure. Java has a Queue
interface you can use, or you can (also) use Deque
, as described in its documentation. In a Queue
, we typically talk about add
(always at one end) remove
(always from the other), and sometimes peek
(just like a stack, returns but does not remove the next element that remove
would return).
q.add("a");
q.add("b");
q.remove();
q.add("c");
q.add("d");
q.peek();
- [“a”]
- [“a”, “b”]
- removes and returns “a”, queue contains [“b”]
- [“b”, “c”]
- [“b”, “c”, “d”]
- returns “b”
In-class exercise 2
q.add(1);
q.add(2);
q.add(3);
q.peek()
q.remove();
q.add(1);
q.remove();
q.remove();
What are the contents of the queue after this code executes? (rear on right)
- [1]
- [1, 2]
- [1, 2, 3]
- [1, 2, 3]
- [2, 3]
- [2, 3, 1]
- [3, 1]
- [1]
A side note: over/underflow
Stacks and queues can underflow. If you call pop
or remove
on an empty stack/queue, this will generate an exception.
Some stacks and queues are bounded, which means they have an explicit capacity. If you try to push
or add
to a stack/queue that is already at capacity, then you will overflow the structure and generate an exception.
Priority queues
A priority queue is like a queue, but it returns the next “smallest” (in Java) thing, rather than the first-in thing, when remove
or peek
is called.
It’s important to note that the exact order of the items stored in the priority queue is not visible to the user; you can only see the “next” / “top” item (that will be returned by peek
or remove
). Internally, priority queues are implemented as “heaps”, which are a tree-based structure similar to, but different from, the binary search trees we talked about briefly earlier this semester. Heaps allow for efficient (log n) insertion and removal of the smallest item.
How do we define “smallest”? The usual way, by either depending upon the “natural” ordering of the elements stored in the PriorityQueue<E>
(that is, they must implement Comparable
) or by passing in a Comparator
when constructing the PriorityQueue
.
Suppose then we do the following with a priority queue:
pq.add("b");
pq.add("a");
pq.remove();
pq.add("c");
pq.add("d");
pq.peek();
- [“b”]
- [“b”, “a”]
- removes and returns “a”, contents are [“b”]
- [“c”, “b”]
- [“c”, “d”, “b”] ; note we don’t know whether “c” or “d” comes first; all we know is “b” is up next to be removed
- returns “b”
In class exercise 3
pq.add(3);
pq.add(2);
pq.add(1);
pq.peek()
pq.remove();
pq.add(1);
pq.remove();
pq.remove();
- [3]
- [3, 2]
- [3, 2, 1] (but really we only know 1 is first)
- returns 1
- removes and returns 1, leaving [3, 2]
- [3, 2, 1]
- removes and returns 1, leaving [3, 2]
- removes and returns 2, leaving [3]
Back to search
Now let’s do search, one more time, knowing what we know about stacks, queues, and priority queues.
Here’s the graph we’ll reference:
This is similar to the homework graph but paths now have a cost on the edges (this is the g(x) from last class), and the vertices are labeled with their estimated cost to the goal, their heuristic value: (h(x)).
Assume that the neighbors of each vertex are returned in alphabetical order. For example, the only neighbors of S are A and B, in that order — S is a neighbor of C, but C is not a neighbor of S in this directed graph!
Suppose we execute our findPath
method from last lecture with a Queue
, so that we do a BFS. In what order are vertices added to the frontier?
(on board; see homework 18 solution)
Suppose we execute our findPath
method from last lecture with a Stack
, so that we do a DFS. What does our frontier look like?
(on board)
S
B A
G2 E A
Suppose we execute our findPath
method from last lecture with a PriorityQueue
, prioritized by h(x) values, so that we do a greedy least-cost search. What does our frontier look like?
(on board)
S
A B
D C B
G1 C B
If we wanted to do the “optimal” A* search, we’d have to order nodes in the priority queue by f(x) = h(x) + g(x). We’d also need to make sure that h(x) was consistent; more on this in later COMPSCI courses.