• No results found

CmpSci 187: Programming with Data Structures Spring 2015

N/A
N/A
Protected

Academic year: 2021

Share "CmpSci 187: Programming with Data Structures Spring 2015"

Copied!
8
0
0

Loading.... (view fulltext now)

Full text

(1)

CmpSci 187: Programming with Data Structures Spring 2015

Lecture #12

John Ridgway March 10, 2015

1 Implementations of Queues

1.1 Linked Queues

A Linked Queue

Implementing a queue with a linked list is very natural. We keep head and tail pointers, enqueue at the tail and dequeue at the head.

LinkedQueue tail:

head:

Node next:

data:

Node next:

data:

Node next:

data:

"A" "B" "C"

A Linked Queue (continued)

• Could we have done this the other way round, with the front of the queue at the tail of the list?

• Enqueuing would have been easy, but dequeuing would have meant re- moving an element from the tail of the list, which means setting the tail pointer to the penultimate node.

Code for the Linked Queue

We make our generic queue fromNode<T> objects.

p u b l i c c l a s s L i n k e d U n b n d Q u e u e < T >

i m p l e m e n t s U n b o u n d e d Q u e u e I n t e r f a c e < T >

{

p r i v a t e Node < T > h e a d ;

(2)

p r i v a t e Node < T > t a i l ;

p u b l i c L i n k e d U n b n d Q u e u e () { h e a d = n u l l ;

t a i l = n u l l ; }

Enqueuing in the Linked Queue

To enqueue an element, we must make a new node and splice it into the list at the tail.

p u b l i c v o i d e n q u e u e ( T e l e m e n t ) {

Node < T > n e w N o d e = new Node < T >( e l e m e n t );

if ( t a i l == n u l l ) { h e a d = n e w N o d e ; } e l s e {

t a i l . s e t N e x t ( n e w N o d e );

}

t a i l = n e w N o d e ; }

The dequeue Operation

To dequeue, we remember the contents of the front node, cut it out of the list, and return the contents.

p u b l i c T d e q u e u e () { if ( i s E m p t y ()) {

t h r o w new Q u e u e U n d e r f l o w E x c e p t i o n ();

} e l s e {

T e l e m e n t = he a d . g e t D a t a ();

h e a d = h e a d . g e t N e x t ();

if ( h e a d == n u l l ) { t a i l = n u l l ; }

r e t u r n e l e m e n t ; }

}

Clicker Question #1

In the code below, the last line has replacedtail = newNode;. What’s wrong with this?

p u b l i c v o i d e n q u e u e ( T e l e m e n t ) {

Node < T > n e w N o d e = new Node < T >( e l e m e n t );

if ( t a i l == n u l l ) { h e a d = n e w N o d e ; } e l s e {

t a i l . s e t N e x t ( n e w N o d e ); } t a i l = t a i l . g e t N e x t (); }

A. the old tail node gets a pointer to itself B. there’s an NPE if the queue is empty

(3)

C. the old tail node gets garbage-collected D. the tail node shouldn’t change forenqueue

1.2 Applications: Palindromes

Applications: Palindromes

A palindrome is a string that is the same when written backward, like “Han- nah”, “Able was I ere I saw Elba”, or “Madam, I’m Adam”. Here’s a simple way to test whether a string is a palindrome:

• Create a stack and a queue (of Characters) and read the entire string, putting a copy of each letter (not spaces or punctuation) into both the stack and queue;

• Pop and dequeue one character at a time, checking that they match. If we empty both the stack and queue without finding a mismatch, the original string was a palindrome.

Palindrome Code: Part I

p u b l i c c l a s s P a l i n d r o m e {

p u b l i c s t a t i c b o o l e a n t e s t ( S t r i n g c a n d i d a t e ) { USI < C h a r a c t e r > s t a c k =

new L i n k e d S t a c k < C h a r a c t e r > ( ) ; UQI < C h a r a c t e r > q u e u e =

new L i n k e d Q u e u e < C h a r a c t e r > ( ) ;

for ( int i = 0; i < c a n d i d a t e . l e n g t h ; i ++) { c h a r ch = c a n d i d a t e . c h a r A t ( i );

if ( C h a r a c t e r . i s L e t t e r ( ch )) {

c h a r l o w e r C a s e = C h a r a c t e r . t o L o w e r C a s e ( ch );

s t a c k . p u s h ( l o w e r C a s e );

q u e u e . e n q u e u e ( l o w e r C a s e );

} }

Palindrome Code: Part II

b o o l e a n s t i l l O K = t r u e ;

w h i l e ( s t i l l O K && ! s t a c k . i s E m p t y ()) {

c h a r f r o m S t a c k = s t a c k . pop (); // u n b o x i n g c h a r f r o m Q u e u e = q u e u e . d e q u e u e (); // u n b o x i n g if ( f r o m S t a c k != f r o m Q u e u e ) {

s t i l l O K = f a l s e ; }

}

r e t u r n s t i l l O K ; }

}

(4)

Clicker Question #2

Here’s a way to test whether a string is a palindrome recursively. If the first letter and last letter do not match, return false. Otherwise recurse on the string without those two letters. What is the base case of this recursion?

A. it doesn’t matter as the recursion is invalid B. return true on a string with no letters C. return true if there is zero or one letter D. return false if there is one letter, true if 0

1.3 Array-based Queues

The Array Queues Way

Since a queue is a list of elements with a fixed order, a natural thought is to place the elements in an array:

0 1 2 3 4

B C r=rear

enqueue A enqueue B enqueue C dequeue A r

Avoiding Shifting the Array

We had to shift them, which gets expensive, so why not keep track of the front too?

0 1 2 3 4

B C D E f=front r=rear

enqueue A enqueue B enqueue C dequeue A enqueue D enqueue E

f r

Circular Queues

But now we run out of space. So wrap-around and put enqueued elements at beginning of the array:

(5)

0 1 2 3 4

F G C D E f=front r=rear

enqueue A enqueue B enqueue C dequeue A enqueue D enqueue E enqueue F dequeue B enqueue G r f

Implementing a Circular Queue

• Instead of incrementing the index into the array with an instruction like index++, we instead sayindex = (index + 1) \% capacity.

• Remember that the Java\%operator gives us the remainder when its first argument is divided by the second. So in this way locationcapacity - 1 is followed by location0.

Clicker Question #3

The elements in the queue start at locationfront and continue to location rear. Which of these expressions gives the number of elements in the queue, assuming the queue is not empty?

A. (rear - front) % capacity B. (rear - front + 1) % capacity C. (front - rear + 1) % capacity D. (rear+capacity+1 - front) % capacity

Coding the Circular Queue

• Note that a full queue and an empty queue both have rear just before front. But we keep track of numElements, which tells these two cases apart.

Circular Queue Attributes and Constructors

p u b l i c c l a s s A r r a y B n d Q u e u e < T > i m p l e m e n t s BQI < T > { p r i v a t e f i n a l int D E F C A P = 1 0 0 ;

p r i v a t e T [] q u e u e ;

p r i v a t e int n u m E l e m e n t s = 0;

p r i v a t e int f r o n t = 0;

p r i v a t e int r e a r = -1;

p u b l i c A r r a y B n d Q u e u e ( int m a x S i z e ) { q u e u e = ( T []) new O b j e c t [ m a x S i z e ];

}

(6)

p u b l i c A r r a y B n d Q u e u e () { t h i s ( D E F C A P );

}

Circular Queue Transformers

p u b l i c v o i d e n q u e u e ( T e l e m e n t ) { if ( i s F u l l ()) { t h r o w new QOE (); } r e a r = ( r e a r +1) % q u e u e . l e n g t h ; q u e u e [ r e a r ] = e l e m e n t ;

n u m E l e m e n t s += 1; } p u b l i c T d e q u e u e () {

if ( i s E m p t y ()) { t h r o w new QUE (); } T t o R e t u r n = q u e u e [ f r o n t ];

q u e u e [ f r o n t ] = n u l l ;

f r o n t = ( f r o n t + 1) % q u e u e . l e n g t h ; n u m E l e m e n t s -= 1;

r e t u r n t o R e t u r n ; }

Circular Queue Observers

p u b l i c b o o l e a n i s E m p t y () { r e t u r n ( n u m E l e m e n t s == 0); } p u b l i c b o o l e a n i s F u l l () {

r e t u r n n u m E l e m e n t s == q u e u e . l e n g t h ; } }

Application: The Game of War

• War is a two-player card game that calls for no decision-making by either player. You shuffle the deck and play deterministically until the game ends. Each player has a “hand”, originally half the deck.

• A basic play is for each player to turn up the first card in their hand. The player with the higher rank card wins both cards and puts them at the end of their hand.

The Game of War

• If the two cards have the same rank, each player deals out three cards face-down from their hand and turns up the next card in their hand. The player with the higher-ranked of these wins all ten cards now on the table.

• If those two cards have equal rank, each player plays four more cards for a second “war”, and so on until there is a resolution or one player runs out of cards.

(7)

Simulating the Game of War

• We first need to create and shuffle a virtual deck of cards – DJW’s code is worth keeping in mind for future reference though we won’t reproduce it here

• Our deck is an array of ints in the range from 0 through 12, since we don’t care about suits

• We form an array with four of each rank, then shuffle using a Random object. For each position in turn, we choose a random position after it and swap those two cards

Simulating the Game of War

• DJW’s WarGame class sets up two queues of Integers for the players’

hands, and a third queue prize for the cards that will be won in the current battle, if any. A variable, set by the user, limits the number of battles that may happen before the game is abandoned.

• The play method in WarGame deals the cards into the two player’s hands and then runs battles until the game ends.

Simulating the Game of War

• The battle method puts the players’ next cards into the prize queue, then checks the ranks of those two cards. If they are different, it puts the cards in the prize queue into the winner’s hand. If they are the same, it puts three cards from each player into the prize queue, then recursively calls itself.

• If a player runs out of cards, a QueueUnderflowException is thrown and caught by the play method, which declares the game over and keeps going.

Comparing the Implementations

• We’ve been careful in both implementations to make the enqueue and dequeue operations each O(1) time (not “always the same time”, of course, but “at most different by a constant factor”).

• The constructor for a linked queue is also O(1), but the constructor for an array with initial size N takes O(N ) time.

(8)

Comparing the Implementations

• An exception is the unbounded array implementation, which might take O(N ) time with N items in the queue if it has to resize the array.

• As we’ve noted, if we double the array size each time we change it, the total time to resize the array up to size N turns out to be O(N ), or an amortized O(1) per enqueue. Individual enqueues might be slower, but we have the guarantee about the total time

Comparing the Implementations

• The linked structure uses an extra pointer in each node, doubling the space needed for the pointer to the element object.

• The array implementation uses the space for the unused entries in the array. Thus if the array is always at least half full, the array implemen- tation uses less space, but both use only O(N ) space for a queue whose maximum size over time is N elements.

References

Related documents