Doubly Linked Lists

COS2611 - Programming: Data Structures · Linked Lists

Doubly Linked Lists

A doubly linked list is a type of data structure that consists of nodes. Each node contains three components: a data field, a pointer to the next node, and a pointer to the previous node. This structure allows for traversal in both directions: forward and backward. Doubly linked lists are useful when you need to access elements from both ends or perform operations that require bidirectional traversal.

Structure of a Doubly Linked List

Each node in a doubly linked list has the following structure:

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

In this structure:

  • data: This is the value stored in the node.
  • next: This is a pointer that points to the next node in the list.
  • prev: This is a pointer that points to the previous node in the list.

Creating a Doubly Linked List

To create a doubly linked list, you need to define the head of the list. The head is a pointer to the first node. If the list is empty, the head will be null.

Node* head = NULL;

You can create a new node using the following function:

Node* createNode(int value) {  Node* newNode = new Node();  newNode->data = value;  newNode->next = NULL;  newNode->prev = NULL;  return newNode;}

Inserting Nodes in a Doubly Linked List

You can insert nodes at different positions in a doubly linked list: at the beginning, at the end, or in the middle.

Inserting at the Beginning

To insert a node at the beginning, you need to adjust the pointers of the new node and the existing head:

void insertAtBeginning(Node** head, int value) {  Node* newNode = createNode(value);  newNode->next = *head;  if (*head != NULL) {    (*head)->prev = newNode;  }  *head = newNode;}

Inserting at the End

To insert a node at the end, you need to traverse the list until you reach the last node:

void insertAtEnd(Node** head, int value) {  Node* newNode = createNode(value);  if (*head == NULL) {    *head = newNode;    return;  }  Node* last = *head;  while (last->next != NULL) {    last = last->next;  }  last->next = newNode;  newNode->prev = last;}

Inserting in the Middle

To insert a node in the middle, you need to find the appropriate position:

void insertAfter(Node* prevNode, int value) {  if (prevNode == NULL) {    return;  }  Node* newNode = createNode(value);  newNode->next = prevNode->next;  prevNode->next = newNode;  newNode->prev = prevNode;  if (newNode->next != NULL) {    newNode->next->prev = newNode;  }}

Remember: Always update both the next and prev pointers when inserting in a doubly linked list.

Deleting Nodes in a Doubly Linked List

Deleting nodes requires careful pointer management to avoid memory leaks. You can delete a node from the beginning, the end, or a specific position.

Deleting from the Beginning

To delete from the beginning, you need to adjust the head pointer:

void deleteFromBeginning(Node** head) {  if (*head == NULL) {    return;  }  Node* temp = *head;  *head = (*head)->next;  if (*head != NULL) {    (*head)->prev = NULL;  }  delete temp;}

Deleting from the End

To delete from the end, you need to traverse to the last node:

void deleteFromEnd(Node** head) {  if (*head == NULL) {    return;  }  Node* last = *head;  while (last->next != NULL) {    last = last->next;  }  if (last->prev != NULL) {    last->prev->next = NULL;  } else {    *head = NULL;  }  delete last;}

Deleting a Specific Node

To delete a specific node, you need to adjust the pointers of the adjacent nodes:

void deleteNode(Node** head, Node* delNode) {  if (*head == NULL || delNode == NULL) {    return;  }  if (*head == delNode) {    *head = delNode->next;  }  if (delNode->next != NULL) {    delNode->next->prev = delNode->prev;  }  if (delNode->prev != NULL) {    delNode->prev->next = delNode->next;  }  delete delNode;}

Watch out: Do not forget to free the memory allocated for the deleted node to avoid memory leaks.

Traversing a Doubly Linked List

To traverse a doubly linked list, you can start from the head and move to the next node or start from the tail and move to the previous node.

void traverseForward(Node* head) {  Node* current = head;  while (current != NULL) {    printf("%d ", current->data);    current = current->next;  }}
void traverseBackward(Node* tail) {  Node* current = tail;  while (current != NULL) {    printf("%d ", current->data);    current = current->prev;  }}

Applications of Doubly Linked Lists

Doubly linked lists have various applications, including:

  • Implementing complex data structures like deques (double-ended queues).
  • Managing memory in applications that require frequent insertions and deletions.
  • Creating navigation systems in applications that require back and forth traversal.

Summary

  • A doubly linked list consists of nodes with data, next, and previous pointers.
  • Nodes can be inserted at the beginning, end, or middle of the list.
  • Nodes can be deleted from the beginning, end, or a specific position.
  • Traversal can be done in both forward and backward directions.

Check your understanding

  • What are the main components of a doubly linked list node?
  • How do you insert a node at the end of a doubly linked list?
  • What is the procedure to delete a specific node from a doubly linked list?
  • List one application of a doubly linked list.