CSC383 Jan25

slide version

single file version

Contents

  1. Topics
  2. Some Timing Examples
  3. Array: Add to Front
  4. Node class
  5. Node List: Add to Front
  6. Add to Last
  7. Add to Last(cont.)
  8. Add to Last (cont.)
  9. Improving Add to Last
  10. Add to First Revised
  11. Add to First Revised (cont.)
  12. Add to Last Revised
  13. Remove Front
  14. Remove Last
  15. Doubly Linked Nodes
  16. Boundary Cases
  17. Head and Tail Guard Nodes
  18. Singly Linked with Guard Nodes: Add to Front
  19. Boundary Case
  20. Remove Last
  21. Doubly Linked: Add to Last
  22. Doubly Linked: Remove Front
  23. Doubly Linked: Remove Last
  24. Doubly Linked: Remove Current
  25. Recursion 1: Listing Directories (and all its subdirectories...)
  26. Recursion 2: Listing Directories (the recursive code)

Topics[1] [top]

Some Timing Examples[2] [top]

Sorting algorithms:

Two ways to evaluate:

  1. Count operations for some model and determine which standard function best describes the number of these operations

  2. Run Doubling Experiments

  3. Run Experiments to compare pairs of algorithms

Array: Add to Front[3] [top]

To add the value 3 to the front of the array arr (assuming arr is big enough):

arr[0] 5
arr[1] 10
arr[2] 15
arr[3] ?

We would need to move the existing 3 values:

arr[0] 3
arr[1] 5
arr[2] 10
arr[3] 15

Node class[4] [top]

A Node class for a singly linked list.

private class Node
{
  public E data;
  public Node next;

  public Node(E x)
  {
    data = x;
    next = null;
  }
}

Node List: Add to Front[5] [top]

To add the value 3 to the front of the Node List (we don't have to make any assumptions about the list being big enough!):

we have to

Add to Last[6] [top]

Adding a new value, say 20, after the current last value is more work. We need to

The problem is to get a reference to Node 15.

Node 10's .next works, but that means we need a reference to Node 10, and so on.

We have to begin with start.

Declare Node variable p and set it initially equal to start.

The keep updating p to point to the next Node until it points to the last Node (15 in this case).

In general the last Node is the one whose .next field is equal to null.

Add to Last(cont.)[7] [top]

Add to Last (cont.)[8] [top]

The complete code:

Node tmp = new Node(20);   // Create a Node for the new value 20
Node p = start;            // Have to begin at the first node

if ( p == null ) {         // start will be null if there are no Nodes yet
  start = tmp;             // If so, make start point to the new Node
} else {

  while( p.next != null ) {// Otherwise, keep moving p to the next Node
    p = p.next;            // until p points to the last Node (where .next is null)
  }
  p.next = tmp;            // Set the next field of the current last Node to the new Node
}
    

This version of add has complexity O(N), where N is the number of data items in the list.

Improving Add to Last[9] [top]

With a little more book keeping, we can avoid having to update a Node variable, p, one Node at a time from the start Node to get to a reference to the last Node.

In addition to start, maintain a Node variable last that is null if the list is empty and otherwise references the last data Node.

Add to First Revised[10] [top]

Making this change requires revising both the Add to Front and Add to Last

Here is the revised Add to First code for adding value 3 to the front in pictures:

Empty List
Before After
Non-empty List
Before After

Add to First Revised (cont.)[11] [top]

The complete code:

Node tmp = new Node(3);
tmp.next = start;
if ( start == null ) {
   last = tmp;
}
start = tmp;
    

It is easy to get this wrong by not updating last when the list was empty!

The complexity of this version is O(1)

Add to Last Revised[12] [top]

The revised Add to Last code is similar, but it is now much more efficient than the initial version of Add to Last (The number of operations is fixed and doesn't depend on the length of the list):

Node tmp = new Node(20);
if (last == null) {
   start = last = tmp;
} else {
  last.next = tmp;
  last = tmp;
}      
    

Remove Front[13] [top]

Removing the first value and assigning it to a variable x is easy, but we have to be careful if the list is empty or if it has only one data value:

public E removeFront()
{
  E x;
  if ( start == null ) {        // case 1: empty list
    throw new NoSuchElementException();
  } else if ( start == last ) { // case 2: only 1 element
    x = start.data;
    start = last = null;     
  } else {                      // case 3: more than 1 element   
    x = start.data;
    start = start.next;
  }
  return x;
}
    

(Draw a before and after picture for each of the cases!)

Remove Last[14] [top]

For remove last we run into another efficiency problem. For the list below, start references the first Node (with data 3) and last references the last Node (with data 15).

But to remove 15, we need a reference to the previous Node (with value 10) so that last can point to it and its .next field can be set to null.

We could write a loop to begin at start again and move a Node variable p down to point to the next to last Node (with value 10 in this case). But that would make the running time of this operation depend on the current length of the list.

We could do this, but a better idea is to be able to move to the previous Node as well as the next Node just as i++ or i-- lets us move to the next or previous index in an array.

Doubly Linked Nodes[15] [top]


private class Node
{
  public E data;  /* E declared in the containing class */
  public Node next;
  public Node prev;

  public Node(E d)
  {
    data = d;
    next = null;
    prev = null;
  }
}

Boundary Cases[16] [top]

Using doubly linked nodes can solve the problem of removing the last element efficiently.

Using doubly linked nodes does a bit more code to update both next and prev members instead of just next.

But both doubly linked lists as well as singly linked lists have to be careful to handle boundary cases:

One addition to the structure will allow these boundary cases to be handled with the same code as non boundary cases.

The change is to add two guard nodes: one at the beginning before the first data node and the other after the last data node.

This has the benefit that every data node will have a node before it and one after. In particular this is true even for the first data node and for the last data node.

Head and Tail Guard Nodes[17] [top]

The head and tail nodes below are the guard nodes:

Empty List: size 0

Non-Empty List: size 3

Singly Linked with Guard Nodes: Add to Front[18] [top]

The revised Add to Front for a list of doubly linked Nodes.

Add data value 3 to this list:

we have to

Boundary Case[19] [top]

The code for this example that adds a new node to the front of the list was:

    Node tmp = new Node(3);

    tmp.next = head.next;
    head.next = tmp;      
    

Does this work for the boundary case where the list is empty??

Remove Last[20] [top]

This is a problem for a singly linked list whether we have guard nodes or not.

What is needed is to change to doubly linked Nodes.

But this means we have to redo all the previous operations that were already easy.

Doubly Linked: Add to Last[21] [top]

Although we are using both head and tail nodes, adding to the front didn't have to explicitly reference tail!

This makes it easier to write correct code than in the case without guards.

Similarly, adding to the end doesn't need to explicitly reference head.

Write the code for addLast(E x) method below for a doubly linked list with 2 guard nodes - head and tail.

(Assume head and tail are members of the list class.)

E.g., addLast(20) should 20 to the end of this list:

to get

 public void addLast(E x)
{
        
}
    

Doubly Linked: Remove Front[22] [top]

This is just as easy and doesn't have to consider the case of size() == 1 separately. The same code should work size = 0 as well as for any size > 0.

Write the code for removeFront(E x) method below for a doubly linked list with 2 guard nodes - head and tail. It should return the element that was removed.

(Assume head and tail are members of the list class.)

E.g., removeFront() should 10 from this list:

to get

 public E removeFront()
{
        
}
    

Doubly Linked: Remove Last[23] [top]

This is just as easy and is left as an exercise.

public E removeLast()
{
  
}      
    

Doubly Linked: Remove Current[24] [top]

Quite frequently it happens that a reference to a Node in a doubly linked Node list will be available (it doesn't matter how for the present discussion) and that Node is to be deleted from the list.

For example, suppose a Node variable p is available somehow that references Node with value 10 below.

Only the two links in red need to change:

 
--
3 --
 
 
--
10 --
 
 
--
15 --
 

The code to delete Node p is easier to understand at first by introducing variables to reference the previous Node with value 3 and following Node with value 15.

These are the Nodes whose .next and .prev fields need to change respectively.

 
Node before = p.prev;
Node after = p.next;

before.next = after;
after.prev = before;
    
 
--
3 --
 
 
--
15 --
 

or the Node names before and after can be replaced by p.prev and p.next respectively to get the very short code:

p.prev.next = p.next;
p.next.prev = p.prev;      
    

Recursion 1: Listing Directories (and all its subdirectories...)[25] [top]

The File class used in creating Scanner's, BufferedReaders, etc. has some nifty methods:

Recursion 2: Listing Directories (the recursive code)[26] [top]

public class ListDirs
{
  private static PrintStream ps = System.out;

  
  public static void listAll(String dirName)
  {
    listAll(dirName, "+");
  }
  
  private static void listAll(String dirName, String prefix)
  {
    File f = new File(dirName);
    File[] entries = f.listFiles();

    for(int i = 0; i < entries.length; i++) {
      ps.printf("%s %s\n", prefix, entries[i].getName());
      if ( entries[i].isDirectory()) {
	listAll(dirName + "/" + entries[i].getName(), 
		"   " + prefix);
      }
    }
  }
  
  public static void main(String[] args)
  {
    String dir;

    if ( args.length > 0 ) {
      dir = args[0];
    } else {
      dir = ".";
    }
    listAll(dir);
  }

}