What is a stack?
Here's a simple example we saw earlier in the Introduction:
import java.util.*; public class StackExample { public static void main (String[] argv) { // Use java.util.Stack, and specialize it to String's. Stack<String> toDoList = new Stack<String> (); // Add some strings. toDoList.push ("Pay bills"); toDoList.push ("Clean room"); toDoList.push ("Do homework"); toDoList.push ("See movie"); toDoList.push ("Hang out"); // Print. System.out.println ("My priorities: "); while (! toDoList.isEmpty() ) { String nextPriority = toDoList.pop (); System.out.println (" " + nextPriority); } } }Note:
My priorities: Hang out See movie Do homework Clean room Pay bills
Although we have created a stack of strings, we could just as easily create and use a stack of integers or various kinds of objects.
For example, here's an example that pushes a couple of integer's and prints them out in reverse order:
import java.util.*; public class StackExample2 { public static void main (String[] argv) { Stack<Integer> numberStack = new Stack<Integer> (); // Push some numbers numberStack.push (1); numberStack.push (2); numberStack.push (3); // Print in reverse order by popping off the stack. while (! numberStack.isEmpty() ) { System.out.println (numberStack.pop()); } } }
In-Class Exercise 1: Use a stack to print a string in reverse. Download StackExample3.java and add code to use a Character stack.
We'll use a stack to check whether the parentheses in a string of parentheses are matched:
Consider this string of parentheses: "(()())"
import java.util.*; public class ParenBalancing { public static void main (String[] argv) { // Test 1. String s = "()(())()"; checkParens (s); // Test 2. s = "((())"; checkParens (s); // Test 3. s = ")("; checkParens (s); } static void checkParens (String inputStr) { // Extract letters from String. char[] letters = inputStr.toCharArray(); // We'll need a Character stack. Stack<Character> stack = new Stack<Character> (); boolean unbalanced = false; for (int i=0; i<letters.length; i++) { if (letters[i] == '(') { // Push left paren. stack.push (letters[i]); } else if (letters[i] == ')') { // Right paren: we should have a match on the stack. char ch = stack.pop (); if (ch != '(') { // Not a match. unbalanced = true; break; } } } //end-for if ( (unbalanced) || (! stack.isEmpty()) ) { System.out.println ("String " + inputStr + " has unbalanced parens"); } else { System.out.println ("String " + inputStr + " has balanced parens"); } } }
In-Class Exercise 2: Why do we need the second condition in the if-statement above?
if ( (unbalanced) || (! stack.isEmpty()) ) { System.out.println ("String " + inputStr + " has unbalanced parens"); // ...
In-Class Exercise 3: Download, compile and execute ParenBalancing.java above. The third test results in an error. Can you see what the problem is and fix it?
Next, let's modify the above example to balance two types of parentheses simultaneously:
In-Class Exercise 4: Show the stack contents at each step for the above two examples.
Here's the program:
import java.util.*; public class ParenBalancing3 { public static void main (String[] argv) { // Test 1. String s = "([()])"; checkParens (s); // Test 2. s = "[][()]()"; checkParens (s); // Test 3. s = "((())]"; checkParens (s); // Test 4. s = "[)(]"; checkParens (s); } static void checkParens (String inputStr) { char[] letters = inputStr.toCharArray(); Stack<Character> stack = new Stack<Character> (); boolean unbalanced = false; for (int i=0; i<letters.length; i++) { if ( (letters[i] == '(') || (letters[i] == '[') ) { // Push every left paren of each kind. stack.push (letters[i]); } else if (letters[i] == ')') { // We should have a '(' match on the stack char ch = ')'; if (! stack.isEmpty() ) { ch = stack.pop (); } if (ch != '(') { // Not matched ⇒ unbalanced. unbalanced = true; break; } } else if (letters[i] == ']') { // We should have a '[' match on the stack char ch = ']'; if (! stack.isEmpty() ) { ch = stack.pop (); } if (ch != '[') { // Not matched. unbalanced = true; break; } } } // end-for if ( (unbalanced) || (! stack.isEmpty()) ) { System.out.println ("String " + inputStr + " has unbalanced parens"); } else { System.out.println ("String " + inputStr + " has balanced parens"); } } }
In-Class Exercise 5: Most parentheses matching applications occur in real text, where the parens are distributed inside text, such as:
if ((x < y) && (Math.abs(x)>0)) { if(x==5) {x += 3;}};Download ParenBalancing4.java and modify it to handle parentheses strewn about in real text. You will also need to add a new type of parenthesis: the curly bracket.
Yet another way to check if a string is a palindrome:
Let's examine the program:
import java.util.*; public class Palindrome { public static void main (String[] argv) { // Test 1. String str = "redder"; System.out.println ( str + " " + checkPalindrome(str) ); // Test 2. str = "river"; System.out.println ( str + " " + checkPalindrome(str) ); // Test 3. str = "neveroddoreven"; System.out.println ( str + " " + checkPalindrome(str) ); } static String checkPalindrome (String str) { // Extract the letters. char[] letters = str.toCharArray (); // Create an empty stack. Stack<Character> stack = new Stack<Character>(); // The letters must "balance" up to the middle. int mid = letters.length / 2; // Push the first half. for (int i=0; i<mid; i++) { stack.push (letters[i]); } // Odd or even? We have to adjust the mid-point accordingly. if (letters.length % 2 > 0) { // Odd number => swallow middle letter. mid = mid+1; } // Now check the second half. for (int i=mid; i<letters.length; i++) { char ch = stack.pop (); if (ch != letters[i]) { // Mismatch => not a palindrome. return "is not a palindrome"; } } return "is a palindrome"; } }Note:
In-Class Exercise 6: Download and modify Palindrome2.java so that blanks and other punctuation are ignored in testing for palindromes. Thus, the following should test correctly as palindromes:
Evil did I dwell; lewd I did live // Oldest known recorded palindrome. A man, a plan, a canal: Panama // One of the most famous.You will find the method Character.isLetter() useful, as in
if ( Character.isLetter (ch) ) { // ... ch is a letter (not punctuation) } else { // ... ch is something other than 'a' to 'z' or 'A' to 'Z'. }One way to solve the problem is to extract all the actual letters and put that into a list, and then to use the list in checking for palindromicity.
Let's build our own stack data structure:
public class OurStack { char[] letters; // Store the chars in here. int top; // letters[top]: next available space. // Constructor. public OurStack () { letters = new char [100]; top = 0; } public void push (char ch) { // Note: what if top >= letters.length? letters[top] = ch; top ++; } public char pop () { // Note: what if top < 0? top --; return letters[top]; } public boolean isEmpty () { if (top == 0) { return true; } else { return false; } } }
Let's improve this stack with some error checking:
public class OurStack { char[] letters; // Store the chars in here. int top; // letters[top]: next available space. // Constructor. public OurStack () { letters = new char [100]; top = 0; } public void push (char ch) { // Test for full stack. if (top >= letters.length) { System.out.println ("ERROR: OurStack.push(): stack overflow"); return; } letters[top] = ch; top ++; } public char pop () { // Test for empty stack. if (top <= 0) { System.out.println ("ERROR in OurStack.pop(): stack empty"); // Still need to have a return statement, so we return some "junk letter". return '@'; } top --; return letters[top]; } public boolean isEmpty () { if (top == 0) { return true; } else { return false; } } }Note:
import java.util.*; public class OurStackExample { public static void main (String[] argv) { String s = "((()))"; checkParens (s); s = "((())"; checkParens (s); } static void checkParens (String inputStr) { char[] letters = inputStr.toCharArray(); OurStack stack = new OurStack (); // ... code similar to earlier examples ... } }
In-Class Exercise 7: Download OurStackExample2.java and modify OurStack2.java to use an ArrayList instead of an array. This way, there's no upper limit to the size. What part of the code changes from the original OurStack.java? Do we still need to check for a lower limit in pop()?
Next, let's use a linked-list instead of an array:
import java.util.*; public class OurStack3 { // Use a linked list instead of an array. LinkedList<Character> list; public OurStack3 () { // Can be unlimited in size now. list = new LinkedList<Character>(); } public void push (char ch) { // No need to check for upper limit. list.add (ch); } public char pop () { if (! list.isEmpty()) { return list.removeLast(); } else { System.out.println ("ERROR in OurStack.pop(): stack empty"); return '@'; } } public boolean isEmpty () { return list.isEmpty(); } }Note:
What's an ADT?
First meaning of ADT: a "standard" data structure with "well-known" operations:
Second meaning of ADT: the idea that implementations are hidden
Third meaning of ADT: language features that support ADT's
Let's look at an example of using interfaces.
public class ParenBalancing5 { public static void main (String[] argv) { String s = "()(())()"; // Use this stack implementation for the first example. StackImpl1 stack1 = new StackImpl1 (); ParenCheckTool.check (s, stack1); s = "((())"; // Use another stack implementation for the second example. StackImpl2 stack2 = new StackImpl2 (); ParenCheckTool.check (s, stack2); } }Note:
public class ParenCheckTool { // NOTE: checkParens() is written to accept an interface instead // of an actual class. static void check (String inputStr, OurStackInterface stack) { // This code below is complete unchanged from before ... char[] letters = inputStr.toCharArray(); boolean unbalanced = false; for (int i=0; i < letters.length; i++) { if (letters[i] == '(') { stack.push (letters[i]); } else if (letters[i] == ')') { // We should have a match on the stack. char ch = ')'; if (! stack.isEmpty() ) { ch = stack.pop (); } if (ch != '(') { // Mismatch ⇒ unbalanced. unbalanced = true; break; } } } if ( (unbalanced) || (! stack.isEmpty()) ) { System.out.println ("String " + inputStr + " has unbalanced parens"); } else { System.out.println ("String " + inputStr + " has balanced parens"); } } }
public interface OurStackInterface { // Signature, and only the signature, of the push() method: public void push (char ch); // Same for the pop() method: public char pop (); // Same for the isEmpty() method: public boolean isEmpty (); }
Note:
public class StackImpl1 implements OurStackInterface { // An array implementation ... char[] letters; // Store the chars in here. int top; // letters[top]: next available space. // ... the rest of the code is the same as in OurStack.java ... }
import java.util.*; public class StackImpl2 implements OurStackInterface { // Use a linked list instead of an array. LinkedList<Character> list; // ... rest of the code is the same as OurStack3.java ... }
public class ParenBalancing5 { public static void main (String[] argv) { String s = "()(())()"; // Use this stack implementation for the first example. StackImpl3 stack3 = new StackImpl3 (); ParenCheckTool.check (s, stack3); s = "((())"; // Use another stack implementation for the second example. StackImpl2 stack2 = new StackImpl2 (); ParenCheckTool.check (s, stack2); } }
What is a queue?
The most basic Queue ADT has three operations:
A more powerful queue might also implement methods like:
Consider this example of a queue that uses Java's LinkedList to serve as our queue data structure:
import java.util.*; public class QueueExample { public static void main (String[] argv) { // We'll use Java's LinkedList as our queue. LinkedList<String> taskQueue = new LinkedList<String>(); // Add some strings. taskQueue.add ("Pay bills"); taskQueue.add ("Clean room"); taskQueue.add ("Do homework"); taskQueue.add ("See movie"); taskQueue.add ("Hang out"); // Now extract in "queue" order using the removeFirst() method in LinkedList. System.out.println (taskQueue.removeFirst()); System.out.println (taskQueue.removeFirst()); System.out.println (taskQueue.removeFirst()); System.out.println (taskQueue.removeFirst()); System.out.println (taskQueue.removeFirst()); System.out.println ("⇒ Tasks remaining: " + taskQueue.size()); } }Note:
In-Class Exercise 8: Modify QueueExample.java above to print the queue after each removal. Change the actual task strings to suit your own priorities.
In-Class Exercise 9: Modify QueueExample.java above to use Java's ArrayList instead of a LinkedList. You may need to look at the Java API's to see which of ArrayList's remove methods you could use.
Consider the following two-person card game:
In-Class Exercise 10: Find a partner and play the game. You can use small pieces of paper numbered 0, ..., 9 and start by dealing 5 cards each.
We will write a program to simulate the card game:
// This card game is a variation of the War card game described // in the book "Object-Oriented Data Structures Using Java" by // N.Dale, D.T.Joyce and C.Weems. import java.util.*; public class StrangeCardGame { public static void main (String[] argv) { // Use cards numbered 0,..,9 and deal 5 cards to each player. playGame (5, 10); } static void playGame (int dealSize, int numCards) { // Cards dealt out to player 1: LinkedList<Integer> player1 = new LinkedList<Integer>(); // Cards dealt out to player 2: LinkedList<Integer> player2 = new LinkedList<Integer>(); // The pile between the two players: LinkedList<Integer> pile = new LinkedList<Integer>(); // Make the cards and shuffle them randomly. int[] cards = new int [numCards]; for (int i=0; i<cards.length; i++) { cards[i] = i; } shuffle (cards); // Deal cards to each player. int cardCount = 0; for (int k=0; k<dealSize; k++) { player1.add (cards[cardCount]); player2.add (cards[cardCount+1]); cardCount += 2; // Note: += operator. } // Now play. boolean done = false; int round = 0; while (! done) { // Each player plays their first card. int player1first = player1.removeFirst (); pile.add (player1first); int player2first = player2.removeFirst (); pile.add (player2first); System.out.println ("Round 0: player1's card=" + player1first + " player2's card=" + player2first); if (player1first > player2first+2) { // Add pile into player 1's cards. addListToList (pile, player1); System.out.println (" ⇒ player1 gets pile"); } else if (player2first > player1first+2) { // Add pile into player 2's cards. addListToList (pile, player2); System.out.println (" ⇒ player2 gets pile"); } else { System.out.println (" ⇒ both cards added to pile"); } if (player1.isEmpty()) { System.out.println ("Player 2 wins!"); done = true; } else if (player2.isEmpty()) { System.out.println ("Player 1 wins!"); done = true; } } //end-while } static void shuffle (int[] A) { // ... random permutation ... } static void addListToList (LinkedList<Integer> list1, LinkedList<Integer> list2) { // ... Extract every item in list1 and add them to list2 ... } }Note:
In-Class Exercise 11: The above code is incomplete. Download StrangeCardGame.java and implement the addListToList() method using queue operations.
Consider the following simple animation:
An anteater will systematically hunt down ants one by one
⇒
The next ant is taken from the head of the queue.
In-Class Exercise 12: Download, compile and try out AntEater.java. Click rapidly in the screen in a few different places and watch the anteater scour for ants.
Here's the program
// Various import's needed for GUI's. import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.util.*; // This we need for LinkedList. class AntEaterPanel extends JPanel { // We'll put all the ant-clicks into a queue, from which the anteater // will pull out the next target. LinkedList<Point> antQueue = new LinkedList<Point>(); Point antEater; // The current position of the anteater. Point nextAnt; // The position of the current ant being chased. // Constructor. public AntEaterPanel () { // Listen to mouseclicks. this.addMouseListener ( new MouseAdapter () { public void mouseClicked (MouseEvent e) { handleClick (e.getX(), e.getY()); } } ); // The anteater will run in a separate thread. Thread t = new Thread () { public void run () { move (); } }; t.start (); } public void paintComponent (Graphics g) { super.paintComponent (g); // Clear drawing area. g.setColor (Color.white); Dimension D = this.getSize(); g.fillRect (0,0, D.width, D.height); // Draw the ants. g.setColor (Color.gray); if (nextAnt != null) { g.fillOval (nextAnt.x-2,nextAnt.y-2, 4, 4); } for (Iterator<Point> iter=antQueue.iterator(); iter.hasNext(); ) { Point p = iter.next(); g.fillOval (p.x-2,p.y-2, 4, 4); } // AntEater. g.setColor (Color.red); g.fillOval (antEater.x-10,antEater.y-10, 20, 20); } void draw () { // Place a call to paintComponent(). this.repaint (); } void handleClick (int x, int y) { // Add a new ant to the queue. antQueue.add (new Point(x,y)); draw (); } void move () { // This is where we'll start the anteater. antEater = new Point (0,0); while (true) { // Anteater sleeps 100 milliseconds. try { Thread.sleep (100); } catch (InterruptedException e) { } if (nextAnt == null) { if (! antQueue.isEmpty() ) { // See if there's an ant to chase. nextAnt = antQueue.removeFirst(); } } else { if ( distance(nextAnt, antEater) < 10 ) { // Eat the ant. nextAnt = null; } else { // Otherwise, step towards ant. double theta = Math.atan2 ((nextAnt.y - antEater.y), (nextAnt.x - antEater.x)); double stepsize = 10.0; antEater.x = (int) (antEater.x + stepsize*Math.cos(theta)); antEater.y = (int) (antEater.y + stepsize*Math.sin(theta)); draw (); } } } } double distance (Point p, Point q) { double distSq = (p.x-q.x)*(p.x-q.x) + (p.y-q.y)*(p.y-q.y); return Math.sqrt (distSq); } } //end-panel class AntEaterFrame extends JFrame { public AntEaterFrame () { this.setTitle ("AntEater"); this.setSize (300, 300); // The frame only consists of the panel. AntEaterPanel panel = new AntEaterPanel (); this.getContentPane().add (panel); this.setVisible (true); } } //end-frame public class AntEater { public static void main (String[] argv) { AntEaterFrame f = new AntEaterFrame (); } }Note:
In-Class Exercise 13: Modify the code above to print the number of ants eaten and to print the total distance travelled by the anteater.
In-Class Exercise 14:
Consider how the anteater selects the next ant to pursue after it's
finished with the current ant. In the program above, it goes after
the next in queue order. What would we have to change, and how, to
get the anteater to go after the nearest ant from among
those in the queue? Write down pseudocode on paper and then
try to implement it. What "queue rules" are we violating in
doing this? Can you think of an alternative data structure
for this variation of the problem?