Some of the topics we discussed today are covered in these sections of Problem Solving with Algorithms:
Here are some more notes.
A linked list is a common and useful data structure. It looks like this:
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