Fundamentals of Programming II Linked Lists Computer Science 112 Fundamentals of Programming II Linked Lists
The List Clan AbstractCollection AbstractList ListInterface * TwoWayNode LinkedList ArrayList Array
Insert into An Array List def insert(self, i, item): """Inserts the item at position i.""" self._resize() if i < 0: i = 0 elif i > len(self): i = len(self) if i < len(self): for j in range(len(self), i, -1): self._items[j] = self._items[j - 1] self._items[i] = item self._size += 1 self.incModCount() No precondition on the index, can insert after the last item The index is normalized: 0 <= i <= len(self)
Problems with Arrays Must waste memory, especially if the structure is less than half full When the structure becomes full, must grow memory to resize, which is O(n) in memory and running time Insertions and removals are O(n) on average, due to shifting of items
Linked Structures to the Rescue Uses memory only as needed, physical size grows or shrinks with logical size in constant time Insertions and removals require no shifting of items
Problems with Linked Structures Appending, which is constant time for arrays, is linear for a singly linked structure newNode 1 head probe 5 4 3 2
Solution: Add a Tail Pointer Maintain a tail pointer, which is either None or points to the last node in the structure tail is a second external pointer, which is None when the structure is empty newNode 1 head tail 5 4 3 2
Solution: A Tail Pointer newNode = Node(1, None) if head == None: head = newNode else: tail.next = newNode tail = newNode newNode 1 head tail 5 4 3 2
Other Problems Links only go one in one direction, so movement in the other direction is difficult and expensive Insertions and removals require access to the previous node’s next field tail pointer won’t help to remove the last node Insertions and removals at the head of the structure are special cases that complicate the code
Other Linked Structures head D1 D2 D3 D4 A singly linked structure permits movement in one direction only head D1 D2 D3 A doubly linked structure permits movement in both directions and simplifies some operations
Doubly Linked Structures head D1 D2 D3 A circular, doubly linked structure with a dummy header node permits movement in both directions allows constant-time access to the head or tail eliminates special cases in code when access is at the beginning or the end of the structure
An Empty Structure head When there are no data, there is a single dummy header node Its two links point ahead and back to itself Its data field is None
The Node Class P D N Strictly a utility class class TwoWayNode(object): def __init__(self, data, previous = None, next = None): self.data = data self.previous = previous self.next = next P D N Strictly a utility class No need for accessor or mutator methods
Declaring an External Pointer class TwoWayNode(object): def __init__(self, data, previous = None, next = None): self.data = data self.previous = previous self.next = next head = TwoWayNode(None) head.previous = head.next = head head
Appending a Node temp head “A” head.previous always points to the last node The last node’s next pointer always points back to head temp = TwoWayNode("A", head.previous, head) # Step 1 temp head “A”
Appending a Node temp head “A” head.previous always points to the last node The last node’s next pointer always points back to head temp = TwoWayNode("A", head.previous, head) # Step 1 head.previous.next = temp # Step 2 temp head “A”
Appending a Node temp head “A” head.previous always points to the last node The last node’s next pointer always points back to head temp = TwoWayNode("A", head.previous, head) # Step 1 head.previous.next = temp # Step 2 head.previous = temp # Step 3 temp head “A”
Analysis temp head “A” No loop is needed to locate the last node Append is a constant time operation! No if statements are needed to check for special cases temp = TwoWayNode("A", head.previous, head) # Step 1 head.previous.next = temp # Step 2 head.previous = temp # Step 3 temp head “A”
Data for LinkedList self._head from node import TwoWayNode from abstractlist import AbstractList class LinkedList(AbstractList): """A linked list implementation.""" # Constructor def __init__(self, sourceCollection = None): """Sets the initial state of self, which includes the contents of sourceCollection, if it's present.""" self._head = TwoWayNode(None) self._head.previous = self._head.next = self._head AbstractList.__init__(self, sourceCollection) self._head
Index-Based Operations For __getitem__, __setitem__, insert, and pop Need to locate the ith node in the linked structure Define a helper method named _getNode Takes an index as an argument and returns the node at that position
Defining _getNode Usage: def _getNode(self, i): """Helper method: returns a pointer to the node at position i.""" probe = self._head.next while i > 0: probe = probe.next i -= 1 return probe Usage: ithNode = self._getNode(i) # Then access data, next, or previous in ithNode
Defining [] (subscript for access) def __getitem__(self, i): """Precondition: 0 <= i < len(self) Returns the item at position i. Raises: IndexError.""" if i < 0 or i >= len(self): raise IndexError("List index out of range") ithNode = self._getNode(i) return ithNode.data Usage: item = lyst[i] What is the running time of [] for a linked list?
Insertion into a Linked List 2 1 self._head D1 D2 lyst.insert(1, D3) The data nodes have virtual index positions, starting at 0 The header node has a virtual index position of the length of the list No special cases of insertions at head or tail, no if statements!
Insertion into a Linked List 2 1 self._head D1 D2 lyst.insert(1, D3) ithNode Get a pointer to the ith node
Insertion into a Linked List 2 1 self._head D1 D2 lyst.insert(1, D3) ithNode newNode D3 Get a pointer to the ith node Create a new node with the ith node’s previous as the previous and the ith node as the next
Insertion into a Linked List 2 1 self._head D1 D2 lyst.insert(1, D3) ithNode newNode D3 Get a pointer to the ith node Create a new node with the ith node’s previous as the previous and the ith node as the next Reset the ith node’s previous.next to the new node
Insertion into a Linked List 2 1 self._head D1 D2 lyst.insert(1, D3) ithNode newNode D3 Get a pointer to the ith node Create a new node with the ith node’s previous as the previous and the ith node as the next Reset the ith node’s previous.next to the new node Reset the ith node’s previous to the new node
Defining insert What is the running time of insert for a linked list? def insert(self, i, item): """Inserts the item at position i.""" if i < 0: i = 0 elif i > len(self): i = len(self) ithNode = self._getNode(i) ithNode = TwoWayNode(item, ithNode.previous, ithNode) ithNode.previous.next = ithNode ithNode.previous = ithNode self._size += 1 self.incModCount() What is the running time of insert for a linked list?
For Wednesday List iterators