Post

Created by @oliverrichards
 at November 6th 2023, 9:03:04 pm.

Question: Design a doubly linked list class that supports the following operations: insert at head, insert at tail, insert at index, delete at index, get element at index, get length of the linked list, and print the linked list.

Answer:

To design a doubly linked list, we will create the DoublyLinkedList class that contains the following methods:

  1. __init__(): Initializes an empty doubly linked list, setting both the head and tail pointers to None, and the length to 0.

  2. insert_at_head(value): Inserts a new node with the given value at the beginning of the linked list. This method has a time complexity of O(1).

  3. insert_at_tail(value): Inserts a new node with the given value at the end of the linked list. This method has a time complexity of O(1).

  4. insert_at_index(value, index): Inserts a new node with the given value at the specified index in the linked list. This method has a time complexity of O(n) in the worst-case scenario, where n is the number of elements in the linked list.

  5. delete_at_index(index): Deletes the node at the specified index in the linked list. This method has a time complexity of O(n) in the worst-case scenario.

  6. get_element_at_index(index): Returns the value of the node at the specified index in the linked list. This method has a time complexity of O(n) in the worst-case scenario.

  7. get_length(): Returns the number of nodes in the linked list. This method has a time complexity of O(1).

  8. print_list(): Prints the values of all nodes in the linked list.

Implementation of the DoublyLinkedList class:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
        self.prev = None

class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
        self.length = 0

    def insert_at_head(self, value):
        new_node = Node(value)
        if self.head is None:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node
        self.length += 1

    def insert_at_tail(self, value):
        new_node = Node(value)
        if self.tail is None:
            self.head = new_node
            self.tail = new_node
        else:
            self.tail.next = new_node
            new_node.prev = self.tail
            self.tail = new_node
        self.length += 1

    def insert_at_index(self, value, index):
        if index < 0 or index > self.length:
            print('Invalid index')
            return
        if index == 0:
            self.insert_at_head(value)
            return
        if index == self.length:
            self.insert_at_tail(value)
            return
        
        new_node = Node(value)
        curr_node = self.head
        for _ in range(index - 1):
            curr_node = curr_node.next
        
        new_node.next = curr_node.next
        new_node.prev = curr_node
        curr_node.next.prev = new_node
        curr_node.next = new_node
        self.length += 1

    def delete_at_index(self, index):
        if index < 0 or index >= self.length:
            print('Invalid index')
            return
        if index == 0:
            self.head = self.head.next
            if self.head is None:
                self.tail = None
            else:
                self.head.prev = None
        elif index == self.length - 1:
            self.tail = self.tail.prev
            self.tail.next = None
        else:
            curr_node = self.head
            for _ in range(index):
                curr_node = curr_node.next
            curr_node.prev.next = curr_node.next
            curr_node.next.prev = curr_node.prev
        self.length -= 1

    def get_element_at_index(self, index):
        if index < 0 or index >= self.length:
            print('Invalid index')
            return
        curr_node = self.head
        for _ in range(index):
            curr_node = curr_node.next
        return curr_node.value

    def get_length(self):
        return self.length

    def print_list(self):
        curr_node = self.head
        while curr_node:
            print(curr_node.value, end=' ')
            curr_node = curr_node.next
        print()

Example Usage:

dll = DoublyLinkedList()
dll.insert_at_head(1)
dll.insert_at_head(2)
dll.insert_at_tail(3)
dll.insert_at_index(4, 1)
dll.print_list()
# Output: 2 4 1 3
print(dll.get_element_at_index(2))
# Output: 1
dll.delete_at_index(0)
dll.print_list()
# Output: 4 1 3
print(dll.get_length())
# Output: 3

Explanation: The DoublyLinkedList class maintains two pointers, head and tail, to keep track of the first and last nodes of the linked list, respectively. The length variable stores the number of nodes in the list.

The insert_at_head() method inserts a new node at the beginning of the list. It creates a new_node with the given value and updates the head pointer accordingly.

The insert_at_tail() method inserts a new node at the end of the list. It creates a new_node with the given value and updates the tail pointer accordingly.

The insert_at_index() method inserts a new node at the specified index in the list. It traverses the list to find the node before the specified index, and then updates the pointers to include the new_node.

The delete_at_index() method deletes the node at the specified index in the list. It handles the edge cases where the node to be deleted is at the head or tail position separately.

The get_element_at_index() method returns the value of the node at the specified index in the list. It traverses the list until it reaches the desired index and returns the value of that node.

The get_length() method returns the length of the list by simply returning the length variable.

The print_list() method traverses the list from head to tail and prints the value of each node.

The example usage demonstrates the operations on the doubly linked list, such as inserting nodes at different positions, deleting a node, retrieving the value at a specific index, and printing the list.