March 10, 2025
LinkedListself to reference the current instance of LinkedListLinkedList contains an instance of ListNode in _headaddfirst creates a new ListNode and updates _headremovefirst updates _head and returns the datagraph LR
Head["_head"] --> Node1["Node 1<br>data: 42"]
Node1 --> Node2["Node 2<br>data: 17"]
Node2 --> Node3["Node 3<br>data: 99"]
Node3 --> Null["None"]
classDef nodeStyle fill:#f9f9f9,stroke:#333,stroke-width:1px
classDef nullStyle fill:#f0f0f0,stroke:#999,stroke-dasharray: 5 5
class Node1,Node2,Node3 nodeStyle
class Null nodeStyle
class Head nodeStyle
ListNode object that contains:
data that stores the valuelink that points to the next ListNodelink references to the next node_head attribute points to the first node in the listListNode storing None marks the end of the linked listlink references!Queue with a LinkedListclass LinkedList:
def __init__(self):
self._head = None
def addfirst(self, item):
self._head = ListNode(item, self._head)
def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
currentnode = self._head
while currentnode.link is not None:
currentnode = currentnode.link
currentnode.link = ListNode(item)
def removefirst(self):
item = self._head.data
self._head = self._head.link
return item
def removelast(self):
if self._head.link is None:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link.link is not None:
currentnode = currentnode.link
item = currentnode.link.data
currentnode.link = None
return itemLinkedListLL = LinkedList()
LL.addfirst(3)
LL.addfirst(5)
print(LL.removefirst() == 5)
LL.addlast(9)
LL.addlast(13)
print(LL.removefirst() == 3)
print(LL.removefirst() == 9)
print(LL.removelast() == 13)True
True
True
True
LinkedList can be used to build a Queuewhile loops!ListQueueLinkedListListNode and LinkedList, draw a picture to show their relationship! Can you explain the True outputs?LinkedList?def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
currentnode = self._head
while currentnode.link is not None:
currentnode = currentnode.link
currentnode.link = ListNode(item)while currentnode.link is not None is not efficient!def removelast(self):
if self._head.link is None:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link.link is not None:
currentnode = currentnode.link
item = currentnode.link.data
currentnode.link = None
return itemwhile currentnode.link.link is not None efficient?removelast traverses all nodes to find second-to-last one!Stack, Queue, and Dequepush, pop, peekenqueue, dequeue, peekaddfirst, addlast, removefirst, removelastStack, Queue, and Deque
ListStack, InefficientListStack, RobustStackListQueueSimple, … , ListQueueListDequeLinkedList!Queue and/or Deque with a LinkedListLinkedList movement in both directionsQueue with a LinkedList?class LinkedListTrial:
def __init__(self):
self._head = None
self._tail = None
def addfirst(self, item):
self._head = ListNode(item, self._head)
if self._tail is None: self._tail = self._head
def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
self._tail.link = ListNode(item)
self._tail = self._tail.link
def removefirst(self):
item = self._head.data
self._head = self._head.link
if self._head is None: self._tail = None
return item
def removelast(self):
if self._head is self._tail:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link is not self._tail:
currentnode = currentnode.link
item = self._tail.data
self._tail = currentnode
self._tail.link = None
return itemLinkedListTrialLL = LinkedListTrial()
LL.addfirst(3)
LL.addfirst(5)
print(LL.removefirst() == 5)
LL.addlast(9)
LL.addlast(13)
print(LL.removefirst() == 3)
print(LL.removefirst() == 9)
print(LL.removelast() == 13)True
True
True
True
LinkedList can be used to build a Queuewhile loop!ListQueueLinkedListTrial concerns?def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
self._tail.link = ListNode(item)
self._tail = self._tail.linkwhile loop in addlastaddfirst is calledself._tail is updated and new node is addeddef removelast(self):
if self._head is self._tail:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link is not self._tail:
currentnode = currentnode.link
item = self._tail.data
self._tail = currentnode
self._tail.link = None
return itemwhile currentnode.link is not self._tail efficient?removelast still traverses nodes to perform a removal!LinkedListclass LinkedListPrime:
def __init__(self):
self._head = None
self._tail = None
self._length = 0
def addfirst(self, item):
self._head = ListNode(item, self._head)
if self._tail is None: self._tail = self._head
self._length += 1
def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
self._tail.link = ListNode(item)
self._tail = self._tail.link
self._length += 1
def removefirst(self):
item = self._head.data
self._head = self._head.link
if self._head is None: self._tail = None
self._length -= 1
return item
def removelast(self):
if self._head is self._tail:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link is not self._tail:
currentnode = currentnode.link
item = self._tail.data
self._tail = currentnode
self._tail.link = None
self._length -= 1
return item
def __len__(self):
return self._lengthLinkedListPrime__init__self variable enables access to the instance attributes_head and _tail attributes_length attribute to make it easy to track sizeLinkedListPrime instance at zero_length?__init__self._length = 0 is a \(O(1)\) operationaddfirst and addlastself._length += 1 is a \(O(1)\) operationremovefirst and removelastself._length -= 1 is a \(O(1)\) operationLinkedListLL = LinkedListPrime()
LL.addfirst(3)
LL.addfirst(5)
print(LL.removefirst() == 5)
LL.addlast(9)
LL.addlast(13)
print(LL.removefirst() == 3)
print(LL.removefirst() == 9)
print(LL.removelast() == 13)
print(len(LL) == 0)True
True
True
True
True
LinkedListPrime has features equivalent to LinkedListNewQueue-like structureLinkedQueue with a LinkedListclass LinkedQueue:
def __init__(self):
self._L = LinkedListPrime()
def enqueue(self, item):
self._L.addlast(item)
def dequeue(self):
return self._L.removefirst()
def peek(self):
item = self._L.removefirst()
self._L.addfirst(item)
return item
def display(self):
current = self._L._head
while current is not None:
print(current.data, end=" ")
current = current.link
def __len__(self):
return len(self._L)
def isempty(self):
return len(self) == 0LinkedQueuedef manipulate_queue():
queue = LinkedQueue()
queue.enqueue('umbrella')
queue.enqueue('backpack')
queue.enqueue('sandals')
print("Queue contents after enqueue operations:", end=" ")
queue.display()
queue.dequeue()
print("\nQueue contents after dequeue operation:", end=" ")
queue.display()
manipulate_queue()Queue contents after enqueue operations: umbrella backpack sandals
Queue contents after dequeue operation: backpack sandals
LinkedQueue is functionally equivalent to the ListQueueLinkedQueue uses display to reveal the contentsdef addfirst(self, item):
self._head = ListNode(item, self._head)
if self._tail is None: self._tail = self._head
self._length += 1
def addlast(self, item):
if self._head is None:
self.addfirst(item)
else:
self._tail.link = ListNode(item)
self._tail = self._tail.link
self._length += 1def removefirst(self):
item = self._head.data
self._head = self._head.link
if self._head is None: self._tail = None
self._length -= 1
return item
def removelast(self):
if self._head is self._tail:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link is not self._tail:
currentnode = currentnode.link
item = self._tail.data
self._tail = currentnode
self._tail.link = None
self._length -= 1
return itemremovelast method!def removelast(self):
if self._head is self._tail:
return self.removefirst()
else:
currentnode = self._head
while currentnode.link is not self._tail:
currentnode = currentnode.link
item = self._tail.data
self._tail = currentnode
self._tail.link = None
self._length -= 1
return itemwhile loop in removelast is a \(O(n)\) operationQueue using a LinkedList?LinkedList and its nodesLinkedListListNodeself.data stores the data value inside of the ListNodeself.prev stores a reference to the previous ListNodeself.link stores a reference to the next ListNodeListNode: for any two nodes a and b, it must be true that b == a.link if and only if a = b.prevDoublyLinkedListclass DoublyLinkedList:
def __init__(self):
self._head = None
self._tail = None
self._length = 0
def addfirst(self, item):
if len(self) == 0:
self._head = self._tail = ListNode(item, None, None)
else:
newnode = ListNode(item, None, self._head)
self._head.prev = newnode
self._head = newnode
self._length += 1
def addlast(self, item):
if len(self) == 0:
self._head = self._tail = ListNode(item, None, None)
else:
newnode = ListNode(item, self._tail, None)
self._tail.link = newnode
self._tail = newnode
self._length += 1
def __len__(self):
return self._lengthaddfirst and addlast!def _addbetween(self, item, before, after):
node = ListNode(item, before, after)
if after is self._head:
self._head = node
if before is self._tail:
self._tail = node
self._length += 1_addbetweenaddfirst and addlast methods call _addbetweenDoublyLinkedListclass DoublyLinkedList:
def __init__(self):
self._head = None
self._tail = None
self._length = 0
def __len__(self):
return self._length
def _addbetween(self, item, before, after):
node = ListNode(item, before, after)
if after is self._head:
self._head = node
if before is self._tail:
self._tail = node
self._length += 1
def addfirst(self, item):
self._addbetween(item, None, self._head)
def addlast(self, item):
self._addbetween(item, self._tail, None)
def _remove(self, node):
before, after = node.prev, node.link
if node is self._head:
self._head = after
else:
before.link = after
if node is self._tail:
self._tail = before
else:
after.prev = before
self._length -= 1
return node.data
def removefirst(self):
return self._remove(self._head)
def removelast(self):
return self._remove(self._tail)
def display(self):
current = self._head
while current is not None:
print(current.data)
current = current.nextDoublyLinkedList_addbetween method:
_head or _tail node as neededaddfirst and addlast call this method_remove method:
prev or link node as neededremovefirst and removelast call this methoddisplay method?def display(self):
current = self._head
while current is not None:
print(current.data)
current = current.nextdisplay method is a \(O(n)\) operationwhile loop traverses all nodes in the listdisplay!
DoublyLinkedListdef manipulate_doubly_linked_list():
DLL = DoublyLinkedList()
print(len(DLL))
DLL.addfirst(3)
DLL.addfirst(5)
print(DLL.removefirst() == 5)
DLL.addlast(9)
DLL.addlast(13)
print(DLL.removefirst() == 3)
print(DLL.removefirst() == 9)
print(DLL.removefirst() == 13)
print(len(DLL))
manipulate_doubly_linked_list()0
True
True
True
True
0
List or DoublyLinkedList?C = A + B concatenates the two lists together+ creates a new listC without modifying A or BDoublyLinkedList permitted, efficient concatenation!def __iadd__(self, other):
if other._head is not None:
if self._head is None:
self._head = other._head
else:
self._tail.link = other._head
other._head.prev = self._tail
self._tail = other._tail
self._length = self._length + other._length
other.__init__()
return self__iadd__!__iadd__ method is called when += is usedother DoublyLinkedList is concatenated to selfDoublyLinkedListL = DoublyLinkedList( **Key Insight**:)
[L.addlast(i) for i in range(11)]
B = DoublyLinkedList()
[B.addlast(i+11) for i in range(10)]
L += B
n = L._head
while n is not None:
print(n.data, end = ' ')
n = n.linkDoublyLinkedList L is concatenated with B+= calls the __iadd__ method in DoublyLinkedListB is “drained” of values when it is the other parameterDeque with a DoublyLinkedList?Deque, or a double-ended queue, will contain an instance of the DoublyLinkedList as was done previouslyDeque endsDeque are \(O(1)\) operations!Algorithmology