Doubly Linked List


A doubly linked list is a type of linked list in which each node contains a data element and two pointers, one pointing to the next node in the sequence (like a regular linked list) and another pointing to the previous node. This bidirectional linking enables easy traversal in both directions.

Here's a breakdown of the doubly linked list and various operations:

Doubly Linked List Structure:

A node in a doubly linked list typically looks like this in a C-style struct:

Doubly Linked List Node Structure

struct Node 
{ int data; 
struct Node* next; 
struct Node* prev; 
};
data: Holds the actual data of the node.
next: Points to the next node in the list.
prev: Points to the previous node in the list.



Doubly Linked List Operations:

Insertion:

At the beginning (prepend):
Create a new node.
Set its next to the current head.
Set the prev of the current head to the new node.
Update the head to the new node.


struct Node* insertAtBeginning(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = head;
    newNode->prev = NULL;

    if (head != NULL) {
        head->prev = newNode;
    }

    return newNode;
}

At the end (append):
Create a new node.
Set its prev to the current tail.
Set the next of the current tail to the new node.
Update the tail to the new node.

struct Node* insertAtEnd(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = head;

    newNode->data = data;
    newNode->next = NULL;

    if (head == NULL) {
        newNode->prev = NULL;
        return newNode;
    }

    while (last->next != NULL) {
        last = last->next;
    }

    last->next = newNode;
    newNode->prev = last;

    return head;
}


In the middle (insert after a specific node):
Create a new node.
Set its next to the next node of the specified node.
Set its prev to the specified node.
Update the next of the specified node to the new node.
Update the prev of the next node to the new node.


struct Node* insertAfter(struct Node* head, struct Node* prevNode, int data) {
    if (prevNode == NULL) {
        printf("Previous node cannot be NULL");
        return head;
    }

    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = prevNode->next;
    newNode->prev = prevNode;
    prevNode->next = newNode;

    if (newNode->next != NULL) {
        newNode->next->prev = newNode;
    }

    return head;
}


Deletion:
Image credit:geeksforgeeks

At the beginning:
Update the next of the head to the next node.
Update the prev of the new head to NULL.
Free the memory of the old head.

struct Node* deleteAtBeginning(struct Node* head) {
    if (head == NULL) {
        printf("List is empty. Nothing to delete.\n");
        return NULL;
    }

    struct Node* temp = head;
    head = temp->next;

    if (head != NULL) {
        head->prev = NULL;
    }

    free(temp);
    return head;
}



At the end:
Update the prev of the tail to the previous node.
Update the next of the new tail to NULL.
Free the memory of the old tail.

struct Node* deleteAtEnd(struct Node* head) {
    if (head == NULL) {
        printf("List is empty. Nothing to delete.\n");
        return NULL;
    }

    struct Node* temp = head;
    while (temp->next != NULL) {
        temp = temp->next;
    }

    if (temp->prev != NULL) {
        temp->prev->next = NULL;
    } else {
        head = NULL;
    }

    free(temp);
    return head;
}


In the middle (delete a specific node):
Update the next of the previous node to the next node of the specified node.
Update the prev of the next node to the previous node of the specified node.
Free the memory of the specified node.

struct Node* deleteNode(struct Node* head, struct Node* delNode) {
    if (head == NULL || delNode == NULL) {
        printf("List is empty or node to be deleted is NULL.\n");
        return head;
    }

    if (delNode->prev != NULL) {
        delNode->prev->next = delNode->next;
    } else {
        head = delNode->next;
    }

    if (delNode->next != NULL) {
        delNode->next->prev = delNode->prev;
    }

    free(delNode);
    return head;
}



Traversal(Display):
Traverse the list from the head to the tail or vice versa using the next and prev pointers.

void traverse(struct Node* head) {
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d<--> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

Search:
Traverse the list and compare the data of each node with the target value until the node is found or the end of the list is reached.

Advantages of Doubly Linked List:
    Bidirectional Traversal: Easy traversal in both directions.
    Insertion and Deletion: Efficient insertion and deletion operations at both ends and in the middle.
    We can quickly insert a new node before a given node.
    In a singly linked list, to delete a node, a pointer to the previous node is needed. To get this previous     node, sometimes the list is traversed. In DLL, we can get the previous node using the previous        
    pointer  
Disadvantages:
    Extra Memory: Requires extra memory for the prev pointers.
    All operations require an extra pointer previous to be maintained. For example, in insertion, we need      to modify previous pointers together with the next pointers.
Complexity: 
    Slightly more complex implementation compared to singly linked lists.
    Algorithms Complexity:
    Insertion/Deletion at the beginning or end: O(1)
    Insertion/Deletion in the middle: O(n) (where n is the number of nodes)
    Search: O(n)

These complexities are based on the assumption that you have a reference to the node where you want to perform insertion, deletion, or search. If you need to search for a node first, additional time will be required.


Doubly Linked List- C program Implementation
/************************************************************************/
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
};



struct Node* insertAtBeginning(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = head;
    newNode->prev = NULL;

    if (head != NULL) {
        head->prev = newNode;
    }

    return newNode;
}

struct Node* insertAtEnd(struct Node* head, int data) {
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = head;

    newNode->data = data;
    newNode->next = NULL;

    if (head == NULL) {
        newNode->prev = NULL;
        return newNode;
    }

    while (last->next != NULL) {
        last = last->next;
    }

    last->next = newNode;
    newNode->prev = last;

    return head;
}

struct Node* insertAfter(struct Node* head, struct Node* prevNode, int data) {
    if (prevNode == NULL) {
        printf("Previous node cannot be NULL");
        return head;
    }

    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = prevNode->next;
    newNode->prev = prevNode;
    prevNode->next = newNode;

    if (newNode->next != NULL) {
        newNode->next->prev = newNode;
    }

    return head;
}

struct Node* deleteAtBeginning(struct Node* head) {
    if (head == NULL) {
        printf("List is empty. Nothing to delete.\n");
        return NULL;
    }

    struct Node* temp = head;
    head = temp->next;

    if (head != NULL) {
        head->prev = NULL;
    }

    free(temp);
    return head;
}

struct Node* deleteAtEnd(struct Node* head) {
    if (head == NULL) {
        printf("List is empty. Nothing to delete.\n");
        return NULL;
    }

    struct Node* temp = head;
    while (temp->next != NULL) {
        temp = temp->next;
    }

    if (temp->prev != NULL) {
        temp->prev->next = NULL;
    } else {
        head = NULL;
    }

    free(temp);
    return head;
}

struct Node* deleteNode(struct Node* head, struct Node* delNode) {
    if (head == NULL || delNode == NULL) {
        printf("List is empty or node to be deleted is NULL.\n");
        return head;
    }

    if (delNode->prev != NULL) {
        delNode->prev->next = delNode->next;
    } else {
        head = delNode->next;
    }

    if (delNode->next != NULL) {
        delNode->next->prev = delNode->prev;
    }

    free(delNode);
    return head;
}

void traverse(struct Node* head) {
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d<--->", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");
}

int main() {
    struct Node* head = NULL;

    head = insertAtEnd(head, 10);
    head = insertAtBeginning(head, 5);
    head = insertAfter(head, head->next, 7);
    head = insertAfter(head, head->next, 100);
    head = insertAfter(head, head, 200);
    printf("Doubly Linked List: ");
    traverse(head);

    head = deleteNode(head, head->next); // Delete the node with data 5

    printf("Doubly Linked List after deletion: ");
    traverse(head);

    head = deleteAtBeginning(head);
    printf("Doubly Linked List after deletion at the beginning: ");
    traverse(head);

    head = deleteAtEnd(head);
    printf("Doubly Linked List after deletion at the end: ");
    traverse(head);

    return 0;
}




Comments

Popular posts from this blog

Data Structures CST 201 KTU Third Semester Syllabus Notes and Solved Questions- Dr Binu V P 984739060

Stack

Quick Sort