Data Structure Class Note - UNIT 2
Data Structure Class Note - UNIT 2
Data Structure Class Note - UNIT 2
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.
TOP MAXSTK
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.
In order to make manipulations in a stack, there are certain operations provided to us.
Adds an item to the stack. If the stack is full, then it is said to be an Overflow condition.
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.
#include <stdio.h>
#include <stdlib.h>
#define SIZE 4
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:
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.
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
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. 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.
Algorithm:
This algorithm finds the VALUE of an arithmetic expression P written in Postfix notation.
[End of if structure]
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 → )
Algorithm
POLISH (P, Q)
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
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");
}
}
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.
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.
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.
}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");
}
}
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");
}
struct Node
{
int data;
struct Node *next;
};
new_node->data = new_data;
new_node->next = (*head_ref);
(*head_ref) = new_node;
}
if (prev_node == NULL)
{
printf("the given previous node cannot be NULL");
return;
}
new_node->next = prev_node->next;
prev_node->next = new_node;
}
new_node->data = new_data;
new_node->next = NULL;
if (*head_ref == NULL)
{
*head_ref = new_node;
return;
}
last->next = new_node;
return;
}
int main()
{
struct Node* head = NULL;
append(&head, 6);
push(&head, 7);
push(&head, 1);
append(&head, 4);
insertAfter(head->next, 8);
return 0;
}
C code:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
if (temp == NULL)
return;
prev->next = temp->next;
free(temp);
}
int main()
{
struct Node* head = NULL;
push(&head, 7);
push(&head, 1);
push(&head, 3);
push(&head, 2);
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.
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.
Illustration:
See the below illustration where E is being inserted at the beginning of the doubly linked list.
Illustration:
See the below illustration where ‘E‘ is being inserted after ‘B‘.
2.6
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.
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.
struct Node {
int data;
struct Node *next;
};
1. Insertion
2. Deletion
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:
After insertion
3) Insertion in between the nodes: To insert a node in between the two nodes, follow these steps:
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.
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.