Linked List

There are 3 basic types of linked list:

  • singly linked list

  • circular linked list

  • doubly linked list

singly linked list

Singly linked list is composed by nodes.

Each node has 2 parts:

  • data

  • pointer: It points to the next node.

There are 2 special nodes.

  • head node: It's the first node in the linked list. Usually, we set the head node as a node without data to make sure a linked list has ONE node at least.

  • tail node: It's the last node. It points to Null.

Different from Array, Singly linked list is good at inserting and deleting an item. The time complexity is O(1). But it's terrible to find one. The time complexity is O(n).

circular linked list

It's a special singly linked list. The tail node points to the head node.

doubly linked list

It's a special singly linked list, too. Each node has 2 pointers which point to the previous node and next node.

It's widely used in practice. It makes inserting and deleting easier.

For exemple, we use doubly linked list to create LRU Caching.

In the exemple, cache is a doubly linked list. Head node is a node without data. Tail node is the least used node. We remove the tail when the cache is full.

class LinkedNode:
    def __init__(self, key, val):
        self.key = key
        self.val = val
        self.next = None
        self.prev = None

class LRUCache:
        
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.head = LinkedNode(None, None)
    
    def _move_to_head(self,pointer: LinkedNode) -> None:
        pointer.next = self.head.next
        if self.head.next is not None:
            self.head.next.prev = pointer
        self.head.next = pointer
        pointer.prev = self.head
    
    def _remove(self, pointer: LinkedNode) -> LinkedNode:
        prev_node = pointer.prev
        next_node = pointer.next
        prev_node.next = next_node
        if next_node is not None:
            next_node.prev = prev_node
        
        return pointer
        
        

    def get(self, key: int) -> int:
        pointer = self.head.next
        
        while pointer is not None:
            if pointer.key == key:
                pointer = self._remove(pointer)
                self._move_to_head(pointer)
                return pointer.val
            
            pointer = pointer.next
        
        return -1
    
    def put(self, key: int, value: int) -> None:
        pointer = self.head.next
        tail = None
        
        while pointer is not None:
            if pointer.key == key:
                pointer = self._remove(pointer)
                self._move_to_head(pointer)
                pointer.val = value
                return None
            if pointer.next is None:
                tail = pointer
            pointer = pointer.next
        
        after_head = LinkedNode(key, value)
        self._move_to_head(after_head)
        self.capacity -= 1
        
        if self.capacity < 0:
            before_tail = tail.prev
            before_tail.next = None
            tail.prev = None
            self.capacity = 0
        
        return None

There are some typical problems.