Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Data Structure Class Note - UNIT 2

Download as pdf or txt
Download as pdf or txt
You are on page 1of 32

Data Structure & Algorithms

Mr. Sujit Biswas


Assistant Professor
CSE (Data Science) Department
B V Raju Institute of Technology, Narsapur

Unit 1:
2.1 What is Stack?

A stack is a linear data structure in which the insertion of a new element and removal of an existing
element takes place at the same end represented as the top of the stack.

To implement the stack, it is required to maintain the pointer to the top of the stack, which is the last
element to be inserted because we can access the elements only on the top of the stack.

XXX YYY ZZZ

TOP MAXSTK

LIFO (Last in First Out):

This strategy states that the element that is inserted last will come out first. You can take a pile of
plates kept on top of each other as a real-life example. The plate which we put last is on the top and
since we remove the plate that is at the top, we can say that the plate that was put last comes out first.

Basic Operations on Stack:

In order to make manipulations in a stack, there are certain operations provided to us.

 push() to insert an element into the stack


 pop() to remove an element from the stack
 top() Returns the top element of the stack.
 isEmpty() returns true if stack is empty else false.
 size() returns the size of stack.
Push:

Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.

Algorithm for PUSH:

PUSH (STACK, TOP, MAXSTK, ITEM)

1. If TOP = MAXSTK, then write OVERFLOW and return.


2. Set TOP : = TOP + 1.
3. Set STACK [TOP] : = ITEM
4. Return.

Pop:

Removes an item from the stack. The items are popped in the reversed order in which they are
pushed. If the stack is empty, then it is said to be an Underflow condition.

Algorithm for POP:

POP (STACK, TOP, ITEM)

1. If TOP = 0, then write UNDERFLOW and return.


2. Set ITEM : = STACK [TOP].
3. Set TOP : = TOP - 1
4. Return.

C programming Code for Stack (Push & Pop)

#include <stdio.h>
#include <stdlib.h>
#define SIZE 4

int top = -1, inp_array[SIZE];


void push();
void pop();
void show();
int main()
{
int choice;

while (1)
{
printf("\nPerform operations on the stack:");
printf("\n1.Push the element\n2.Pop the element\n3.Show\n4.End");
printf("\n\nEnter the choice: ");
scanf("%d", &choice);

switch (choice)
{
case 1:
push();
break;
case 2:
pop();
break;
case 3:
show();
break;
case 4:
exit(0);

default:
printf("\nInvalid choice!!");
}
}
}

void push()
{
int x;

if (top == SIZE - 1)
{
printf("\nOverflow!!");
}
else
{
printf("\nEnter the element to be added onto the stack: ");
scanf("%d", &x);
top = top + 1;
inp_array[top] = x;
}
}

void pop()
{
if (top == -1)
{
printf("\nUnderflow!!");
}
else
{
printf("\nPopped element: %d", inp_array[top]);
top = top - 1;
}
}

void show()
{
if (top == -1)
{
printf("\nUnderflow!!");
}
else
{
printf("\nElements present in the stack: \n");
for (int i = top; i >= 0; --i)
printf("%d\n", inp_array[i]);
}
}

Complexity Analysis:

2.2 Types of Stacks:


 Fixed Size Stack: As the name suggests, a fixed size stack has a fixed size and cannot grow
or shrink dynamically. If the stack is full and an attempt is made to add an element to it, an
overflow error occurs. If the stack is empty and an attempt is made to remove an element from
it, an underflow error occurs.
 Dynamic Size Stack: A dynamic size stack can grow or shrink dynamically. When the stack
is full, it automatically increases its size to accommodate the new element, and when the stack
is empty, it decreases its size. This type of stack is implemented using a linked list, as it allows
for easy resizing of the stack.

In addition to these two main types, there are several other variations of Stacks, including:

 Infix to Postfix Stack: This type of stack is used to convert infix expressions to postfix
expressions.
 Expression Evaluation Stack: This type of stack is used to evaluate postfix expressions.
 Recursion Stack: This type of stack is used to keep track of function calls in a computer
program and to return control to the correct function when a function returns.
 Memory Management Stack: This type of stack is used to store the values of the program
counter and the values of the registers in a computer program, allowing the program to return
to the previous state when a function returns.
 Balanced Parenthesis Stack: This type of stack is used to check the balance of parentheses
in an expression.
 Undo-Redo Stack: This type of stack is used in computer programs to allow users to undo
and redo actions.

2.3 Applications of the stack:

Polish Notation:
This type of notation was introduced by the Polish mathematician Lukasiewicz. Polish Notation in
data structure tells us about different ways to write an arithmetic expression. An arithmetic expression
contains 2 things, i.e., operands and operators. Operands are either numbers or variables that can be
replaced by numbers to evaluate the expressions. Operators are symbols symbolizing the operation to
be performed between operands present in the expression. Like the expression
(1+2) ∗ (3+4) standard becomes ∗ + 1 2 + 3 4 in Polish Notation. Polish notation is also called prefix
notation. It means that operations are written before the operands. The operators are placed left for
every pair of operands. Let’s say for the expression a + b, the prefix notation would be + a b.

Types of Notations
Three types of polish notations exist in the data structure. Let's have look at them one-by-one.
Infix Notation:
This polish notation in data structure states that the operator is written in between the operands. It is
the most common type of notation we generally use to represent expressions. It's the fully
parenthesized notation. We find it much easier to write mathematical expressions in Infix Notation,
but it is difficult to parse expressions on computers in the form of infix Polish Notation.
(3+7)

(1*(2+3))

Prefix Notation:
This polish notation in data structure states that the operator should be present as a prefix or before the
operands. This notation is also known as "Polish Notation". For example, if we have an expression
like x + y, then here x and y are operands, and ‘+’ is the operator. The prefix notation or polish notation
of this expression will be "+ x y".
3+7 will convert into +37

1*(2+3) will convert into *1(+23)

Postfix Notation:
This notation states that the operator should be present as a suffix, postfix, or after the operands. It is
also known as Suffix notation or Reverse Polish Notation. For example, if we have an expression like
x + y, then here x and y are operands, and ‘+’ is the operator. The prefix notation or polish notation of
this expression will be "x y + ".
In general, a computer can easily understand postfix expressions. This notation is universally
accepted and is preferred for designing and programming the arithmetic and logical units of a CPU
(Central Processing Unit). All the expressions that are entered into a computer are converted into
Postfix or Reverse Polish Notation, stored in a stack, and then computed. Therefore, postfix expression
plays a vital role in the tech industry.
3+7 will convert into 37+

1*(2+3) will convert into (23+)1*

Conversion of an Infix Expression to Postfix Expression


Generally, humans find infix polish notation much easier to understand than postfix or reverse polish
notation. To convert each expression from infix to postfix, we assign priority to each of the operators
present in the expression. Each operator has its priority for an expression. For example, if we take
some operators, i.e., +, -, *, /, then these will be arranged in priority.

Higher Priority Operators: *, /, %.


Lower Priority Operators: +, -.
Order of Operators: +, −, ∗, /, ^.

A Conversion Algorithm for INFIX POLISH NOTATION to POSTFIX POLISH NOTATION is


given below:

1. Push "(" in the stack and add ")" at the end of the infix polish notation of the given expression.

2. Repeat the below steps for each of the elements present in the Infix Polish Notation.

 If "(" is encountered, then push the element onto the stack.


 If an operand, i.e. a variable or a number, is encountered, we add it in the postfix or reverse
polish notation.
 If ")" is encountered, we pop from the stack until the popped element is "(" and add these
elements to the postfix expression. After that discard "(" from the stack and do not add it again
to the postfix expression.
 If an operator "x" is encountered, then, again and again, pop from the stack and add each
operator to the postfix expression which has the same or higher precedence than operator "x".
3. After performing step 2 on each element, we will pop all the elements from the stack and add
them to the postfix expression.

Evaluation of a Postfix Expression:

Algorithm:

This algorithm finds the VALUE of an arithmetic expression P written in Postfix notation.

1. Add a right parenthesis “)” at the end of P.


2. Scan P from left to right and repeat steps 3 and 4 for each element of P until the “)” is
encountered.
3. If an operand is encountered put it on STACK.
4. If an operator ⊕ is encountered, then
a. Remove the two top elements from STACK, where A is the TOP and B is the next TOP
element.
b. Evaluate B⊕A.
c. Place the result of (b) back on STACK.

[End of if structure]

[End of Step 2 loop]

5. Set VALUE equal to the top element on STACK.


6. Exit.

Exp:

P: 5, 6, 2, +, *, 12, 4, /, -
Q: 5 + (6 + 2) – 12/4
P: 5, 6, 2, +, *, 12, 4, /, -, )
1 2 3 4 5 6 7 8 9 10
Symbol Scanned STACK
1→5 5
2→6 5, 6
3→2 5, 6, 2
4→+ 5, 8
5→* 40
6 → 12 40, 12
7→4 40, 12, 4
8→/ 40, 3
9→- 37
10 → )

Transforming Infix Expression into Postfix Expressions:

Algorithm

POLISH (P, Q)

1. PUSH “(“ onto STACK and add ”)” to the end of Q.


2. Scan Q from left to right and repeat Steps 3 to 6 for each element until STACK is empty.
3. If an Operand is encountered, add it to P.
4. If left parentheses are encountered PUSH it onto STACK.
5. If an Operator is encountered, then
a. POP from STACK and add to P.
b. Add ⊕ to STACK.
[End if structure]
6. If right parentheses are encountered, then
a. POP from STACK and add to P.
b. Remove the left parentheses.
[End of if structure]

[End of Step 2 Loop]

7. Exit.

Example:

Q: A + (B + C – (D / E ↑ F) * G) + H

A + ( B + C - ( D / E ↑ F ) * G ) * H )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Symbol
STACK Expression
Scanned
1→A ( A
2→+ (+ A
3→( (+( A
4→B (+( AB
5→+ (+(+ AB
6→C (+(+ ABC
7→- (+(- ABC+
8→( (+(-( ABC+
9→D (+(-( ABC+D
10 → / (+(-(/ ABC+D
11 → E (+(-(/ ABC+DE
12 → ↑ (+(-(/↑ ABC+DE
13 → F (+(-(/↑ ABC+DEF
14 → ) (+(- ABC+DEF↑/
15 → * (+(-* ABC+DEF↑/
16 → G (+(-* ABC+DEF↑/G
17 → ) (+ ABC+DEF↑/G*-
18 → * (+* ABC+DEF↑/G*-
19 → H (+* ABC+DEF↑/G*-H
20 → ) ABC+DEF↑/G*-H*+

P: A B C + D E F ↑ / G * - H * +

2.4 Queue:
Queue, like Stack, is also an abstract data structure. The thing that makes queue different from stack
is that a queue is open at both its ends. Hence, it follows FIFO (First-In-First-Out) structure, i.e. the
data item inserted first will also be accessed first. The data is inserted into the queue through one end
and deleted from it using the other end.

A real-world example of queue can be a single-lane one-way road, where the vehicle enters first,
exits first. More real-world examples can be seen as queues at the ticket windows and bus-stops.

Representation of Queues
Similar to the stack ADT, a queue ADT can also be implemented using arrays, linked lists, or pointers.
As a small example in this tutorial, we implement queues using a one-dimensional array. But queue
has two end, FRONT and REAR. We can add element in REAR and we can delete element from
FRONT.
1 2 3 4 5 6 7

FRONT: 0
REAR: 0

A B C D
1 2 3 4 5 6 7

FRONT: 1
REAR: 4

B C D
1 2 3 4 5 6 7

FRONT: 2
REAR: 4

B C D E F
1 2 3 4 5 6 7

FRONT: 2
REAR: 6

Insert the elements in Queue:


Algorithm
QINSERT (QUEUE, N, FRONT, REAR, ITEM)
1. If FRONT = 1 and REAR = N, or FRONT = REAR +1, then
Write OVERFLOW and return.
2. If FRONT = NULL, then
Set FRONT : = 1 and REAR : = 1
Else if REAR = N, then
Set REAR : = 1
Else
Set REAR = REAR + 1
[End if structure]
3. Set QUEUE [REAR] : = ITEM
4. Return.

Delete the elements from Queue:


Algorithm
QDELETE (QUEUE, N, FRONT, REAR, ITEM)
1. If FRONT = NULL, then
Write UNDERFLOW and return.
2. Set ITEM : = QUEUE [FRONT]
3. If FRONT = REAR, then
Set FRONT : NULL and REAR : = NULL
Else if FRONT = N, then
Set FRONT : = 1
Else
Set FRONT : = FRONT + 1
[End of if structure]
4. Return

C Code for insert and delete element in queue:


#include <stdio.h>
# define SIZE 100
void enqueue();
void dequeue();
void show();
int inp_arr[SIZE];
int Rear = - 1;
int Front = - 1;
main()
{
int ch;
while (1)
{
printf("1.Enqueue Operation\n");
printf("2.Dequeue Operation\n");
printf("3.Display the Queue\n");
printf("4.Exit\n");
printf("Enter your choice of operations : ");
scanf("%d", &ch);
switch (ch)
{
case 1:
enqueue();
break;
case 2:
dequeue();
break;
case 3:
show();
break;
case 4:
exit(0);
default:
printf("Incorrect choice \n");
}
}
}

void enqueue()
{
int insert_item;
if (Rear == SIZE - 1)
printf("Overflow \n");
else
{
if (Front == - 1)

Front = 0;
printf("Element to be inserted in the Queue\n : ");
scanf("%d", &insert_item);
Rear = Rear + 1;
inp_arr[Rear] = insert_item;
}
}

void dequeue()
{
if (Front == - 1 || Front > Rear)
{
printf("Underflow \n");
return ;
}
else
{
printf("Element deleted from the Queue: %d\n", inp_arr[Front]);
Front = Front + 1;
}
}

void show()
{

if (Front == - 1)
printf("Empty Queue \n");
else
{
printf("Queue: \n");
for (int i = Front; i <= Rear; i++)
printf("%d ", inp_arr[i]);
printf("\n");
}
}

2.5 Circular Queue:

A Circular Queue is an extended version of a normal queue where the last element of the queue is
connected to the first element of the queue forming a circle.
The operations are performed based on FIFO (First In First Out) principle. It is also called ‘Ring
Buffer’.

In a normal Queue, we can insert elements until queue becomes full. But once queue becomes full,
we cannot insert the next element even if there is a space in front of queue.

Operations on Circular Queue:


 Front: Get the front item from the queue.
 Rear: Get the last item from the queue.
 enQueue (value) This function is used to insert an element into the circular queue. In a
circular queue, the new element is always inserted at the rear position.
 Check whether the queue is full – [i.e., the rear end is in just before the front end in a circular
manner].
 If it is full then display Queue is full.
 If the queue is not full then, insert an element at the end of the queue.
 deQueue() This function is used to delete an element from the circular queue. In a circular
queue, the element is always deleted from the front position.
 Check whether the queue is Empty.
 If it is empty then display Queue is empty.
 If the queue is not empty, then get the last element and remove it from the queue.

Illustration of Circular Queue Operations:


Follow the below image for a better understanding of the enqueue and dequeue operations.
Implement Circular Queue using Array:

2.6 Understanding the basics of Linked List:


Linked List is a linear data structure, in which elements are not stored at a contiguous location, rather
they are linked using pointers. Linked List forms a series of connected nodes, where each node stores
the data and the address of the next node.
Node Structure: A node in a linked list typically consists of two components:
Data: It holds the actual value or data associated with the node.
Next Pointer: It stores the memory address (reference) of the next node in the sequence.
Head and Tail: The linked list is accessed through the head node, which points to the first node in the
list. The last node in the list points to NULL or nullptr, indicating the end of the list. This node is
known as the tail node.

Why linked list data structure needed?


Here are a few advantages of a linked list that is listed below, it will help you understand why it is
necessary to know.
 Dynamic Data structure: The size of memory can be allocated or de-allocated at run time
based on the operation insertion or deletion.
 Ease of Insertion/Deletion: The insertion and deletion of elements are simpler than arrays
since no elements need to be shifted after insertion and deletion, Just the address needed to be
updated.
 Efficient Memory Utilization: As we know Linked List is a dynamic data structure the size
increases or decreases as per the requirement so this avoids the wastage of memory.
 Implementation: Various advanced data structures can be implemented using a linked list like
a stack, queue, graph, hash maps, etc.

Example:
In a system, if we maintain a sorted list of IDs in an array id [] = [1000, 1010, 1050, 2000, 2040].

If we want to insert a new ID 1005, then to maintain the sorted order, we have to move all the elements
after 1000 (excluding 1000).

Deletion is also expensive with arrays until unless some special techniques are used. For example, to
delete 1010 in id [], everything after 1010 has to be moved due to this so much work is being done
which affects the efficiency of the code.

Types of linked lists:


There are mainly three types of linked lists:

 Single-linked list
 Double linked list
 Circular linked list
2.6.1 Singly Linked List:

A singly linked list is a linear data structure in which the elements are not stored in contiguous
memory locations and each element is connected only to its next element using a pointer.

Characteristics of a Singly Linked List:


 Each node holds a single value and a reference to the next node in the list.
 The list has a head, which is a reference to the first node in the list, and a tail, which is a
reference to the last node in the list.
 The nodes are not stored in a contiguous block of memory, but instead, each node holds the
address of the next node in the list.
 Accessing elements in a singly linked list requires traversing the list from the head to the
desired node, as there is no direct access to a specific node in memory.

Application of Singly Linked Lists:


 Memory management: Singly linked lists can be used to implement memory pools, in which
memory is allocated and deallocated as needed.
 Database indexing: Singly linked lists can be used to implement linked lists in databases,
allowing for fast insertion and deletion operations.
 Representing polynomials and sparse matrices: Singly linked lists can be used to efficiently
represent polynomials and sparse matrices, where most elements are zero.
 Operating systems: Singly linked lists are used in operating systems for tasks such as
scheduling processes and managing system resources.

Advantages of Singly Linked Lists:


 Dynamic memory allocation: Singly linked lists allow for dynamic memory allocation,
meaning that the size of the list can change at runtime as elements are added or removed.
 Cache friendliness: Singly linked lists can be cache-friendly as nodes can be stored in separate
cache lines, reducing cache misses and improving performance.
 Space-efficient: Singly linked lists are space-efficient, as they only need to store a reference to
the next node in each element, rather than a large block of contiguous memory.
Disadvantages of Singly Linked Lists:
 Poor random-access performance: Accessing an element in a singly linked list requires
traversing the list from the head to the desired node, making it slow for random access
operations compared to arrays.
 Increased memory overhead: Singly linked lists require additional memory for storing the
pointers to the next node in each element, resulting in increased memory overhead compared
to arrays.
 Vulnerability to data loss: Singly linked lists are vulnerable to data loss if a node’s next pointer
is lost or corrupted, as there is no way to traverse the list and access other elements.
 Not suitable for parallel processing: Singly linked lists are not suitable for parallel processing,
as updating a node requires exclusive access to its next pointer, which cannot be easily done
in a parallel environment.
 Backward traversing not possible: In singly linked list does not supports backward traversing.

Build a Singly Linked List:

Initialize the PTR or START:


Algorithm:
1. Set PTR : = START
2. Repeat Steps 3 and 4 While PTR ≠ NULL
3. Apply PROCESS to INFO [PTR]
4. Set PTR : = LINK [PTR]
[End of Loop 2]
5. Exit.

Finds the number of elements in a Linked List:


Algorithm:
COUNT (INFO, LINK, START, NUM)
1. Set NUM: = 0.
2. Set PTR : = START.
3. Repeat Steps 4 and 5 While PTR ≠ NULL.
4. Set NUM : = NUM + 1.
5. Set PTR : = LINK [PTR].
[End of Step 3 Loop]
6. Return.

Searching Element in a Linked List:


Algorithm:
SEARCH (INFO, LINK, START, ITEM, LOC)
1. Set PTR : = START.
2. Repeat Step 3 While PTR ≠ NULL.
3. If ITEM = INFO [PTR], then
Set LOC : = PTR, and Exit
Else
Set PTR : = LINK [PTR]
[End If structure]
[End of Step 2 Loop]
4. Set LOC : = NULL.
5. Exit.

Searching Element in a Linked List (Sorted list):


Algorithm:
SEARCH (INFO, LINK, START, ITEM, LOC)
1. Set PTR : = START.
2. Repeat Step 3 While PTR ≠ NULL.
3. If ITEM < INFO [PTR], then
Set PTR : = LINK [PTR].
Else if ITEM = INFO [PTR], then
Set LOC : = PTR, and Exit.
Else
Set LOC : = NULL, and Exit.
[End If structure]
[End Step 2 Loop]
4. Set LOC : = NULL.
5. Exit.

C code for Searching a Node:


#include<stdio.h>
#include<stdlib.h>
void create(int);
void search();
struct node
{
int data;
struct node *next;
};
struct node *head;
void main ()
{
int choice,item,loc;
do
{
printf("\n1.Create\n2.Search\n3.Exit\n4.Enter your choice?");
scanf("%d",&choice);
switch(choice)
{
case 1:
printf("\nEnter the item\n");
scanf("%d",&item);
create(item);
break;
case 2:
search();
case 3:
exit(0);
break;
default:
printf("\nPlease enter valid choice\n");
}

}while(choice != 3);
}
void create(int item)
{
struct node *ptr = (struct node *)malloc(sizeof(struct node *));
if(ptr == NULL)
{
printf("\nOVERFLOW\n");
}
else
{
ptr->data = item;
ptr->next = head;
head = ptr;
printf("\nNode inserted\n");
}

}
void search()
{
struct node *ptr;
int item,i=0,flag;
ptr = head;
if(ptr == NULL)
{
printf("\nEmpty List\n");
}
else
{
printf("\nEnter item which you want to search?\n");
scanf("%d",&item);
while (ptr!=NULL)
{
if(ptr->data == item)
{
printf("item found at location %d ",i+1);
flag=0;
}
else
{
flag=1;
}
i++;
ptr = ptr -> next;
}
if(flag==1)
{
printf("Item not found\n");
}
}

Insert Node at the beginning of a Linked List:


Algorithm:
INSERT (INFO, LINK, START, AVAIL, ITEM)
1. If AVAIL = NULL, then
Write OVERFLOW and Return.
2. Set NEW : = AVAIL and AVAIL : = LINK [AVAIL]
3. Set INFO [NEW] : = ITEM.
4. Set LINK [NEW] : = START.
5. Set START : = NEW.
6. Exit.

C code:
#include<stdio.h>
#include<stdlib.h>
void beginsert(int);
struct node
{
int data;
struct node *next;
};
struct node *head;
void main ()
{
int choice,item;
do
{
printf("\nEnter the item which you want to insert?\n");
scanf("%d",&item);
beginsert(item);
printf("\nPress 0 to insert more ?\n");
scanf("%d",&choice);
}while(choice == 0);
}
void beginsert(int item)
{
struct node *ptr = (struct node *)malloc(sizeof(struct node *));
if(ptr == NULL)
{
printf("\nOVERFLOW\n");
}
else
{
ptr->data = item;
ptr->next = head;
head = ptr;
printf("\nNode inserted\n");
}

Insert Node after given node of a Linked List:


Algorithm:
INSERT (INFO, LINK, START, AVAIL, ITEM)
1. If AVAIL = NULL, then
Write OVERFLOW and Return.
2. Set NEW : = AVAIL and AVAIL : = LINK [AVAIL]
3. Set INFO [NEW] : = ITEM.
4. If LOC = NULL, then
Set LINK [NEW] : = START and START : = NEW.
Else
Set LINK [NEW] : = LINK [LOC] and LINK [LOC] : = NEW.
[End of If structure]
5. Exit.
C code:
#include <stdio.h>
#include <stdlib.h>

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

void push(struct Node** head_ref,


int new_data)
{
struct Node* new_node =
(struct Node*) malloc(sizeof(struct Node));

new_node->data = new_data;

new_node->next = (*head_ref);

(*head_ref) = new_node;
}

void insertAfter(struct Node* prev_node,


int new_data)
{

if (prev_node == NULL)
{
printf("the given previous node cannot be NULL");
return;
}

struct Node* new_node =


(struct Node*) malloc(sizeof(struct Node));
new_node->data = new_data;

new_node->next = prev_node->next;

prev_node->next = new_node;
}

void append(struct Node** head_ref,


int new_data)
{
struct Node* new_node =
(struct Node*) malloc(sizeof(struct Node));

struct Node *last = *head_ref;

new_node->data = new_data;

new_node->next = NULL;

if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}

while (last->next != NULL)


last = last->next;

last->next = new_node;
return;
}

void printList(struct Node *node)


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

int main()
{
struct Node* head = NULL;
append(&head, 6);

push(&head, 7);

push(&head, 1);

append(&head, 4);

insertAfter(head->next, 8);

printf("Created Linked list is: ");


printList(head);

return 0;
}

Deleting the Node from a Linked list:


Algorithm:
DEL (INFO, LINK, START, AVAIL, LOC, LOCP) [LOCP is the Location when N in the first
position]
1. If LOCP = NULL, then
Set START : LINK [START]
Else
Set LINK [LOCP] : = LINK [LOC]
[End if structure]
2. Set LINK [LOC] : = AVAIL and AVAIL : = LOC
3. Exit

C code:
#include <stdio.h>
#include <stdlib.h>

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

void push(struct Node** head_ref, int new_data)


{
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
new_node->data = new_data;
new_node->next = (*head_ref);
(*head_ref) = new_node;
}

void deleteNode(struct Node** head_ref, int key)


{
struct Node *temp = *head_ref, *prev;

if (temp != NULL && temp->data == key) {


*head_ref = temp->next; // Changed head
free(temp); // free old head
return;
}

while (temp != NULL && temp->data != key) {


prev = temp;
temp = temp->next;
}

if (temp == NULL)
return;

prev->next = temp->next;

free(temp);
}

void printList(struct Node* node)


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

int main()
{
struct Node* head = NULL;
push(&head, 7);
push(&head, 1);
push(&head, 3);
push(&head, 2);

puts("Created Linked List: ");


printList(head);
deleteNode(&head, 1);
puts("
Linked List after Deletion of 1: ");
printList(head);
return 0;
}

2.6.2 Double Linked list:

In a doubly linked list, each node contains references to both the next and previous nodes. This allows
for traversal in both forward and backward directions, but it requires additional memory for the
backward reference.

Characteristics of the Doubly Linked List:


The characteristics of a doubly linked list are as follows:

 Dynamic size: The size of a doubly linked list can change dynamically, meaning that nodes
can be added or removed as needed.
 Two-way navigation: In a doubly linked list, each node contains pointers to both the previous
and next elements, allowing for navigation in both forward and backward directions.
 Memory overhead: Each node in a doubly linked list requires memory for two pointers
(previous and next), in addition to the memory required for the data stored in the node.

Advantages of Doubly Linked List 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 Doubly Linked List 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 Doubly Linked List:


 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.

Add a node at the front in a Doubly Linked List:


The new node is always added before the head of the given Linked List. The task can be performed
by using the following 5 steps:

1. Firstly, allocate a new node (say new_node).


2. Now put the required data in the new node.
3. Make the next of new_node point to the current head of the doubly linked list.
4. Make the previous of the current head point to new_node.
5. Lastly, point head to new_node.

Illustration:

See the below illustration where E is being inserted at the beginning of the doubly linked list.

Add a node after a given node in a Doubly Linked List:


We are given a pointer to a node as prev_node, and the new node is inserted after the given node.
This can be done using the following 6 steps:

1. Firstly create a new node (say new_node).


2. Now insert the data in the new node.
3. Point the next of new_node to the next of prev_node.
4. Point the next of prev_node to new_node.
5. Point the previous of new_node to prev_node.
6. Change the pointer of the new node’s previous pointer to new_node.

Illustration:

See the below illustration where ‘E‘ is being inserted after ‘B‘.
2.6

2.6.3 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.
 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 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:

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

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:

 Insertion at the beginning of the list


 Insertion at the end of the list
 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.

And then

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,

After insertion
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,

After searching and insertion,

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.

You might also like