For working professionals
For fresh graduates
More
I've spent countless hours exploring various techniques to efficiently organize information. One structure that consistently stands out is the doubly linked list data structure. As a programmer, I find myself reaching for doubly linked lists in a surprising number of scenarios. Today, I'm here to share my experience and equip you with a comprehensive understanding of this powerful data structure.
A doubly linked list's nodes, or members, are linear data structures with three fields each:
This two-way navigation allows for efficient insertion. It also helps to delete, and traverse in both forward and backward directions.
Doubly linked list example (C++)
C++
struct Node {
int data;
Node* prev;
Node* next;
};
This code defines a Node structure with an integer data field and pointers to the prev and next nodes.
Doubly linked lists support various operations:
Let's explore some common operations with doubly linked list examples.
The provided code defines a function insertAtBeginning. It inserts a new node at the beginning of a doubly linked list data structure. It also acts as a main function that demonstrates its usage.
C++
void insertAtBeginning(Node** head_ref, int new_data) {
// Allocate memory for a new node
Node* new_node = new Node;
new_node->data = new_data;
new_node->prev = NULL;
// Make the new node the head
new_node->next = *head_ref;
// Change prev of current head to new node
if (*head_ref != NULL) {
(*head_ref)->prev = new_node;
}
// Update the head reference
*head_ref = new_node;
}
int main() {
Node* head = NULL;
insertAtBeginning(&head, 5);
insertAtBeginning(&head, 4);
insertAtBeginning(&head, 3);
// Print the list (forward traversal)
Node* temp = head;
while (temp != NULL) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
// Output: 3 4 5
}
This code inserts nodes 3, 4, and 5 at the beginning of an initially empty list. The output (3 4 5) demonstrates the successful forward traversal.
The accompanying C++ code adds nodes to the end of a doubly linked list program in data structure and then prints it. Here's a breakdown of the code and what it produces:
C++
void insertAtEnd(Node** head_ref, int new_data) {
// Allocate memory for a new node
Node* new_node = new Node;
new_node->data = new_data;
// Handle the empty list case
if (*head_ref == NULL) {
new_node->prev = NULL;
new_node->next = NULL;
*head_ref = new_node;
return;
}
// Traverse to the last node
Node* last = *head_ref;
while (last->next != NULL) {
last = last->next;
}
// Update last node's next and new node's prev
last->next = new_node;
new_node->prev = last;
new_node->next = NULL; // Since it's the last node
}
int main() {
Node* head = NULL;
insertAtEnd(&head, 5);
insertAtEnd(&head, 10);
insertAtEnd(&head, 15);
// Print the list (forward traversal)
Node* temp = head;
while (temp != NULL) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
// Output: 5 10 15
}
This code inserts nodes 5, 10, and 15 at the end of an initially empty list. The output (5 10 15) confirms successful insertion.
The accompanying C++ code specifies the deleteAtBeginning function. If removes the first node from a doubly linked list. It also shows how it is used in the main function. Here's a breakdown of the code and the intended results:
C++
void deleteAtBeginning(Node** head_ref) {
// Handle empty list case
if (*head_ref == NULL) {
return;
}
// Store the head node
Node* temp = *head_ref;
// Update head reference if there's more than one node
if (temp->next != NULL) {
temp->next->prev = NULL;
*head_ref = temp->next;
} else {
// If it's the only node, set head to NULL
*head_ref = NULL;
}
// Deallocate memory of the deleted node
delete temp;
}
int main() {
Node* head = NULL;
insertAtBeginning(&head, 5);
insertAtBeginning(&head, 4);
insertAtBeginning(&head, 3);
deleteAtBeginning(&head);
// Print the list (forward traversal)
Node* temp = head;
while (temp != NULL) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
// Output: 4 5
}
This code deletes the first node (value 3) from the list. The output (4 5) shows the successful deletion.
The accompanying C++ code provides the deleteAtEnd function. It removes the last node from a doubly linked list data structure and shows how it is used in the main function. Here's a breakdown of the code and the intended results:
C++
void deleteAtEnd(Node** head_ref) {
// Handle empty list case
if (*head_ref == NULL) {
return;
}
// Traverse to the last node
Node* last = *head_ref;
while (last->next != NULL) {
last = last->next;
}
// Handle deletion of the only node
if (last == *head_ref) {
*head_ref = NULL;
} else {
// Update the prev pointer of the second-last node
last->prev->next = NULL;
}
// Deallocate memory of the deleted node
delete last;
}
int main() {
Node* head = NULL;
insertAtEnd(&head, 5);
insertAtEnd(&head, 10);
insertAtEnd(&head, 15);
deleteAtEnd(&head);
// Print the list (forward traversal)
Node* temp = head;
while (temp != NULL) {
std::cout << temp->data << " ";
temp = temp->next;
}
std::cout << std::endl;
// Output: 5 10
}
Use code with caution.
This code removes the last node (value 15) from the list. The output (5 10) verifies the deletion.
Doubly linked lists allow for traversing the list in both directions due to the prev pointer:
The provided C++ code snippet iterates through a doubly linked list program in data structure and prints the data of each node. Here's a breakdown of what the code does:
C++
Node* temp = head;
while (temp != NULL) {
// Access data in temp
std::cout << temp->data << " ";
temp = temp->next;
}
This code iterates through the list, starting from the head node. It prints the data in each node (temp->data) until it reaches the end (indicated by a NULL next pointer).
The provided C++ code snippet iterates through a doubly linked list in data structure program. It then prints the data of each node, but this time in reverse order. Here's a breakdown of what the code does:
C++
Node* temp = *head_ref; // Assuming we have a tail pointer
while (temp != NULL) {
// Access data in temp
std::cout << temp->data << " ";
temp = temp->prev;
}
This code starts from the last node (assuming we have a tail pointer) and iterates backward. It accesses data and moves to the prev node until it reaches the head node (indicated by a NULL prev pointer).
There are two primary ways to search for elements in a doubly linked list:
We iterate through the list, comparing the data in each node with the search key. If a match is found, we return the node or its position.
C++
Node* search(Node* head, int key) {
Node* temp = head;
while (temp != NULL) {
if (temp->data == key) {
return temp;
}
temp = temp->next;
}
return NULL; // Search key not found
}
Use code with caution.
The search function takes the head of the list and the search key as input. It iterates through the list, comparing the data in each node with the key. If a match is found, the function returns a pointer to that node. Otherwise, it returns NULL if the key is not present.
Random access is accessing an element based on its position. It is less efficient in doubly linked lists than it is in arrays. You would need to go through the list from beginning to end until you reach the desired location and access the element's contents. This strategy may be less efficient for frequent random access operations.
There are variations of the basic doubly linked list in data structure program that offer specific advantages:
The final node's next pointer in a circular doubly linked list points back to the head node. This forms a closed loop in the list. This structure can be useful for applications like implementing a round-robin scheduler.
A closed loop is created in a circular doubly linked list data structure when the final node's next pointer points back to the head node. This structure can be useful for tasks such as building a round-robin scheduler or depicting a continuous flow.
While doubly linked lists offer advantages, there are pitfalls to be aware of:
Failure to deallocate memory after removing nodes from the list can result in memory leaks. This happens because the memory utilized by the removed node is still allocated but inaccessible to your software.
Solution: Always use delete (or related functions in other languages) to free memory associated with removed nodes after updating pointers during deletion operations.
Here's a doubly linked list example of a memory leak:
C++
void deleteNode(Node* delNode) {
// Incorrect implementation (memory leak)
// delNode is removed from the list structure, but its memory isn't deallocated
// ... (list manipulation code)
}
Mistakes in managing pointers might result in unexpected behavior or crashes. Common issues are:
Solution: Handle pointer updates carefully while inserting, deleting, and traversing. Double-check your reasoning to ensure that pointers appropriately reflect the list's structure. Tools such as debuggers and print statements are used to visually represent pointer values during development.
Here's an doubly linked list example of incorrect pointer modification:
C++
void insertAtEnd(Node** head_ref, int new_data) {
// Incorrect implementation (may break the list)
Node* new_node = new Node;
new_node->next = NULL;
// This should point to the last node, but it's left unassigned
new_node->prev = NULL;
*head_ref = new_node; // This makes the new node the head, potentially breaking the list if there were existing nodes
}
By understanding these common mistakes and following best practices, you can work with doubly linked lists and avoid potential issues in your code.
Doubly linked list data structures are functional data structures useful for scenarios that demand forward and backward traversals. Understanding the essential ideas, operations, and trade-offs of doubly linked lists compared to other data structures will allow you to use them effectively in your programming activities.
1. What is the structure declaration of a doubly linked list?
The structure declaration includes fields for data and two pointers (prev and next). It refers to the previous and next nodes in the list.
2. Is a doubly linked list a unidirectional data structure?
No, a doubly linked list data structure is bidirectional. Because the previous pointer is present, you can navigate the list both forward and backward.
3. What is a doubly circular linked list in data structure?
A closed loop is created in a circular doubly linked list data structure when the final node's next pointer points back to the head node.
4. What is a doubly linked list also called?
Doubly linked lists are also sometimes called two-way lists. This name highlights their key characteristics. Each node in the list has pointers to both the previous and the next node. It makes for efficient traversal in both forward and backward directions.
5. What are the 4 types of linked lists?
Common linked list types include:
6. What is the application of a doubly linked list?
Doubly linked lists are useful when frequent insertions/deletions from both ends or two-way traversal are required. Application of doubly linked list examples include:
7. What are the advantages of a doubly linked list?
8. What are the three parts of a doubly linked list?
Each node in a doubly linked list data structure typically has three parts:
Mukesh Kumar
Working with upGrad as a Senior Engineering Manager with more than 10+ years of experience in Software Development and Product Management and Pro…Read More
Talk to our experts. We are available 7 days a week, 9 AM to 12 AM (midnight)
Indian Nationals
1800 210 2020
Foreign Nationals
+918045604032
1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.
2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.