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
TrueLinkedList 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
TrueLinkedList 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
TrueLinkedListPrime 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
0List 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