0% found this document useful (0 votes)
5 views

Data Structure Unit-4.1-Doubly-Linked-List

A Doubly Linked List (DLL) includes an additional pointer for the previous node, allowing traversal in both directions and more efficient insertion and deletion operations compared to singly linked lists. While DLLs require extra space for the previous pointer and more complex operations, they are useful in applications such as web browsers and cache management. The document also describes various insertion methods for DLLs and introduces circular linked lists, which connect the last node back to the first, forming a circle.

Uploaded by

aniruddh2573
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views

Data Structure Unit-4.1-Doubly-Linked-List

A Doubly Linked List (DLL) includes an additional pointer for the previous node, allowing traversal in both directions and more efficient insertion and deletion operations compared to singly linked lists. While DLLs require extra space for the previous pointer and more complex operations, they are useful in applications such as web browsers and cache management. The document also describes various insertion methods for DLLs and introduces circular linked lists, which connect the last node back to the first, forming a circle.

Uploaded by

aniruddh2573
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Doubly Linked List

A Doubly Linked List (DLL) contains an extra pointer, typically called the
previous pointer, together with the next pointer and data which are there in the
singly linked list.

Following is a representation of a DLL node:


// Node of a doubly linked list

struct Node {
int data;

// Pointer to next node in DLL


struct Node* next;

// Pointer to previous node in DLL


struct Node* prev;
};
Advantages of DLL over the singly linked list:
• A DLL can be traversed in both forward and backward directions.
• The delete operation in DLL is more efficient if a pointer to the node
to be deleted is given.
• 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 of DLL over the singly linked list:


• Every node of DLL Requires extra space for a previous pointer. It is
possible to implement DLL with a single pointer though
(See this and this).
• 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. For example in the following functions for
insertions at different positions, we need 1 or 2 extra steps to set the
previous pointer.

Applications of DLL:
• It is used by web browsers for backward and forward navigation of
web pages
• LRU ( Least Recently Used ) / MRU ( Most Recently Used ) Cache
are constructed using Doubly Linked Lists.
• Used by various applications to maintain undo and redo
functionalities.
• In Operating Systems, a doubly linked list is maintained by thread
scheduler to keep track of processes that are being executed at that
time.

Insertion in DLL:
A node can be added in four ways:
• At the front of the DLL
• After a given node.
• At the end of the DLL
• Before a given node.

1) Add a node at the front:


The new node is always added before the head of the given Linked List. And
newly added node becomes the new head of DLL. For example, if the given
Linked List is 1->0->1->5 and we add an item 5 at the front, then the Linked
List becomes 5->1->0->1->5. Let us call the function that adds at the front of
the list push(). The push() must receive a pointer to the head pointer because the
push must change the head pointer to point to the new node (See this)

Below is the implementation of the 5 steps to insert a node at the front of the
linked list:
/* Given a reference (pointer to pointer) to the head of a
list and an int, inserts a new node on the front of the
list. */
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
/* 2. put in the data */
new_node->data = new_data;

/* 3. Make next of new node as head and previous as NULL


*/
new_node->next = (*head_ref);
new_node->prev = NULL;

/* 4. change prev of head node to new node */


if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;

/* 5. move the head to point to the new node */


(*head_ref) = new_node;
}

2) Add a node after a given node:


We are given a pointer to a node as prev_node, and the new node is inserted
after the given node.

Below is the implementation of the 7 steps to insert a node after a given node in
the linked list:
/* Given a node as prev_node, insert a new node after the
* given node */
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}
/* 2. allocate new node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. Make the next of prev_node as new_node */


prev_node->next = new_node;

/* 6. Make prev_node as previous of new_node */


new_node->prev = prev_node;

/* 7. Change previous of new_node's next node */


if (new_node->next != NULL)
new_node->next->prev = new_node;
}
3) Add a node at the end:
The new node is always added after the last node of the given Linked List. For
example, if the given DLL is 5->1->0->1->5->2 and we add item 30 at the end,
then the DLL becomes 5->1->0->1->5->2->30. Since a Linked List is typically
represented by its head of it, we have to traverse the list till the end and then
change the next of last node to the new node.

Below is the implementation of the 7 steps to insert a node at the end of the
linked list:
/* Given a reference (pointer to pointer) to the head
of a DLL and an int, appends a new node at the end */
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

struct Node* last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so


make next of it as NULL*/
new_node->next = NULL;

/* 4. If the Linked List is empty, then make the new


node as head */
if (*head_ref == NULL) {
new_node->prev = NULL;
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


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

/* 6. Change the next of last node */


last->next = new_node;

/* 7. Make last node as previous of new node */


new_node->prev = last;

return;
}
4) Add a node before a given node:
Follow the below steps to solve the problem:
Let the pointer to this given node be next_node and the data of the new node be
added as new_data.
• Check if the next_node is NULL or not. If it’s NULL, return from the
function because any new node can not be added before a NULL
• Allocate memory for the new node, let it be called new_node
• Set new_node->data = new_data
• Set the previous pointer of this new_node as the previous node of the
next_node, new_node->prev = next_node->prev
• Set the previous pointer of the next_node as the new_node,
next_node->prev = new_node
• Set the next pointer of this new_node as the next_node, new_node-
>next = next_node;
• If the previous node of the new_node is not NULL, then set the next
pointer of this previous node as new_node, new_node->prev->next =
new_node
• Else, if the prev of new_node is NULL, it will be the new head node.
So, make (*head_ref) = new_node.

Following is the complete program to test the above functions:


// A complete working C program to
// demonstrate all insertion
// methods
#include <stdio.h>
#include <stdlib.h>

// A linked list node


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

/* Given a reference (pointer to pointer) to the head of a


list and an int, inserts a new node on the front of the
list. */
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

/* 2. put in the data */


new_node->data = new_data;

/* 3. Make next of new node as head and previous as NULL


*/
new_node->next = (*head_ref);
new_node->prev = NULL;

/* 4. change prev of head node to new node */


if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;

/* 5. move the head to point to the new node */


(*head_ref) = new_node;
}

/* Given a node as prev_node, insert a new node after the


* given node */
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}

/* 2. allocate new node */


struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

/* 3. put in the data */


new_node->data = new_data;

/* 4. Make next of new node as next of prev_node */


new_node->next = prev_node->next;

/* 5. Make the next of prev_node as new_node */


prev_node->next = new_node;

/* 6. Make prev_node as previous of new_node */


new_node->prev = prev_node;

/* 7. Change previous of new_node's next node */


if (new_node->next != NULL)
new_node->next->prev = new_node;
}
/* Given a reference (pointer to pointer) to the head
of a DLL and an int, appends a new node at the end */
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));

struct Node* last = *head_ref; /* used in step 5*/

/* 2. put in the data */


new_node->data = new_data;

/* 3. This new node is going to be the last node, so


make next of it as NULL*/
new_node->next = NULL;

/* 4. If the Linked List is empty, then make the new


node as head */
if (*head_ref == NULL) {
new_node->prev = NULL;
*head_ref = new_node;
return;
}

/* 5. Else traverse till the last node */


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

/* 6. Change the next of last node */


last->next = new_node;

/* 7. Make last node as previous of new node */


new_node->prev = last;

return;
}

// This function prints contents of linked list starting


// from the given node
void printList(struct Node* node)
{
struct Node* last;
printf("\nTraversal in forward direction \n");
while (node != NULL) {
printf("%d ", node->data);
last = node;
node = node->next;
}

printf("\nTraversal in reverse direction \n");


while (last != NULL) {
printf("%d ", last->data);
last = last->prev;
}
}

// Driver code
int main()
{
/* Start with the empty list */
struct Node* head = NULL;

// Insert 6. So linked list becomes 6->NULL


append(&head, 6);

// Insert 7 at the beginning. So linked list becomes


// 7->6->NULL
push(&head, 7);

// Insert 1 at the beginning. So linked list becomes


// 1->7->6->NULL
push(&head, 1);

// Insert 4 at the end. So linked list becomes


// 1->7->6->4->NULL
append(&head, 4);

// Insert 8, after 7. So linked list becomes


// 1->7->8->6->4->NULL
insertAfter(head->next, 8);

printf("Created DLL is: ");


printList(head);

getchar();
return 0;
}
Output
Created DLL is:
Traversal in forward direction
1 7 8 6 4
Traversal in reverse direction
4 6 8 7 1

Circular Linked List

What is Circular linked list?


The circular linked list is a linked list where all nodes are connected to form a
circle. In a circular linked list, the first node and the last node are connected to
each other which forms a circle. There is no NULL at the end.

There are generally two types of circular linked lists:


• Circular singly linked list: In a circular Singly linked list, the last
node of the list contains a pointer to the first node of the list. We
traverse the circular singly linked list until we reach the same node
where we started. The circular singly linked list has no beginning or
end. No null value is present in the next part of any of the nodes.

Representation of Circular singly linked list


• Circular Doubly linked list: Circular Doubly Linked List has
properties of both doubly linked list and circular linked list in which
two consecutive elements are linked or connected by the previous and
next pointer and the last node points to the first node by the next
pointer and also the first node points to the last node by the previous
pointer.
Representation of circular doubly linked list
Note: We will be using the singly circular linked list to represent the working of
the circular linked list.

Representation of circular linked list:


Circular linked lists are similar to single Linked Lists with the exception of
connecting the last node to the first node.
Node representation of a Circular Linked List:
// Class Node, similar to the linked list
class Node{
int value;

// Points to the next node.


Node next;
}

Example of Circular singly linked list:

Example of circular linked list


The above Circular singly linked list can be represented as:
// Initialize the Nodes.
Node one = new Node(3);
Node two = new Node(5);
Node three = new Node(9);

// Connect nodes
one.next = two;
two.next = three;
three.next = one;
Explanation: In the above program one, two, and three are the node with
values 3, 5, and 9 respectively which are connected in a circular manner as:
• For Node One: The Next pointer stores the address of Node two.
• For Node Two: The Next stores the address of Node three
• For Node Three: The Next points to node one.

Operations on the circular linked list:


We can do some operations on the circular linked list similar to the singly
linked list which are:
1. Insertion
2. Deletion

1. Insertion in the circular linked list:


A node can be added in three ways:
1. Insertion at the beginning of the list
2. Insertion at the end of the list
3. Insertion in between the nodes

1) Insertion at the beginning of the list: To insert a node at the beginning of


the list, follow these steps:
• Create a node, say T.
• Make T -> next = last -> next.
• last -> next = T.

Circular linked list before insertion


And then,
Circular linked list after insertion
Below is the code implementation to insert a node at the beginning of the list:
struct Node *addBegin(struct Node *last, int data)
{
if (last == NULL)
return addToEmpty(last, data);

// Creating a node dynamically.


struct Node *temp
= (struct Node *)malloc(sizeof(struct Node));

// Assigning the data.


temp -> data = data;

// Adjusting the links.


temp -> next = last -> next;
last -> next = temp;

return last;
}
2) Insertion at the end of the list: To insert a node at the end of the list, follow
these steps:
• Create a node, say T.
• Make T -> next = last -> next;
• last -> next = T.
• last = T.

Before insertion,
Circular linked list before insertion of node at the end
After insertion,

Circular linked list after insertion of node at the end


Below is the code implementation to insert a node at the beginning of the list:
struct Node *addEnd(struct Node *last, int data)
{
if (last == NULL)
return addToEmpty(last, data);

// Creating a node dynamically.


struct Node *temp =
(struct Node *)malloc(sizeof(struct Node));

// Assigning the data.


temp -> data = data;

// Adjusting the links.


temp -> next = last -> next;
last -> next = temp;
last = temp;

return last;
}
3) Insertion in between the nodes: To insert a node in between the two nodes,
follow these steps:
• Create a node, say T.
• Search for the node after which T needs to be inserted, say that node is
P.
• Make T -> next = P -> next;
• P -> next = T.
Suppose 12 needs to be inserted after the node has the value 10,

Circular linked list before insertion


After searching and insertion,

Circular linked list after insertion


Below is the code to insert a node at the specified position of the List:
struct Node *addAfter(struct Node *last, int data, int item)
{
if (last == NULL)
return NULL;

struct Node *temp, *p;


p = last -> next;

// Searching the item.


do
{
if (p ->data == item)
{
// Creating a node dynamically.
temp = (struct Node *)malloc(sizeof(struct Node));

// Assigning the data.


temp -> data = data;

// Adjusting the links.


temp -> next = p -> next;

// Adding newly allocated node after p.


p -> next = temp;

// Checking for the last node.


if (p == last)
last = temp;

return last;
}
p = p -> next;
} while (p != last -> next);

cout << item << " not present in the list." << endl;
return last;
}
2. Deletion in a circular linked list:
1) Delete the node only if it is the only node in the circular linked list:
• Free the node’s memory
• The last value should be NULL A node always points to another node,
so NULL assignment is not necessary.
Any node can be set as the starting point.
Nodes are traversed quickly from the first to the last.
2) Deletion of the last node:
• Locate the node before the last node (let it be temp)
• Keep the address of the node next to the last node in temp
• Delete the last memory
• Put temp at the end
3) Delete any node from the circular linked list: We will be given a node and
our task is to delete that node from the circular linked list.
Algorithm:
Case 1: List is empty.
• If the list is empty we will simply return.
Case 2:List is not empty
• If the list is not empty then we define two pointers curr and prev and
initialize the pointer curr with the head node.
• Traverse the list using curr to find the node to be deleted and before
moving to curr to the next node, every time set prev = curr.
• If the node is found, check if it is the only node in the list. If yes, set
head = NULL and free(curr).
• If the list has more than one node, check if it is the first node of the
list. Condition to check this( curr == head). If yes, then move prev
until it reaches the last node. After prev reaches the last node, set head
= head -> next and prev -> next = head. Delete curr.
• If curr is not the first node, we check if it is the last node in the list.
Condition to check this is (curr -> next == head).
• If curr is the last node. Set prev -> next = head and delete the node
curr by free(curr).
• If the node to be deleted is neither the first node nor the last node, then
set prev -> next = curr -> next and delete curr.
• If the node is not present in the list return head and don’t do anything.
Below is the implementation for the above approach:
// C++ program to delete a given key from
// linked list.
#include <bits/stdc++.h>
using namespace std;

// Structure for a node


class Node {
public:
int data;
Node* next;
};

// Function to insert a node at the


// beginning of a Circular linked list
void push(Node** head_ref, int data)
{

// Create a new node and make head


// as next of it.
Node* ptr1 = new Node();
ptr1->data = data;
ptr1->next = *head_ref;

// If linked list is not NULL then


// set the next of last node
if (*head_ref != NULL) {

// Find the node before head and


// update next of it.
Node* temp = *head_ref;
while (temp->next != *head_ref)
temp = temp->next;
temp->next = ptr1;
}
else

// For the first node


ptr1->next = ptr1;

*head_ref = ptr1;
}

// Function to print nodes in a given


// circular linked list
void printList(Node* head)
{
Node* temp = head;
if (head != NULL) {
do {
cout << temp->data << " ";
temp = temp->next;
} while (temp != head);
}

cout << endl;


}

// Function to delete a given node


// from the list
void deleteNode(Node** head, int key)
{

// If linked list is empty


if (*head == NULL)
return;

// If the list contains only a


// single node
if ((*head)->data == key && (*head)->next == *head) {
free(*head);
*head = NULL;
return;
}

Node *last = *head, *d;

// If head is to be deleted
if ((*head)->data == key) {

// Find the last node of the list


while (last->next != *head)
last = last->next;

// Point last node to the next of


// head i.e. the second node
// of the list
last->next = (*head)->next;
free(*head);
*head = last->next;
return;
}

// Either the node to be deleted is


// not found or the end of list
// is not reached
while (last->next != *head && last->next->data != key) {
last = last->next;
}

// If node to be deleted was found


if (last->next->data == key) {
d = last->next;
last->next = d->next;
free(d);
}
else
cout << "Given node is not found in the list!!!\n";
}

// Driver code
int main()
{
// Initialize lists as empty
Node* head = NULL;

// Created linked list will be


// 2->5->7->8->10
push(&head, 2);
push(&head, 5);
push(&head, 7);
push(&head, 8);
push(&head, 10);

cout << "List Before Deletion: ";


printList(head);

deleteNode(&head, 7);

cout << "List After Deletion: ";


printList(head);

return 0;
}
Output
List Before Deletion: 10 8 7 5 2
List After Deletion: 10 8 5 2

Advantages of Circular Linked Lists:


• Any node can be a starting point. We can traverse the whole list by
starting from any point. We just need to stop when the first visited
node is visited again.
• Useful for implementation of a queue. Unlike this implementation, we
don’t need to maintain two pointers for front and rear if we use a
circular linked list. We can maintain a pointer to the last inserted node
and the front can always be obtained as next of last.

• Circular lists are useful in applications to repeatedly go around the list.


For example, when multiple applications are running on a PC, it is
common for the operating system to put the running applications on a
list and then cycle through them, giving each of them a slice of time to
execute, and then making them wait while the CPU is given to another
application. It is convenient for the operating system to use a circular
list so that when it reaches the end of the list it can cycle around to the
front of the list.
• Circular Doubly Linked Lists are used for the implementation of
advanced data structures like the Fibonacci Heap.
• Implementing a circular linked list can be relatively easy compared to
other more complex data structures like trees or graphs.

Disadvantages of circular linked list:


• Compared to singly linked lists, circular lists are more complex.
• Reversing a circular list is more complicated than singly or doubly
reversing a circular list.
• It is possible for the code to go into an infinite loop if it is not handled
carefully.
• It is harder to find the end of the list and control the loop.
• Although circular linked lists can be efficient in certain applications,
their performance can be slower than other data structures in certain
cases, such as when the list needs to be sorted or searched.
• Circular linked lists don’t provide direct access to individual nodes

Applications of circular linked lists:


• Multiplayer games use this to give each player a chance to play.
• A circular linked list can be used to organize multiple running
applications on an operating system. These applications are iterated
over by the OS.
• Circular linked lists can be used in resource allocation problems.
• Circular linked lists are commonly used to implement circular buffers,
• Circular linked lists can be used in simulation and gaming.

Why circular linked list?


• A node always points to another node, so NULL assignment is not
necessary.
• Any node can be set as the starting point.
• Nodes are traversed quickly from the first to the last.

You might also like