Introduction to Algorithms
Lecture 7: Notes

Some of the topics we discussed today are covered in these sections of Problem Solving with Algorithms:

Here are some more notes.

linked lists

A linked list is a common and useful data structure. It looks like this:

%3

Like an array, a linked list can hold a sequence of elements. But it performs quite differently from an array. We can access the j-th element of an array in constant time for any j, but inserting or deleting an element at the beginning of an array or in the middle takes time O(N), where N is the length of the array. Conversely, accessing the j-th element of a linked list takes time O(j), but insertions and deletions take O(1).

An element of a linked list is called a node. A node contains one or more values, plus a pointer to the next node in the list. The first node of a linked list is called its head. The last node of a linked list is its tail. The tail always points to None (in Python, or its equivalent such as nil in other languages).

By the way, we will sometimes illustrate a linked list more compactly:

2 → 4 → 7 → None

The two pictures above denote the same structure; the first is simply more detailed.

Note that a Python list is not a linked list! A Python list is an array. :)

Here is a node type for a linked list in Python:

class Node:
  def __init__(self, val, next):
    self.val = val
    self.next = next

We can build the 3-element linked list pictured above as follows:

r = Node(7, None)
q = Node(4, r)
p = Node(2, q)

Now p refers to the head of the list.

Let's build a simple class LinkedList that illustrates common operations on linked lists. A LinkedList will have a single attribute head that points to the head of the list. Initially it is None:

class LinkedList:
  def __init__(self):
    self.head = None

We can easily prepend a value to a linked list. We allocate a new node to hold the value, and set the node's next pointer to the current head of the list. Then we update the head of the list to point to the new node:

  def prepend(self, val):
    n = Node(val, self.head)
    self.head = n

Typically we combine the preceding operations into a single step, so we can write the method in a single line:

  def prepend(self, val):
    self.head = Node(val, self.head)

We can traverse a linked list using a local variable that initially points to the head of the list. In a loop, we advance this variable to point to each node in turn. Here is a method that will print all values in a LinkedList:

  def printAll(self):
    p = self.head
    while p != None:
      print(p.val)
      p = p.next

Using the preceding methods, let's create a new LinkedList, prepend some values, and print them all out:

>>> l = LinkedList()
>>> for i in range(1, 6):
...   l.prepend(i)
>>> l.printAll()
5
4
3
2
1

To append a value to a linked list, if we only have a pointer to the head (as in the LinkedList class), then we generally need to traverse to the end of the list to find the last node. We then set the last node's next pointer to a new node containing the value to append.

However, that will not work if the LinkedList is empty, since then there is no last node. In this case, we set self.head to point to a new node with the new value:

  def append(self, val):
    n = Node(val, None)
    if self.head == None:
      self.head = n
    else:
      p = self.head
      while p.next != None:
        p = p.next
      p.next = n

This method runs in O(N) in the worst case, where N is the number of elements in the list. Alternatively, if we keep a pointer to the tail (last node) in the list, then we can append in O(1).

To delete a value from a linked list, we generally need to traverse down the list to find the node p that precedes the node we wish to delete. We then assign

p.next = p.next.next

This makes the node chain exclude the node we are deleting. (And then Python's garbage collector will realize that there are no more pointers to that node, and will reclaim its memory.)

If the node we are deleting is the first in the list, then there is no preceding node, so we must handle this as a special case.

Here is a LinkedList method to delete a value from a list:

  # Delete the node (if any) with the given value.
  def delete(self, val):
    if self.head == None:
      return    # list is empty
    if self.head.val == val:
      self.head = self.head.next   # delete first node
      return
    p = self.head
    while p.next != None:
      if p.next.val == val:
        p.next = p.next.next   # delete the node after p
        return
      p = p.next