06a: Nodes and Stack ADT

ICS211, Spring 2013
Dr. Zach

(switch view)

Status

Discussion 02: Good Code

Array-based stack implementation

Arrays as data structure

Primary advantage

Disadvantages

Concept: linked list data structure

Simplest possible node class

public class Node {
  public String data;
  public Node next;
}

Building a list

  String[] names = {"Alice", "Bob", "Carol", "Doug"};
  Node head = new Node();
  head.data = names[0];
  Node current = head;
  for (int i = 1; i < names.length; i++) {
    current.next = new Node();
    current = current.next;
    current.data = names[i];
    current.next = null;     //not really necessary
  }

  //at this point, head points to the start of the list
  for (Node n = head; n != null; n = n.next) {
    System.out.print(n.data + " ");
  }
  System.out.println();  

Exercise

//weird contrived example
Node one;
one = new Node();
Node two = new Node();
Node three = new Node();
one.next = two;
three.next = two;
one.data = "Albert";
three.data = one.data;
two.data = "Bianca";
three.data = "Calvin";
Node head = three; 

for (Node n = head; n != null; n = n.next) {
  System.out.println(n.data);
}
Node one and three both point to node two.
Prints:
Calvin 
Bianca

A real Node class (with encapulsation)

public class Node {

  private String data;
  private Node next;
    
  public Node(String data, Node next) {
    this.data = data;
    this.next = next;
  }
  
  public Node(String data) {
    this(data, null);
  }
  
  public String getData() {
    return data;
  }
  
  public void setData(String data) {
    this.data = data;
  }
  
  public Node getNext() {
    return next;
  }
  
  public void setNext(Node next) {
    this.next = next;
  }
}

Using revised Node class

  String[] names = {"Alice", "Bob", "Carol", "Doug"};
  Node head = new Node(names[0]);
  Node current = head;
  for (int i = 1; i < names.length; i++) {
    current.setNext(new Node(names[i]));
    current = current.getNext();
  }

  //at this point, head points to the start of the list
  for (Node n = head; n != null; n = n.getNext()) {
    System.out.print(n.getData() + " ");
  }
  System.out.println();

Although Node is encapsulated, resulting linked list structure is not.

Exercise

//another strange example
Node head = new Node("Albert");
Node sub = new Node("Bianca", head);
head = sub;
head = new Node("Calvin", head);
head = new Node("Dale");
head.setNext(sub);

for (Node n = head; n != null; n = n.getNext()) {
  System.out.println(n.getData());
}
Builds a list of nodes: Calvin -> Bianca -> Albert,
but then loses list when set head to Dale node
Makes Dale node point to Bianca -> Albert sublist
Therefore, prints:
Dale
Bianca
Albert

Node-based stack implementation (1/6)

Instance variables:


  private Node top;

Node-based stack implementation (2/6)

Constructor:

  public Stack() {
    this.top = null;
  }

Node-based stack implementation (3/6)

  public void push(String item) {
    this.top = new Node(item, top);
  }  

Node-based stack implementation (4/6)

  public String peek() {
    if (this.top == null) {
      throw new IllegalStateException("Can't peek() into empty stack.");
    }
    return this.top.getData();
  }  

Node-based stack implementation (5/6)

  public String pop() {
    if (this.top == null) {
      throw new IllegalStateException("Can't pop() from empty stack.");
    }
    String data = this.top.getData();
    this.top = this.top.getNext();
    return data;
  }  

Node-based stack implementation (6/6)

  public int size() {
    int size = 0;
    Node curr = this.top;
    while (curr != null) {
      size++;
      curr = curr.getNext();
    }
    return size;
  }  

Sidenote: for vs while loop

  public int size() {
    int size = 0;
    Node curr = this.top;
    while (curr != null) {
      size++;
      curr = curr.getNext();
    }
    return size;
  }  
  public int size() {
    int size = 0;
    for (Node curr = this.top; curr != null; curr = curr.getNext()) {
      size++;
    }
    return size;
  }  

Is this clearer? Maybe, maybe not.

  public int size() {    
    int size;
    Node curr;
    for (size = 0, curr = this.top; curr != null; size++, curr = curr.getNext());
    return size;
  }  

Amusing to move all code out of the loop body, but this is not clearer.

Complete node-based stack implementation

Now with efficient size tracking, so all operations O(1)

public class Stack {

  private Node top;
  private int size;

  public Stack() {
    this.top = null;
    this.size = 0;
  }

  public void push(String item) {
    this.top = new Node(item, top);
    this.size++;
  }  

  public String peek() {
    if (this.top == null) {
      throw new IllegalStateException("Can't peek() into empty stack.");
    }
    return this.top.getData();
  }
  
  public String pop() {
    if (this.top == null) {
      throw new IllegalStateException("Can't pop() from empty stack.");
    }
    String data = this.top.getData();
    this.top = this.top.getNext();
    this.size--;
    return data;
  }  

  public int size() {
    return this.size;
  }  
}

Stack ADT

Generics

Problem

Nodes and Stack only work with String:

public class Node {

  private String data;
  private Node next;
  
  //...

Declare the type parameter

public class Node<E> {

Generic Node

public class Node<E> {

  private E data;
  private Node<E> next;
    
  public Node(E data, Node<E> next) {
    this.data = data;
    this.next = next;
  }
  
  public Node(E data) {
    this(data, null);
  }
  
  public E getData() {
    return data;
  }
  
  public void setData(E data) {
    this.data = data;
  }
  
  public Node<E> getNext() {
    return next;
  }
  
  public void setNext(Node<E> next) {
    this.next = next;
  }
}

Using the generic Node class

Specify the element type of each Node ("String Node" vs "Integer Node").

  String[] names = {"Alice", "Bob", "Carol", "Doug"};
  Node<String> head = new Node<String>(names[0]);
  Node<String> current = head;
  for (int i = 1; i < names.length; i++) {
    current.setNext(new Node<String>(names[i]));
    current = current.getNext();
  }

  //at this point, head points to the start of the list
  for (Node<String> n = head; n != null; n = n.getNext()) {
    System.out.print(n.getData() + " ");
  }
  System.out.println();

Generic stack implementation

public class Stack<E> {

  private Node<E> top;
  private int size;

  public Stack() {
    this.top = null;
    this.size = 0;
  }

  public void push(E item) {
    this.top = new Node<E>(item, top);
    this.size++;
  }  

  public E peek() {
    if (this.top == null) {
      throw new IllegalStateException("Can't peek() into empty stack.");
    }
    return this.top.getData();
  }
  
  public E pop() {
    if (this.top == null) {
      throw new IllegalStateException("Can't pop() from empty stack.");
    }
    E data = this.top.getData();
    this.top = this.top.getNext();
    this.size--;
    return data;
  }  

  public int size() {
    return this.size;
  }  
}

And last week's code with generic stack...

  Stack<String> s = new Stack<String>();
  System.out.println(s.size());  //0
  s.push("Alice");
  s.push("Bob");
  s.push("Carol");
  System.out.println(s.size());  //3
  System.out.println(s.peek());  //Carol
  System.out.println(s.pop());   //Carol
  s.pop();
  System.out.println(s.peek());  //Alice
  System.out.println(s.size());  //1

But now can also do this:

  Stack<Integer> s = new Stack<Integer>();
  s.push(3);
  s.push(12);
  System.out.println(s.pop());

Cool! Stack will work with any type (object, not primitive, but can use wrapper classes).

More on generics

BTW: Good Java references

If you want to master the more advanced aspects of Java, I enjoyed:

Summary

For next time...