CS 181 - Data Structures

Static data structures = fixed length

Dynamic data structures = grow and shrink as needed during execution

We will implement data structures using classes, inheritance, composition

Self-Referencing Classes

Self-referencing class = one or more data members are object of that class

class ListNode { Object data; ListNode next; ... }

ListNode next usually referred to as "link" or "pointer" ... can be "next" in sense of sorted order, chronological order, priority, etc.

Self-referencing objects can be used for List, Stack, Queue, Tree

If link is null, no object follows

Dynamic Memory Allocation

Dynamic memory allocation necessary for dynamic data structures

Obtain needed memory, release unneeded memory during execution

Java operator "new" does dynamic memory allocation

ListNode work = new ListNode (data, next);

Linked List

List is Abstract Data Type used in many CS applications

List is collection of nodes each with link to next one

List has beginning link to first node

List has last node with "null" link

Insertions and deletions can be made anywhere in List

List is good choice when no idea about size of data structure or if may vary wildly during execution

class Vector is Java's version of dynamic Array

Tradeoff: List = dynamic; Array = no overhead for dynamically allocating memory, updating links

Operations:

insertAtFront -- put node at front of List

insertAtBack -- put node at end of List

removeFromFront -- remove node from front of List

removeFromBack -- remove node from end of List

isEmpty -- ask if List is empty

Alternative operations:

print -- list contents of List in order

insertAtMiddle -- put node somewhere in List

removeFromMiddle -- remove node from somewhere in List

reportFront -- read node from front of List

class ListNode { Object data; ListNode next; ListNode( Object o, ListNode nextNode ) { data = o; next = nextNode; } ListNode( Object o ) { this( o, null ); } Object getObject() { return data; } ListNode getnext() { return next; } } public class List { ListNode firstNode; ListNode lastNode; String name; public List( String s ) { name = s; firstNode = lastNode = null; } public List() { this( "list" ); } public void insertAtFront( Object insertItem ) { if ( isEmpty() ) firstNode = lastNode = new ListNode( insertItem ); else firstNode = new ListNode( insertItem, firstNode ); } public void insertAtBack( Object insertItem ) { if ( isEmpty() ) firstNode = lastNode = new ListNode( insertItem ); else lastNode = lastNode.next = new ListNode( insertItem ); } public Object removeFromFront() throws EmptyListException { Object removeItem = null; if ( isEmpty() ) throw new EmptyListException( name ); removeItem = firstNode.data; if ( firstNode.equals( lastNode )) firstNode = lastNode = null; else firstNode = firstNode.next; return removeItem; } public Object removeFromBack() throws EmptyListException { Object removeItem = null; if ( isEmpty() ) throw new EmptyListException( name ); removeItem = lastNode.data; if ( firstNode.equals( lastNode )) firstNode = lastNode = null; else { ListNode current = firstNode; while (current.next !=lastNode) current = current.next; lastNode = current; current.next = null; } return removeItem; } public boolean isEmpty() { return firstNode == null; } public void print() { if ( isEmpty() ) { System.out.println( "Empty " + name ); return; } System.out.print( "The " + name + " is: " ); ListNode current = firstNode; while ( current != null ) { System.out.print( current.data.toString() + " "); current = current.next; } System.out.println( "\n" ); } } public class EmptyListException extends RuntimeException { public EmptyListException( String name ) { super("The " + name + " is empty"); } } public class ListTest { public static void main() { List objList = new List(); Boolean b = new Boolean( true ); Character c = new Character('$'); Integer i = new Integer( 34567 ); String s = new String( "hello" ); objList.insertAtFront( b ); objList.print(); objList.insertAtFront( c ); objList.print(); objList.insertAtBack( i ); objList.print(); objList.insertAtBack( s ); objList.print(); Object removedObj; try { removedObj = objList.removeFromFront(); System.out.println( removedObj.toString() + " removed" ); objList.print(); removedObj = objList.removeFromFront(); System.out.println( removedObj.toString() + " removed" ); objList.print(); removedObj = objList.removeFromBack(); System.out.println( removedObj.toString() + " removed" ); objList.print(); removedObj = objList.removeFromBack(); System.out.println( removedObj.toString() + " removed" ); objList.print(); } catch ( EmptyListException e ) { System.err.println( "\n" + e.toString() ); } } }

Object class allows any kind of node in List

Each node should be object of class that has a toString() method

EmptyListException good way to handle that situation

Stack

Stack is Abstract Data Type used in many CS applications

Stack is like cafeteria trays

Last-In, First-Out (LIFO)

Stack = insertions and deletions can be made only at top of Stack

Operations:

push -- put node on top of Stack

pop -- remove node from top of Stack

isEmpty -- ask if Stack is empty

Alternative operations:

print -- list contents of Stack in order

top -- read node on top of Stack

Stack is constrained form of List

Implement Stack through List inheritance

public class Stack extends List { public Stack() { super( "stack" );} public void push( Object o ) { insertAtFront( o ); } public Object pop() throws EmptyListException { return removeFromFront(); } public boolean isEmpty() { return super.isEmpty(); } public void print(){super.print();} } public class StackTest { public static void main() { Stack objStack = new Stack(); Boolean b = new Boolean( true ); Character c = new Character('$'); Integer i = new Integer( 34567 ); String s = new String( "hello" ); objStack.push( b ); objStack.print(); objStack.push( c ); objStack.print(); objStack.push( i ); objStack.print(); objStack.push( s ); objStack.print(); Object removedObj = null; try { while ( true ) { removedObj = objStack.pop(); System.out.println( removedObj.toString() + " popped" ); objStack.print(); } } catch ( EmptyListException e ) { System.err.println( "\n" + e.toString() ); } } }

List contains other methods (insertAtBack, removeFromBack) that could be used...

objStack.insertAtBack (a);

Sometimes disadvantage of implementing through inheritance

Implement Stack through List composition

public class Stack { List s; public Stack() { s = new List( "stack" ); } public void push( Object o ) { s.insertAtFront( o ); } public Object pop() throws EmptyListException { return s.removeFromFront(); } public boolean isEmpty() { return s.isEmpty(); } public void print() { s.print(); } }

Composition hides methods of List that we do not want

objStack.insertAtBack (a);

...now is compile-time error

Queue

Queue is Abstract Data Type used in many CS applications

Queue is like ticket line

Queue = insertions made at back (tail) and deletions made at front (head) of queue

First-In, First-Out (FIFO)

Operations:

enqueue -- put node at rear of queue

dequeue -- remove node from front of queue

isEmpty -- ask if queue is empty

Alternative operations:

print -- list contents of Queue in order

front -- read node from front of queue

public class Queue { List q; public Queue() { q = new List( "queue" ); } public void enqueue( Object o ) { q.insertAtBack( o ); } public Object dequeue() throws EmptyListException { return q.removeFromFront(); } public boolean isEmpty() { return q.isEmpty(); } public void print() { q.print(); } }

Tree

Tree = data structure with links to one or more sub-trees

Binary tree = tree with links to at most two sub-trees

Refer to sub-trees as left child and right child

Node with no children is leaf node

Binary tree can make searching and sorting more efficient

Binary search tree = nodes in left sub-tree "less than" this node and nodes in right sub-tree "greater than" this node

class TreeNode { TreeNode left; int data; TreeNode right; public TreeNode( int d ) { data = d; left = right = null; } public void insert( int d ) { if ( d < data ) { if ( left == null ) left = new TreeNode( d ); else left.insert( d ); } else if ( d > data ) { if ( right == null ) right = new TreeNode( d ); else right.insert( d ); } } } public class Tree { TreeNode root; public Tree() { root = null; } public void insertNode( int d ) { if ( root == null ) root = new TreeNode( d ); else root.insert( d ); } public void inorderTraversal() { inorderHelper( root ); } private void inorderHelper( TreeNode node ) { if ( node == null ) return; inorderHelper( node.left ); System.out.print(node.data + " "); inorderHelper( node.right ); } }

Binary search tree facilitates duplicate elimination

Tightly packed binary tree = each level has about twice as many nodes as previous level

More Complex Data Structures

Doubly-linked lists = usually next and previous links

Multiply-linked lists = each node is on several lists simultaneously