Linked List vs. Array List Worksheet Solutions

Written by: Prof. Nathan Sprague

Tracing Linked List Code

The insertAtPos method below is broken. Assuming that the figure below represents the state of memory, draw the state of memory after list.insertAtPos(1, "C") is called. How should the method be fixed?

   head                                    tail
    |                                        |
    |                                        |
    v                                        v
+--------+    +--------+   +--------+    +--------+
| null |-|--> | "A"  |-|-->| "B"  |-|--> | null |x|
+--------+    +--------+   +--------+    +--------+


public boolean insertAtPos(int pos, E it) {

    // Return false if the position is invalid
    if ((pos < 0) || (pos > listSize)) {
        return false;
    }

    // Find the insertion node
    Link<E> current = head.next();
    for (int i = 0; i < pos; i++) {
        current = current.next();
    }

    // Insert
    Link<E> newLink = new Link<E>(it, current);
    current.setNext(newLink);
    if (tail == current) {
        tail = current.next(); // New tail
    }

    listSize++;
    return true;

}


Analyzing List Algorithms

Analyze each of the methods below according to the instructions in the Javadoc comments.

Code Listing 1:

/*
 * What is the big-Theta running time of this method, assuming that list is
 *
 *
 * an ArrayList:
 *
 *
 *
 * a LinkedList:
 *
 *
 *
 */
public static int sumByIndex(List<Integer> list) {

    int sum = 0;
    for (int i = 0; i < list.size(); i++) {
        sum += list.get(i); // Theta(1) for ArrayList
                            // Theta(i) for LinkedList
    }
    return sum;
}

ArrayList: $\Theta(n)$

LinkedList: $\Theta(n^2)$

The analysis for LinkedList is a little complicated. The get operation requires $\Theta(i)$ time to access the $i$th element. (The get method will begin at the head, and follow references until it reaches posiiton $i$.) This means that the first time through the loop will require that we follow 0 references, the second 1, the third 2 etc. Overall, we need to step through $\sum_{i=0}^{n-1}i$ references. As we saw earlier, this sum is $\Theta(n^2)$. This same general reasoning will apply to all of the examples below where the operation in the loop grows from 1 to n or shrinks from n to 1 over the course of iteration.

(Actually, the fact that Java’s LinkedList class uses a doubly-linked list means that the analysis above is slightly wrong. The get operation follows something like $\min(i, n-i)$ references, since it starts from the tail if the index is past the halfway point. This doesn’t change the bit-$\Theta$ running time.)

Code Listing 2:

/*
 * What is the big-Theta running time of this method, assuming that list is
 *
 *
 * an ArrayList:
 *
 *
 *
 * a LinkedList:
 *
 *
 *
 */
public static int sumWithIterator(List<Integer> list) {

    int sum = 0;
    for (int curValue : list) { // Each access Theta(1) for both List types.
        sum += curValue;
    }
    return sum;

}

ArrayList: $\Theta(n)$

LinkedList: $\Theta(n)$

Answering this question requires us to keep in mind that for-each loops in Java make use of the iterator provided by the collection.

It is safe to assume that the iterator provided by the LinkedList class will store a reference to the most recently accessed node. It won’t re-start from the head each time next is called.

Code Listing 3:

/*
 * What is the big-Theta running time of this method, assuming that toList
 * is initially empty, and that both lists are of type
 *
 *
 * ArrayList:
 *
 *
 *
 * LinkedList:
 *
 *
 *
 */
public static void copy(List<Integer> fromList,
                        List<Integer> toList) {

    for (int item : fromList) {
        toList.add(item); // ArrayList:  Theta(1) (Amortized)
                          // LinkedList: Theta(1)
    }

}

ArrayList: $\Theta(n)$

LinkedList: $\Theta(n)$

Again, this is doubly-linked, so adding at the end is a constant time operation.

Code Listing 4:

/*
 * What is the big-Theta running time of this method, assuming that toList
 * is initially empty, and that both lists are of type
 *
 *
 * ArrayList:
 *
 *
 *
 * LinkedList:
 *
 *
 *
 */
public static void copyReverseA(List<Integer> fromList,
                                List<Integer> toList) {

    for (int item : fromList) {
        toList.add(0, item); //  ArrayList: Theta(n)   (Where n represents
                             //      the current size of toList)
                             //  LinkedLst: Theta(1)
    }

}

ArrayList: $\Theta(n^2)$

LinkedList: $\Theta(n)$

Code Listing 5:

/*
 * What is the big-Theta running time of this method, assuming that toList
 * is initially empty, and that both lists are of type
 *
 *
 * ArrayList:
 *
 *
 *
 * LinkedList:
 *
 *
 *
 */
public static void copyReverseB(List<Integer> fromList,
                                List<Integer> toList) {
    int value;
    for (int i = fromList.size() - 1; i >= 0; i--) {
        value = fromList.get(i); // Theta(1) for ArrayList
                                 // Theta(i) for LinkedList



        toList.add(value);  // Theta(1) for ArrayList (amortized)
                            // Theta(1) for LinkedList
    }

}

ArrayList: $\Theta(n)$

LinkedList: $\Theta(n^2)$

Code Listing 6:

/*
 * What is worst-case the big-Theta running time of this method, assuming
 * that toList is initially empty, and that both lists are of the indicated
 * type. Describe the worst case.
 *
 *
 * ArrayList:
 *
 *
 *
 * LinkedList:
 *
 *
 *
 * What is average-case the big-Theta running time of this method, assuming
 * that toList is initially empty, and that both lists are of the indicated
 * type.
 *
 *
 * ArrayList:
 *
 *
 *
 * LinkedList:
 *
 *
 *
 */
public static void copyScramble(List<Integer> fromList,
                                List<Integer> toList) {

    Random gen = new Random();
    int randomIndex;
    int value;
    for (int i = 0; i < fromList.size(); i++) {
        randomIndex = gen.nextInt(fromList.size());
        value = fromList.remove(randomIndex);
        toList.add(value);
    }
}

Worst case ArrayList: $\Theta(n^2)$

The worst case happens in the (unlikely) event that every random index is 0. This means that all of the remove operations will be a the beginning and will require that all elements be shifted to the left (which requires \Theta(n) steps).

Worst case LinkedList: $\Theta(n^2)$

The worst case happens in the (unlikely) event that every random index is in the dead center of the list. Removal from either end is efficient, but removal from the middle requires the LinkedList to follow references from the head.

This would lead to a summation like $\sum_{i=0}^{n-1}\lfloor \frac{n-i}{2} \rfloor$ which is $\Theta(n^2)$

Average case ArrayList: $\Theta(n^2)$

Average case LinkedList: $\Theta(n^2)$

For both, the average case is about twice as fast as the worst case. For example, the worst case for ArrayList occurred when the element was always removed from position 0, and $n$ elements needed to be shifted. On average, this cost will be closer to $n/2$: Sometimes all of the elements need to be shifted, sometimes none, on average about half.