Data Structure

Doubly Linked List in Python

Pinterest LinkedIn Tumblr

A Doubly Linked List (DLL) is a type of linked list where each node contains a data part and two pointers: one pointing to the next node and the other pointing to the previous node. This allows traversal in both forward and backward directions, offering more flexibility compared to singly linked lists.

Structure of a Doubly Linked List Node

In a DLL, each node consists of three parts:

  1. Data: The value stored in the node.
  2. Previous Pointer: A pointer to the previous node.
  3. Next Pointer: A pointer to the next node.

Here’s a basic Python class representing a node in a doubly linked list:

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

Advantages and Disadvantages of Doubly Linked Lists

Advantages

  1. Bidirectional Traversal: Nodes can be traversed in both forward and backward directions.
  2. Easier Deletion: Deleting a node is easier as it doesn’t require traversal from the head.

Disadvantages

  1. Increased Memory Usage: Each node requires extra memory for the previous pointer.
  2. Complexity: The implementation is more complex compared to singly linked lists.

Insert a Node at the Beginning of a Doubly Linked List

To insert a node at the beginning of a DLL, we need to adjust the previous pointer of the current head and set the new node’s next pointer to the current head.

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

    def insert_at_beginning(self, data):
        new_node = Node(data)
        if self.head is None:
            self.head = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node

# Example usage
dll = DoublyLinkedList()
dll.insert_at_beginning(10)
dll.insert_at_beginning(20)

Insert a Node at the End of a Doubly Linked List

To insert a node at the end, we traverse to the last node and update its next pointer to the new node.

def insert_at_end(self, data):
    new_node = Node(data)
    if self.head is None:
        self.head = new_node
    else:
        temp = self.head
        while temp.next is not None:
            temp = temp.next
        temp.next = new_node
        new_node.prev = temp

# Adding method to the class
DoublyLinkedList.insert_at_end = insert_at_end

# Example usage
dll.insert_at_end(30)
dll.insert_at_end(40)

Insert a Node at a Given Position in a Doubly Linked List

Inserting a node at a specific position requires traversing to that position and updating the pointers accordingly.

def insert_at_position(self, data, position):
    if position == 0:
        self.insert_at_beginning(data)
        return

    new_node = Node(data)
    temp = self.head
    for _ in range(position - 1):
        if temp is None:
            raise IndexError("Position out of bounds")
        temp = temp.next

    new_node.next = temp.next
    new_node.prev = temp

    if temp.next:
        temp.next.prev = new_node
    temp.next = new_node

# Adding method to the class
DoublyLinkedList.insert_at_position = insert_at_position

# Example usage
dll.insert_at_position(25, 2)

Delete a Node in a Doubly Linked List

To delete a node, we update the pointers of the adjacent nodes to bypass the node to be deleted.

def delete_node(self, key):
    temp = self.head

    while temp and temp.data != key:
        temp = temp.next

    if temp is None:
        return

    if temp.prev:
        temp.prev.next = temp.next
    else:
        self.head = temp.next

    if temp.next:
        temp.next.prev = temp.prev

# Adding method to the class
DoublyLinkedList.delete_node = delete_node

# Example usage
dll.delete_node(25)

Delete the First Node of a Doubly Linked List

Deleting the first node involves updating the head pointer.

def delete_first_node(self):
    if self.head is None:
        return
    if self.head.next:
        self.head = self.head.next
        self.head.prev = None
    else:
        self.head = None

# Adding method to the class
DoublyLinkedList.delete_first_node = delete_first_node

# Example usage
dll.delete_first_node()

Delete the Last Node of a Doubly Linked List

To delete the last node, we traverse to the last node and update the previous node’s next pointer.

def delete_last_node(self):
    if self.head is None:
        return
    if self.head.next is None:
        self.head = None
        return

    temp = self.head
    while temp.next:
        temp = temp.next
    temp.prev.next = None

# Adding method to the class
DoublyLinkedList.delete_last_node = delete_last_node

# Example usage
dll.delete_last_node()

Delete a Node at a Given Position in a Doubly Linked List

Deleting a node at a specific position requires updating the pointers of the surrounding nodes.

def delete_at_position(self, position):
    if self.head is None:
        return

    temp = self.head
    for _ in range(position):
        if temp is None:
            raise IndexError("Position out of bounds")
        temp = temp.next

    if temp.prev:
        temp.prev.next = temp.next
    else:
        self.head = temp.next

    if temp.next:
        temp.next.prev = temp.prev

# Adding method to the class
DoublyLinkedList.delete_at_position = delete_at_position

# Example usage
dll.delete_at_position(1)

Search a Node in a Doubly Linked List

To search for a node, we traverse the list until we find the node with the desired value.

def search(self, key):
    temp = self.head
    while temp:
        if temp.data == key:
            return True
        temp = temp.next
    return False

# Adding method to the class
DoublyLinkedList.search = search

# Example usage
print(dll.search(20))  # Output: True
print(dll.search(100))  # Output: False

Traversing a Doubly Linked List Forward

Forward traversal involves iterating from the head to the end of the list.

def traverse_forward(self):
    temp = self.head
    while temp:
        print(temp.data, end=' ')
        temp = temp.next
    print()

# Adding method to the class
DoublyLinkedList.traverse_forward = traverse_forward

# Example usage
dll.traverse_forward()  # Output: 20 10 30 40

Traversing a Doubly Linked List Backward

Backward traversal requires iterating from the end to the head of the list.

def traverse_backward(self):
    temp = self.head
    while temp and temp.next:
        temp = temp.next
    while temp:
        print(temp.data, end=' ')
        temp = temp.prev
    print()

# Adding method to the class
DoublyLinkedList.traverse_backward = traverse_backward

# Example usage
dll.traverse_backward()  # Output: 40 30 10 20

Reverse a Doubly Linked List

Reversing a DLL involves swapping the next and previous pointers of each node.

def reverse(self):
    temp = None
    current = self.head

    while current:
        temp = current.prev
        current.prev = current.next
        current.next = temp
        current = current.prev

    if temp:
        self.head = temp.prev

# Adding method to the class
DoublyLinkedList.reverse = reverse

# Example usage
dll.reverse()
dll.traverse_forward()  # Output: 40 30 10 20

Applications of Doubly Linked Lists

Doubly linked lists are used in various applications such as:

  1. Navigating Backward and Forward: Implementing back and forward navigation in web browsers.
  2. Undo and Redo Operations: Used in applications that require undo and redo functionalities.
  3. Managing Cache Memory: Implementing Least Recently Used (LRU) caches.

Comparison with Singly Linked Lists

Singly Linked Lists

  1. Memory: Less memory usage as nodes have only one pointer.
  2. Traversal: Only supports forward traversal.

Doubly Linked Lists

  1. Memory: More memory usage due to two pointers.
  2. Traversal: Supports both forward and backward traversal.

Common Mistakes and Best Practices in Implementing Doubly Linked Lists

Common Mistakes

  1. Forgetting to Update Pointers: Ensure both previous and next pointers are updated correctly.
  2. Handling Edge Cases: Properly handle cases where the list is empty or contains only one node.

Best Practices

  1. Consistent Pointer Updates: Always update both previous and next pointers when modifying the list.
  2. Check for Null References: Ensure that you handle null references to avoid runtime errors.
  3. Modular Code: Break down operations into smaller methods for easier debugging and maintenance.

Conclusion

Doubly linked lists are versatile data structures that offer flexibility in traversal and modification. Understanding their implementation and applications can significantly enhance your programming skills and enable you to solve complex problems efficiently. With the provided examples and detailed explanations, you should have a solid foundation to implement and utilize doubly linked lists in various programming scenarios.

Write A Comment