Lecture 3
Linked Lists, Recursion
Pointers
The concept of pointers is a central theme in data structures. Java implements pointers implicitly through reference-type variables. Recall that all non-primitive variables in Java are references to objects. For example, the statement
Point coord1 = new Point(25, 50);
declares and instantiates a new
Point object. The variable coord1 is a reference (i.e., implicit pointer) to the object.

The above illustration is an example of pointer diagramming notation, which we shall use extensively throughout the course. This notation only shows the instance variables in an object and not the methods.
If we now declare and instantiate another
Point object, then we will have two reference variables and two objects, as shown below.Point coord2 = new Point();
coord2.x = coord1.y;
coord2.y = coord1.x;

Assigning a reference variable to refer to another merely causes the reference value to point to the target object.
coord1 = coord2;
Note that the above operation does not create a new
Point object. The original object (25,50) is no longer referenced by any variable and will eventually be reclaimed by the garbage collector.

Linked Lists
Linked-List Node
A linked list is constructed from a collection of self-referential nodes which may be defined as follows:
class
SLNode {private Object element; // element stored in this node
private SLNode next; // reference to the next node in the list
:
}

SLNode
constructors and methods
A
SLNode object can be created to point to the next object in the list.public
SLNode(Object e, SLNode n) { //# create a node given element and nextelement = e;
next = n;
}
An alternative constructor has only one argument for the item; the
next field is set to null.public SLNode(Object element)
{
this.element = element;
this.next = null;
//optional}
An update method allows modification of the
next field.public void setNext(SLNode newNext)
{
next = newNext;
}
Another update method allows modification of the
element field.public void setElement(Object newElem) { element = newElem; }
An access method returns the value of the
next reference.public
Another access method returns the object stored in the
element field.
Building a linked list from
SLNode objectsSLNode myNode = new SLNode("MD");

myNode.setNext(new ListNode("VA"));

myNode.getNext().setNext(new ListNode("DC"));

We could have built the same list with a single command.
ListNode myNode = new ListNode("MD", new ListNode("VA", new ListNode("DC")));
Linked-List Animation

Abstract Data Types (ADTs)

Scope
Today we concentrate on
Topics to be covered later in the course
BoundaryViolationException javadoc
Consider Consequence of "Obvious" Implementation

Removal from head
tmp <--- head
head <--- head.getNext()
elem <--- tmp.getElement()
tmp.setNext(null)
return elem

newNode.setNext(head)
head <--- newNode

Insertion at tail
tail.setNext(newNode)
tail <--- tail.getNext()
tail.setNext(null)

Deletion from tail
Can’t be done without incurring O(n) operations.
The Need for a Sentinel Node
Consider inserting at the position prior to the current node.

We would either have to traverse the list up to
current or create a new temporary node in which the contents of current are copied. The latter solution is O(1) so we can opt for it.tmp.setElement(current.getElement());
tmp.setNext(current.getNext());
current.setElement(newNode.getElement());
current.setNext(tmp);
But what if we wanted to delete the last node?

In this case, there is no way to avoid traversing the entire list, since we need to make a change to the node storing "D".
So we adopt the convention that "precursor" points to the node prior to current. The insertion point is in front of current and beyond precursor.
But this new convention leads to problems when removing the first element in the list, inserting in front of the first element in the list, and lists of one or fewer elements.

The solution is to use a sentinel, or header node.

Linked-list implementation of SimpleList ADT
Store header, tail, and current references to point to front of list, end of list, and current position, respectively.
public
{
// Instance variables:
SLNode header, precursor, tail;
:::
Two features of the implementation are subtle and require elaboration. The precursor reference actually points to the node prior to the target node, rather than the target node itself. This use of precursor avoids expensive deletions at the end of the list (O(n) if current points to target node). All data movements are Q (1). The other feature is the use of an empty header or sentinel node. When precursor points to the previous node, insertions and deletions at the front of the list become an annoying special case. The sentinel node avoids this problem by providing a previous node for all cases. We shall see other examples of sentinel nodes later. The initial state of the linked list is empty.
public LinkedSimpleList()
{
header = new SLNode();
precursor = tail = header;
}
Inserting and appending nodes to a linked list
We insert new nodes at the position between
precursor and precursor.next. Insertion is thus a simple splice and can be applied to any position.
{
if (precursor == null )
throw new BoundaryViolationException( "Linked list insertion error" );
else
{
SLNode newNode =
new SLNode(newElem, precursor.getNext());precursor.setNext(newNode);
if (precursor == tail)
tail = precursor.getNext();
}
}
The
append() method appends a new SLNode to the end of the linked list, i.e., at tail.next.public void append(Object newElem)
{
SLNode newNode =
new SLNode(newElem);tail.setNext(newNode);
tail = newNode;
}
Removing and retrieving items
Basic deletion is a bypass in the linked list. We assume that
precursor.next points to the node to be removed.
//------------------------------------------------------------
// Removes the SLNode at the current position, i.e., at
// precursor.next. Returns the object that was stored in the node
// that was removed.
public Object remove() throws BoundaryViolationException
{
if(!isInList())
{
throw new BoundaryViolationException(
"Linked list error removing current node");
}
else
{
Object retElem = precursor.getNext().getElement();
if(tail == precursor.getNext()) tail = precursor;
precursor.setNext(precursor.getNext().getNext());
return retElem;
}
}
Methods for class
LinkedSimpleList
//------------------------------------------------------------
// Returns the object stored at the current position, precursor.next.
public Object getCur() throws BoundaryViolationException
{
if (!isInList())
{
throw new BoundaryViolationException(
"Error getting current elem");
}
else
{
Object retElem = precursor.getNext().getElement();
return retElem;
}
}
//------------------------------------------------------------
// Copies the formal parameter elem to the SLNode at the current
// position, precursor.next. Throws a BoundaryViolationException if
// the current position does not refer to a non-null node.
public void setCur(Object elem) throws BoundaryViolationException
{
if (!isInList())
{
throw new BoundaryViolationException(
"Error getting current elem");
}
else
{
precursor.getNext().setElement(elem);
}
}
//------------------------------------------------------------
// Resets the linked list to the initial state.
public void clear()
{
header.setNext(
null);precursor = tail = header;
}
//------------------------------------------------------------
// Sets the precursor position to point to the first node in the list.
// Since precursor points to the node prior to the current node,
// it is set to point to the heaser node.
public void setFirst()
{
precursor = header;
}
//------------------------------------------------------------
// Assigns the current position to the last node in the list. As such,
// precursor is set to point to the node prior to tail.
public void setLast()
{
if(!isEmpty())
{
if(!isInList())
precursor = header;
while(precursor.getNext() != tail) advance();
}
}
//------------------------------------------------------------
// Sets precursor to point to tail. Afterwards, isInList will return
// false because the current position, precursor.next, will be null.
// This method may be useful for the purpose of appending new nodes
// to the list in conjunction with the insert method. However, this
// method should probably be avoided, since a separate append method
// has been provided.
public void setTail()
{
precursor = tail;
}
//------------------------------------------------------------
// Advances the precursor position by one elem. Throws a
// BoundaryViolationException if the current position is outside
// the boundaries of the list.
public void advance() throws BoundaryViolationException
{
if(!isInList())
{
throw new BoundaryViolationException("Error advancing linked list");
}
else
precursor = precursor.getNext();
}
//------------------------------------------------------------
// Returns the number of elems in the list.
// Is this method efficient?
public int length()
{
SLNode tmp;
int count = 0;
tmp = header.getNext();
while(tmp != null)
{
count++;
tmp = tmp.getNext();
}
return count;
}
//------------------------------------------------------------
// Returns true is the list has no elements, true if it has at least
// one element.
public boolean isEmpty()
{
return (header.getNext() == null);
}
//------------------------------------------------------------
// Returns true if the current position is a valid position in the
// list.
public boolean isInList()
{
return ((precursor != null) && (precursor.getNext() != null));
}
// Prints all objects in the list to standard output.
public void print()
{
for(setFirst();isInList();advance())
System.out.println(getCur());
}
Linked lists – discussion
Recursion
What is recursion?
A recursive definition is one that is defined in terms of itself. It must include a base case so that the recursion will eventually end.
Recursion in mathematics
Many mathematical functions have recursive definitions. Consider the factorial (!) operation on non-negative integers.

To illustrate,
Recursive methods in Java
Java methods may be recursively defined; that is, a Java method may contain one or more calls to itself. The programmer should be aware that every method call, recursive or otherwise, has certain overhead operations associated with it. When a method is invoked, information about that method (such as its actual parameters, local variables, and return value) are placed on the Java Virtual Machine method stack, also known as the Java stack, virtual stack or activation record. As such, recursive methods are often more expensive than their iterative equivalents. Recursion is nonetheless a useful programming tool and is encountered frequently in data structures and algorithms. Later in the course, we shall encounter highly effective algorithms for searching and sorting that make use of recursion.
Recursive factorial method
Recursive Call Trace, Factorial Method
Call traces help us understand the recursive process and analyze the complexity of recursive algorithms. Since each node of the factorial method’s call trace is O(1), the recursive invocation of factorial(n) is O(n).
Recursion and mathematical induction
Recall proofs by induction
Dangers of recursion: infinite regress
Consider the following poorly designed implementation of the factorial method:
Dangers of recursion: redundant calculations
Consider the Fibonacci number sequence, defined as
The sequence: {0,1,1,2,3,5,8,13,...}
where n is a non-negative integer. A method to generate Fibonacci numbers can be implemented recursively.
{
if(n <= 1) return n; //base case
else return fib(n-1) + fib(n-2);
}
Note the redundant calculations: 3 calls to fib(0), 5 calls to fib(1), and so on. Each recursive call does more and more redundant work, resulting in exponential growth.
Mutual recursion
So far we have only considered recursive methods that
call themselves. Another type of recursion involves methods that cyclically
call each other. This is known as cyclical or mutual recursion. In the
following example, methods A and B
are mutually recursive.
![]() |
void A(int n)
{ if (n <= 0) return; n--; B(n); } void B(int n)
|
Divide-and-conquer recursion
One of the more effective uses of recursion is through a class of algorithms known as divide and conquer.
![]()
Recursion Call Trace for ssq Method
Towers of Hanoi
At a remote temple somewhere in Asia, a group of monks is working to move 64 disks from the leftmost peg (peg A) to the rightmost peg (peg C). After all 64 disks have been moved, the universe will dissolve! When will this happen if each disk takes 1 second to move?
Rules of the puzzle:
Towers of Hanoi (2)
Consider the n=4 subproblem.

Step 1: move 3 disks from peg A to peg B
Towers of Hanoi (3)
Step 2: move 1 disk from peg A to peg C

Step 3: move 3 disks from peg B to peg C
Towers of Hanoi (4)
public static void tower(int n, char
start, char finish, char spare)
{
if (n<=1)
System.out.println("Moving
a disk from peg " + start +" to peg " +
finish);
else
{
tower(n-1,start,spare,finish);
System.out.println("Moving
a disk from peg " + start +" to peg " +
finish);
tower(n-1,spare,finish,start);
}
}
Note that the solution to this problem is a divide-and-conquer algorithm. Calling the above method with n=4, i.e., tower(4,'A','C','B') yields the following result.
Call trace for n=4:
numbers inside nodes are n, start, finish

Lafore Towers of
Hanoi applet (not available on public version)
Number of moves = 1 + 2 + 4 + 8.
For general n, number of moves =
.
===> O(2n) complexity
For n=64, number of moves = 264-1 or 1.844 x 1019.
Recall that one move takes one second.
===> Time left in universe = 1.844 x 1019 seconds = 5.85 x 1011 years.
Astronomers estimate the present age of the universe to be 20 billion (2 x 1010) years.