You are on page 1of 43

Unit-2

Introduction To Stack

● A Stack is a linear data structure that follows the LIFO (Last-In-First-Out) principle.
Stack has one end, whereas the Queue has two ends (front and rear).
● It contains only one pointer pointing to the topmost element of the stack.
● Whenever an element is added in the stack, it is added on the top of the stack, and the
element can be deleted only from the stack.
● In other words, a stack can be defined as a container in which insertion and deletion can
be done from the one end known as the top of the stack.
Some key points related to stack
● It is called a stack because it behaves like a real-world stack, piles of books, etc.
● A Stack is an abstract data type with a predefined capacity, which means that it can store
the elements of a limited size.
● It is a data structure that follows some order to insert and delete the elements, and that
order can be LIFO or FILO.
Working of Stack
● Stack works on the LIFO pattern. As we can observe in the below figure there are five
memory blocks in the stack; therefore, the size of the stack is 5.
● Suppose we want to store the elements in a stack and let's assume that stack is empty. We
have taken the stack of size 5 as shown below in which we are pushing the elements one
by one until the stack becomes full.
● Since our stack is full as the size of the stack is 5. In the above cases, we can observe that
it goes from the top to the bottom when we were entering the new element in the stack.
The stack gets filled up from the bottom to the top.
● When we perform the delete operation on the stack, there is only one way for entry and
exit as the other end is closed. It follows the LIFO pattern, which means that the value
entered first will be removed last. In the above case, the value 5 is entered first, so it will
be removed only after the deletion of all the other elements.
Standard Stack Operations
The following are some common operations implemented on the stack:
● push(): When we insert an element in a stack then the operation is known as a push. If the
stack is full then the overflow condition occurs.
● pop(): When we delete an element from the stack, the operation is known as a pop. If the
stack is empty means that no element exists in the stack, this state is known as an
underflow state.
● isEmpty(): It determines whether the stack is empty or not.
● isFull(): It determines whether the stack is full or not.'
● peek(): It returns the element at the given position.
● count(): It returns the total number of elements available in a stack.
● change(): It changes the element at the given position.
● display(): It prints all the elements available in the stack.
PUSH operation
The steps involved in the PUSH operation is given below:
● Before inserting an element in a stack, we check whether the stack is full.
● If we try to insert the element in a stack, and the stack is full, then the overflow condition
occurs.
● When we initialize a stack, we set the value of top as -1 to check that the stack is empty.
● When the new element is pushed in a stack, first, the value of the top gets incremented,
i.e., top=top+1, and the element will be placed at the new position of the top.
● The elements will be inserted until we reach the max size of the stack.
POP operation
The steps involved in the POP operation is given below:
● Before deleting the element from the stack, we check whether the stack is empty.
● If we try to delete the element from the empty stack, then the underflow condition occurs.
● If the stack is not empty, we first access the element which is pointed by the top
● Once the pop operation is performed, the top is decremented by 1, i.e., top=top-1.
Array implementation of Stack

In array implementation, the stack is formed by using the array. All the operations regarding the
stack are performed using arrays. Lets see how each operation can be implemented on the stack
using array data structure.

● Stack: A linear data structure that follows LIFO (Last In, First Out) principle. Elements
are added and removed from the same end, called the top.
● Array: A fixed-size contiguous memory block used to store elements of the same data
type.
Algorithm:
1. Initialization:
○ Define an array stack of a fixed size MAX_SIZE.
○ Declare a variable top to keep track of the top element's index, initially set to -1
(empty stack).
2. Peek Operation (Viewing the Top Element):
○ Check if the stack is empty:
■ If top is -1, raise a stack underflow error.
○ Otherwise, return the element at the top: return stack[top]
3. IsEmpty Operation (Checking for Emptiness):
○ Return true if top is -1, indicating an empty stack.
○ Otherwise, return false.
4. IsFull Operation (Checking for Fullness):
○ Return true if top is equal to MAX_SIZE - 1, indicating a full stack.
○ Otherwise, return false.

Adding an element onto the stack (push operation)

Adding an element into the top of the stack is referred to as push operation. Push operation
involves following two steps.
1. Increment the variable Top so that it can now refer to the next memory location.
2. Add an element at the position of the incremented top. This is referred to as adding a new
element at the top of the stack.
Stack is overflown when we try to insert an element into a completely filled stack therefore, our
main function must always avoid stack overflow condition.
Algorithm:
Push Operation (Adding an Element):
○ Check if the stack is full:
■ If top is equal to MAX_SIZE - 1, raise a stack overflow error.
○ Otherwise, increment top: top = top + 1
○ Add the new element to the stack[top] position.
begin:
if top = n then stack full
top = top + 1
stack (top) : = item;
end
Time Complexity : o(1)

Deletion of an element from a stack (Pop operation)

Deletion of an element from the top of the stack is called pop operation. The value of the
variable top will be incremented by 1 whenever an item is deleted from the stack. The top most
element of the stack is stored in another variable and then the top is decremented by 1. the
operation returns the deleted value that was stored in another variable as the result.
The underflow condition occurs when we try to delete an element from an already empty stack.
Algorithm :
Pop Operation (Removing an Element):
○ Check if the stack is empty:
■ If top is -1, raise a stack underflow error.
○ Otherwise, store the top element in a variable: temp = stack[top]
○ Decrement top: top = top - 1
○ Return the removed element temp.

begin
if top = 0 then stack empty;
item := stack(top);
top = top - 1;
end;
Time Complexity : o(1)
Visiting each element of the stack (Peek operation)

Peek operation involves returning the element which is present at the top of the stack without
deleting it. Underflow condition can occur if we try to return the top element in an already empty
stack.
Algorithm :
PEEK (STACK, TOP)
Begin
if top = -1 then stack empty
item = stack[top]
return item
End
Time complexity: o(n)

Time and Space Complexity:


● Time Complexity:
○ Push, Pop, Peek: O(1) (constant time)
○ IsEmpty, IsFull: O(1) (constant time)
● Space Complexity: O(n), where n is the maximum size of the stack (array size).

Implementation using Array:


#include <iostream>
using namespace std;
int stack[100], n=100, top=-1;
void push(int val) {
if(top>=n-1)
cout<<"Stack Overflow"<<endl;
else {
top++;
stack[top]=val;
}
}
void pop() {
if(top<=-1)
cout<<"Stack Underflow"<<endl;
else {
cout<<"The popped element is "<< stack[top] <<endl;
top--;
}
}
void display() {
if(top>=0) {
cout<<"Stack elements are:";
for(int i=top; i>=0; i--)
cout<<stack[i]<<" ";
cout<<endl;
} else
cout<<"Stack is empty";
}
int main() {
int ch, val;
cout<<"1) Push in stack"<<endl;
cout<<"2) Pop from stack"<<endl;
cout<<"3) Display stack"<<endl;
cout<<"4) Exit"<<endl;
do {
cout<<"Enter choice: "<<endl;
cin>>ch;
switch(ch) {
case 1: {
cout<<"Enter value to be pushed:"<<endl;
cin>>val;
push(val);
break;
}
case 2: {
pop();
break;
}
case 3: {
display();
break;
}
case 4: {
cout<<"Exit"<<endl;
break;
}
default: {
cout<<"Invalid Choice"<<endl;
}
}
}while(ch!=4);
return 0;
}

Output
1) Push in stack
2) Pop from stack
3) Display stack
4) Exit

Enter choice: 1
Enter value to be pushed: 2
Enter choice: 1
Enter value to be pushed: 6
Enter choice: 1
Enter value to be pushed: 8
Enter choice: 1
Enter value to be pushed: 7
Enter choice: 2
The popped element is 7
Enter choice: 3
Stack elements are:8 6 2
Enter choice: 5
Invalid Choice
Enter choice: 4
Exit

Linked list implementation of stack

❖ Instead of using arrays, we can also use linked lists to implement stack.
❖ Linked list allocates the memory dynamically.
❖ However, time complexity in both the scenarios is same for all the operations i.e. push,
pop and peek.
❖ In linked list implementation of stack, the nodes are maintained non-contiguously in the
memory. Each node contains a pointer to its immediate successor node in the stack.
❖ Stack is said to be overflown if the space left in the memory heap is not enough to create
a node.
The top most node in the stack always contains null in its address field. Let's discuss the
way in which each operation is performed in the linked list implementation of stack.
Key Concepts:

● Stack: A linear data structure that follows LIFO (Last In, First Out) principle.
● Linked List: A dynamic data structure where elements (nodes) are not stored
contiguously in memory but linked together using pointers.

Algorithm:

1. Node Structure:
○ Define a Node structure with two fields:
■ data: Stores the element's value.
■ next: Points to the next node in the list.
2. Initialization:
○ Create a top pointer, initially set to NULL (empty stack).
3. Push Operation (Adding an Element):
○ Create a new node with the given data.
○ Set the next pointer of the new node to the current top.
○ Update top to point to the new node.
4. Pop Operation (Removing an Element):
○ Check if the stack is empty:
■ If top is NULL, raise a stack underflow error.
○ Store the current top node in a temporary variable.
○ Move top to point to the next node in the list.
○ Return the data from the removed node and deallocate its memory.
5. Peek Operation (Viewing the Top Element):
○ Check if the stack is empty:
■ If top is NULL, raise a stack underflow error.
○ Return the data from the top node.
6. IsEmpty Operation (Checking for Emptiness):
○ Return true if top is NULL.
○ Otherwise, return false.
Adding a node to the stack (Push operation)

● Adding a node to the stack is referred to as push operation. Pushing an element to a stack
in linked list implementation is different from that of an array implementation.
● In order to push an element onto the stack, the following steps are involved. Create a
node first and allocate memory to it.
○ If the list is empty then the item is to be pushed as the start node of the list. This
includes assigning value to the data part of the node and assigning null to the
address part of the node.
○ If there are some nodes in the list already, then we have to add the new element in
the beginning of the list (to not violate the property of the stack).
● For this purpose, assign the address of the starting element to the address field of the new
node and make the new node, the starting node of the list.
Time Complexity : o(1)

Deleting a node from the stack (POP operation)

● Deleting a node from the top of the stack is referred to as a pop operation. Deleting a
node from the linked list implementation of stack is different from that in the array
implementation. In order to pop an element from the stack, we need to follow the
following steps :
● Check for the underflow condition: The underflow condition occurs when we try to pop
from an already empty stack. The stack will be empty if the head pointer of the list points
to null.
● Adjust the head pointer accordingly: In stack, the elements are popped only from one
end, therefore, the value stored in the head pointer must be deleted and the node must be
freed. The next node of the head node now becomes the head node.
Time Complexity : o(n)

Display the nodes (Traversing)

● Displaying all the nodes of a stack needs traversing all the nodes of the linked list
organized in the form of stack. For this purpose, we need to follow the following steps.
● Copy the head pointer into a temporary pointer.
● Move the temporary pointer through all the nodes of the list and print the value field
attached to every node.
Time Complexity : o(n)

C++
#include <iostream>

using namespace std;

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

// Stack class
class Stack {
private:
Node* top; // Pointer to the top node

public:
Stack() {
top = nullptr; // Initially empty stack
}

bool isEmpty() {
return top == nullptr;
}

void push(int data) {


Node* newNode = new Node();
newNode->data = data;
newNode->next = top; // New node points to current top
top = newNode; // Update top to the new node
cout << data << " pushed to stack\n";
}

int pop() {
if (isEmpty()) {
cout << "Stack Underflow\n";
return -1;
}
Node* temp = top; // Store current top node
int data = temp->data;
top = top->next; // Move top to the next node
delete temp; // Delete the old top node
return data;
}

int peek() {
if (isEmpty()) {
cout << "Stack is empty\n";
return -1;
}
return top->data;
}

void display() {
if (isEmpty()) {
cout << "Stack is empty\n";
return;
}
Node* temp = top;
cout << "Stack elements: ";
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
};

int main() {
Stack s;

s.push(10);
s.push(20);
s.push(30);

s.display(); // Output: Stack elements: 30 20 10

cout << s.pop() << " popped from stack\n";

s.display(); // Output: Stack elements: 20 10

cout << "Top element is " << s.peek() << endl; // Output: Top element is 20

return 0;
}
OUTPUT:

10 pushed to stack
20 pushed to stack
30 pushed to stack
Stack elements: 30 20 10
30 popped from stack
Stack elements: 20 10
Top element is 20

Advantages of Stack
The advantages of using stack are listed below:
1. Efficient data management: Stack helps you manage the data in a LIFO (last in, first out)
method, which is not possible with a Linked list and array.
2. Efficient management of functions: When a function is called, the local variables are stored
in a stack, and it is automatically destroyed once returned.
3. Control over memory: Stack allows you to control how memory is allocated and deallocated.
4. Smart memory management: Stack automatically cleans up the object.
5. Not easily corrupted: Stack does not get corrupted easily; hence it is more secure and
reliable.
6. Does not allow resizing of variables: Variables cannot be resized.

Disadvantages of Stack
The disadvantages of using stack are listed below:
1. Limited memory size: Stack memory is very limited.
2. Chances of stack overflow: Creating too many objects on the stack can increase the risk of
stack overflow.
3. Random access is not possible: In a stack, random accessing the data is not possible.
4. Unreliable: When variable storage will get overwritten, it will sometimes leads to undefined
behavior of the function or program.
5. Undesired termination: The stack will fall outside of the memory area, which might lead to
an abnormal termination.
Application of Stack in Data Structure are as following:

· Evaluation of Arithmetic Expressions


· Backtracking
· Delimiter Checking
· Reverse a Data
· Processing Function Calls

1. Evaluation of Arithmetic Expressions

In computer languages, a stack is an extremely efficient data structure for evaluating arithmetic
statements. Operands and operators are the components of an arithmetic expression.
The arithmetic expression may additionally contain parenthesis such as "left parenthesis" and
"right parenthesis," in addition to operands and operators.
Example: A + (B – C)
The normal precedence rules for arithmetic expressions must be understood in order to evaluate
the expressions. The following are the five fundamental arithmetic operators’ precedence rules:

Operators Associativity Precedence

^ exponentiation Right to left Highest followed by


*Multiplication and
/division

*Multiplication, Left to right Highest followed by +


/division addition and – subtraction

+ addition, – Left to right lowest


subtraction
Evaluation of Arithmetic Expression requires two steps:
1. Put the provided expression first in special notation.
2. In this new notation, evaluate the expression.
Notations for Arithmetic Expression
There are three notations to represent an arithmetic expression:
● Infix Notation
● Prefix Notation
● Postfix Notation
Infix Notation
Each operator is positioned between the operands in an expression written using the infix
notation. Depending on the requirements of the task, infix expressions may be parenthesized or
not.
Example: A + B, (C – D) etc.
Because the operator appears between the operands, all of these expressions are written in infix
notation.
Prefix Notation
The operator is listed before the operands in the prefix notation. Since the Polish mathematician
invented this system, it is frequently referred to as polish notation.
Example: + A B, -CD etc.
Because the operator occurs before the operands in all of these expressions, prefix notation is
used.
Postfix Notation
The operator is listed after the operands in postfix notation. Polish notation is simply reversed in
this notation, which is also referred to as Reverse Polish notation.
Example: AB +, CD+, etc.
All these expressions are in postfix notation because the operator comes after the operands.

Algorithm
1. Scan the given expression from left to right, If symbol is an operand, push it in operand stack
2. Initialize operator stack with a dummy operator with least precedence(say #)
3. If the symbol is an operator, then do the following
i. pop an operator from the stack
ii. check its precedence with the symbol.
iii. if the precedence of the symbol is higher then push the poped operator and then the
symbol.
iv. if symbol is lower or equal precedence than the poped operator then pop two operands
and
perform the operation and store the result
v. If we encounter an opening parenthesis (,
a. we will push '(' it in the operator stack and continues till we encounter ')'
b. If we encounter a closing parenthesis ), we pop an operator from the operator stack and
two data elements from the operand stack and apply the operator to the numeric values and
store the result in the value stack.
vi. Repeat the step until the dummy operator(#)

4. Once we have iterated the entire expression, The last value in the value stack will be the result.

EXPRESSION CONVERSION
Converting one form of expression to another is one of the important applications
of stacks.
● Infix to prefix
● Infix to postfix
● Prefix to Infix
● Prefix to Postfix
● Postfix to Infix
● Postfix to Infix

1. INFIX TO POSTFIX CONVERSION:


Postfix notation is also known as Reverse Polish Notation (RPN) in which every
operator follows all of its operands. This notation is parenthesis free.

For Example:
(A+B) is expressed as AB+ in Postfix.

But before learning about the conversion let us know the operator precedence
for any equation.
● Operator precedence determines the grouping of terms in an expression.
● The associativity of an operator is a property that determines how operators of the
same precedence are grouped in the absence of parentheses.
● This affects how an expression is evaluated.
● Certain operators have higher precedence than others; for example, the
multiplication operator has higher precedence than the addition operator:
For example, x = 7 + 3 * 2; here, x is assigned 13, not 20 because operator * has higher
precedence than +, so it first gets multiplied with 3*2 and then adds into 7. Following is
the table representing various operator precedence, for different conversion.

Category Operator Associativity


Postfix () [] -> . ++ - - Left to right
Unary + - ! ~ ++ - - (type)* & sizeof Right to left
Multiplicative */% Left to right
Additive +- Left to right
Shift << >> Left to right
Relational < <= > >= Left to right
Equality == != Left to right
Bitwise AND & Left to right
Bitwise XOR ^ Left to right
Bitwise OR | Left to right
Logical AND && Left to right
Logical OR || Left to right
Conditional ?: Right to left
Assignment = += -= *= /= %=>>= <<= &= ^= |= Right to left
Comma , Left to right

Algorithm for Infix to Postfix conversion:

1) Scan the infix expression from left to right.


2) If the scanned character is an operand, output it.
3) Else,
If the precedence of the scanned operator is greater than the precedence of the operator in
the stack(or the stack is empty or the stack contains a ‘(‘ ), push it.
Else, Pop all the operators from the stack which are greater than or equal to in precedence
than that of the scanned operator. After doing that Push the scanned operator to the stack.
(If you encounter parenthesis while popping then stop there and push the scanned operator
in the stack.).
4) If the scanned character is an ‘(‘, push it to the stack.
5) If the scanned character is an ‘)’, pop the stack and and output it until a ‘(‘ is encountered,
and discard both the parenthesis.
6) Repeat steps 2-6 until infix expression is scanned.
7) Print the output.
8) Pop and output from the stack until it is not empty.

EXAMPLE PROGRAM:

//infix to postfix
#include<iostream>
#include<cstring>
#define max 50 //for string function
using namespace std;
class infpos
{
char infix[max],post[max],st[max];
int k,i,top;
char ch,h;
public:
infpos()
{
k=-1;
top=-1;
}

void getdata()
{
cout<<"\n\nEnter the postfix expression\t";
cin>>infix;
}

void infixtopost();

int priority(char c)
{
if(c=='^')
return(3);
else if(c=='*'||c=='/')
return(2);
else if(c=='+'||c=='-')
return(1);
else
return(0);
}

void push(char c)
{
if(top==max-1)
cout<<"Stack overflow";
else {
top++;
st[top]=c;
}
}

char pop()
{
char c;
if(top==-1)
return(-1);
else {
c=st[top];
top--;
return(c);
}
}

void insert(char c)
{
cout<<"in:"<<c<<endl;
k++;
post[k]=c;
}
};
void infpos::infixtopost()
{
for(i=0;i<strlen(infix);i++)
{
ch=infix[i];
if(isalpha(ch))
insert(ch);
else if(ch=='(')
push(ch);
else if(ch==')')
{
while(1)
{
h=pop();
if(h!='(')
insert(h);
if(h=='(')
break;
}
}
else
{
while(1)
{
if(priority(ch)>priority(st[top]))
{
push(ch);
break;
}
else
{
h=pop();
insert(h);
}
}
}
}
while(top!=-1)
{
h=pop();
if(h!='(')
insert(h);
}
insert('\0');
cout<<"\nThe postfix notation is \t"<<post;

int main()
{
infpos s;
cout<<"\tINFIX TO POSTFIX";
s.getdata();
s.infixtopost();
return 0;
}
QUEUE DATA STRUCTURE:
● A queue is a useful data structure in programming.
● Queue follows the First In First Out (FIFO) rule - the item that goes in first is the
item that comes out first too.
● One end is always used to insert data (enqueue) and the other is used to remove
data (dequeue).
● Queues provide us with access to the beginning and the end of the list
respectively called the head and tail.

APPLICATIONS OF QUEUE:
The queue is used when things don’t have to be processed immediately, but have
to be processed in First In First Out order
1. When a resource is shared among multiple consumers. Examples include CPU
scheduling, Disk Scheduling.
2. When data is transferred asynchronously (data not necessarily received at the same
rate as sent) between two processes. Examples include IO Buffers, pipes, file IO, etc.
3. Print queue – Jobs sent to the printer
4. Operating system maintains queue in allocating the process to each unit by storing
them in the buffer
5. The interrupts that occur during the system operation are also maintained in queue
6. The allocation of memory for a particular resource (Printer, Scanner, any handheld
devices) is also maintained in the queue.

OPERATIONS ON QUEUE:
Following are the primary operations, a programmer can do in the queue:

▪ Enqueue.
▪ Dequeue.
▪ Peek.
▪ Isfull.
▪ IsEmpty.
Lets us see them in detail in the following.

i) ENQUEUE:
Adds an item to the queue. If the queue is full, then it is said to be an
Overflow condition.
Queues maintain two data pointers, front and rear. Therefore, its operations are
comparatively difficult to implement than that of stacks.
The following steps should be taken to enqueue (insert) data into a queue −
Step 1 − Check if the queue is full.
Step 2 − If the queue is full, produce overflow error and exit.
Step 3 − If the queue is not full, increment the rear pointer to point the
next empty space.
Step 4 − Add a data element to the queue location, where the rear is
pointing.
Step 5 − return success.
Sometimes, we also check to see if a queue is initialized or not, to handle
any unforeseen situations.

ALGORITHM FOR ENQUEUE OPERATION:


procedure enqueue(data)
if queue is full
return overflow
endif
rear ← rear + 1
queue[rear] ← data
return true
end procedure.

ii) DEQUEUE:
Accessing data from the queue is a process of two tasks − access the data
where the front is pointing and remove the data after access. The following steps
are taken to perform dequeue operation −
Step 1 − Check if the queue is empty.
Step 2 − If the queue is empty, produce underflow error and exit.
Step 3 − If the queue is not empty, access the data where front is pointing.
Step 4 − Increment front pointer to point to the next available data
element.
Step 5 − Return success.

ALGORITHM FOR DEQUEUE OPERATION


procedure dequeue
if queue is empty
return underflow
end if
data = queue[front]
front ← front + 1
return true
end procedure
iii) PEEK:
This function helps to see the data at the front of the queue. The algorithm
of peek() function is as follows –

ALGORITHM:
begin procedure peek
return queue[front]
end procedure

iv) ISFULL:
As we are using single dimension array to implement queue, we just check
for the rear pointer to reach at MAXSIZE to determine that the queue is full. In
case we maintain the queue in a circular linked-list, the algorithm will differ.
Algorithm of isfull() function –

ALGORITHM:
begin procedure isfull
if rear equals to MAXSIZE
return true
else
return false
Endif
end procedure

v) ISEMPTY:
This is used to check whether the given queue is empty or not.

ALGORITHM:
begin procedure isempty
if the front is less than MIN or if the front is greater than the rear
return true
else
return false
endif
end procedure

If the value of front is less than MIN or 0, it tells that the queue is not yet
initialized, hence empty.
EXAMPLE:
#include<iostream>
using namespace std;
class queue
{
public:
int a[3];
int start,end;
queue()
{
start=end=0;
}
void enqueue();
void dequeue();
void display();
};

void queue::enqueue()
{
int val;
if(end==3)
{
cout<<"Queue is full";
}
else
{
cout<<"Insert the first value ";
cin>>val;
a[end++]=val;
}
}
void queue::dequeue()
{
if(start==end)
{
cout<<"Queue is empty";
}
else
{
cout<<"\nRemoved value "<<a[start];
a[start++];
}
}
void queue::display()
{
int i;
if(start==end)
{
cout<<"Queue is full";
}
else
{
for(i=start;i<end;i++)
cout<<"\t"<<a[i];
}
}
int main()
{
int a;
queue c;
cout<<"Queue using array";
cout<<"\n\n1. enqueue";
cout<<"\n2. Dequeue";
cout<<"\n3.Display";
cout<<"\n4.Exit";
do
{
cout<<"\n\nenter your choice ";
cin>>a;
switch(a)
{
case 1:
c.enqueue();
break;
case 2:
c.dequeue();
break;
case 3:
c.display();
break;
case 4:
cout<<"Exiting program";
break;
default:
cout<<"\nLimit Reached";
}
}while(a!=4);
return 0;
}

CIRCULAR QUEUE:
Circular Queue is a linear data structure in which the operations are performed
based on FIFO (First In First Out) principle and the last position is connected back to the
first position to make a circle. It is also called ‘Ring Buffer’.

ALGORITHM:
● FRONT: Get the front item from queue.
● REAR: Get the last item from 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
Rear position.
1. Steps: Check whether queue is Full – Check ((rear == SIZE-1
&& front == 0) || (rear == front-1)).
2. If it is full then display Queue is full. If queue is not full then,
check if (rear == SIZE – 1 && front != 0) if it is true then set
rear=0 and insert element.

● 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.
1. Steps: Check whether the queue is Empty means check
(front==-1).
2. If it is empty then display Queue is empty. If the queue is not
empty then step 3
3. Check if (front==rear) if it is true then set front=rear= -1 else
check if (front==size-1), if it is true then set front=0 and return
the element.

APPLICATION OF CIRCULAR QUEUE:


Below we have some common real-world examples where circular queues are
used:
1. Memory Management: The unused memory locations in the case of
ordinary queues can be utilized in circular queues.
2. Traffic system: In computer-controlled traffic system, circular queues
are used to switch on the traffic lights one by one repeatedly as per
the time set.
3. CPU Scheduling: Operating systems often maintain a queue of
processes that are ready to execute or that are waiting for a particular
event to occur.

Implementation of Queue using array

#include <iostream>

#define MAX_SIZE 100 // Maximum size of the queue

using namespace std;

class Queue {
int front, rear; // Indices for front and rear elements
int arr[MAX_SIZE];

public:
Queue() {
front = rear = -1; // Initially empty queue
}

bool isEmpty() {
return front == -1;
}

bool isFull() {
return rear == MAX_SIZE - 1;
}

void enqueue(int data) {


if (isFull()) {
cout << "Queue Overflow\n";
return;
}
if (isEmpty()) {
front = 0;
}
rear++;
arr[rear] = data;
cout << data << " enqueued to queue\n";
}

int dequeue() {
if (isEmpty()) {
cout << "Queue Underflow\n";
return -1;
}
int data = arr[front];
if (front == rear) { // Single element in queue
front = rear = -1;
} else {
front++;
}
return data;
}

int peek() {
if (isEmpty()) {
cout << "Queue is empty\n";
return -1;
}
return arr[front];
}

void display() {
if (isEmpty()) {
cout << "Queue is empty\n";
return;
}
cout << "Queue elements: ";
for (int i = front; i <= rear; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
};

int main() {
Queue q;

q.enqueue(10);
q.enqueue(20);
q.enqueue(30);

q.display(); // Output: Queue elements: 10 20 30

cout << q.dequeue() << " dequeued from queue\n";

q.display(); // Output: Queue elements: 20 30

cout << "Front element is " << q.peek() << endl; // Output: Front element is 20

return 0;
}

10 enqueued to queue
20 enqueued to queue
30 enqueued to queue
Queue elements: 10 20 30
10 dequeued from queue
Queue elements: 20 30
Front element is 20

Implementation of Queue using pointer in C++

#include <iostream>

using namespace std;

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

// Queue class
class Queue {
private:
Node* front, *rear; // Pointers to front and rear nodes

public:
Queue() {
front = rear = nullptr; // Initially empty queue
}

bool isEmpty() {
return front == nullptr;
}

void enqueue(int data) {


Node* newNode = new Node();
newNode->data = data;
newNode->next = nullptr;

if (isEmpty()) {
front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
cout << data << " enqueued to queue\n";
}

int dequeue() {
if (isEmpty()) {
cout << "Queue Underflow\n";
return -1;
}
Node* temp = front;
int data = temp->data;
front = front->next;

if (front == nullptr) { // If queue becomes empty


rear = nullptr;
}

delete temp;
return data;
}

int peek() {
if (isEmpty()) {
cout << "Queue is empty\n";
return -1;
}
return front->data;
}

void display() {
if (isEmpty()) {
cout << "Queue is empty\n";
return;
}
Node* temp = front;
cout << "Queue elements: ";
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
};

// ... Main function (same as in the array implementation)

10 enqueued to queue
20 enqueued to queue
30 enqueued to queue
Queue elements: 10 20 30
10 dequeued from queue
Queue elements: 20 30
Front element is 20

Explanation:
1. Node Structure:
○ data: Stores the element's value.
○ next: Pointer to the next node in the queue.
2. Queue Class:
○ front: Pointer to the front node.
○ rear: Pointer to the rear node.
○ isEmpty(): Checks if the queue is empty.
○ enqueue(data): Inserts an element at the rear.
○ dequeue(): Removes and returns the front element.
○ peek(): Returns the front element without removing it.
○ display(): Prints the elements of the queue.
3. Main Function:
○ Unchanged from the array implementation.

Deque (or double-ended queue)


The deque stands for Double Ended Queue. Deque is a linear data structure where the insertion
and deletion operations are performed from both ends. We can say that deque is a generalized version of
the queue.

Though the insertion and deletion in a deque can be performed on both ends, it does not follow
the FIFO rule. The representation of a deque is given as follows -

Types of deque
There are two types of deque -
○ Input restricted queue
○ Output restricted queue
Input restricted Queue
In input restricted queue, insertion operation can be performed at only one end, while deletion can be
performed from both ends.
Output restricted Queue
In output restricted queue, deletion operation can be performed at only one end, while insertion can be
performed from both ends.

Operations performed on deque


There are the following operations that can be applied on a deque -
○ Insertion at front
○ Insertion at rear
○ Deletion at front
○ Deletion at rear
We can also perform peek operations in the deque along with the operations listed above. Through peek
operation, we can get the deque's front and rear elements. So, in addition to the above operations,
following operations are also supported in deque -
○ Get the front item from the deque
○ Get the rear item from the deque
○ Check whether the deque is full or not
○ Checks whether the deque is empty or not
Now, let's understand the operation performed on deque using an example.

1. Insertion at the front end


In this operation, the element is inserted from the front end of the queue. Before implementing the
operation, we first have to check whether the queue is full or not. If the queue is not full, then the element
can be inserted from the front end by using the below conditions -
○ If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
○ Otherwise, check the position of the front if the front is less than 1 (front < 1), then reinitialize it
by front = n - 1, i.e., the last index of the array.

2. Insertion at the rear end


In this operation, the element is inserted from the rear end of the queue. Before implementing the
operation, we first have to check again whether the queue is full or not. If the queue is not full, then the
element can be inserted from the rear end by using the below conditions -
○ If the queue is empty, both rear and front are initialized with 0. Now, both will point to the first
element.
○ Otherwise, increment the rear by 1. If the rear is at last index (or size - 1), then instead of
increasing it by 1, we have to make it equal to 0.

3. Deletion at the front end


In this operation, the element is deleted from the front end of the queue. Before implementing the
operation, we first have to check whether the queue is empty or not.
➢ If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the
deletion. If the queue is not full, then the element can be inserted from the front end by using the
below conditions -
➢ If the deque has only one element, set rear = -1 and front = -1.
➢ Else if front is at end (that means front = size - 1), set front = 0.
➢ Else increment the front by 1, (i.e., front = front + 1).

4. Deletion at the rear end


In this operation, the element is deleted from the rear end of the queue. Before implementing the
operation, we first have to check whether the queue is empty or not.
➢ If the queue is empty, i.e., front = -1, it is the underflow condition, and we cannot perform the
deletion.
➢ If the deque has only one element, set rear = -1 and front = -1.
➢ If rear = 0 (rear is at front), then set rear = n - 1.
➢ Else, decrement the rear by 1 (or, rear = rear -1).
❖ Check empty
This operation is performed to check whether the deque is empty or not. If front = -1, it means that the
deque is empty.
● Check full
This operation is performed to check whether the deque is full or not. If front = rear + 1, or front = 0 and
rear = n - 1 it means that the deque is full.
The time complexity of all of the above operations of the deque is O(1), i.e., constant.

Applications of deque
○ Deque can be used as both stack and queue, as it supports both operations.
○ Deque can be used as a palindrome checker means that if we read the string from both ends, the
string would be the same.

TREE:
The tree is a hierarchical (or non-linear) data structure that naturally forms a hierarchy.
In computer science, a tree is a widely used abstract data type that simulates a hierarchical tree
structure. A tree is a collection of entities called nodes. Nodes are connected by edges. Each
node contains a value or data, and it may or may not have a child node, these nodes are of four
types,
● Root node
● Parent node
● Child node
● Leaf node
Where root node is the topmost node of the tree, it also acts as a parent node for the
lower-level nodes, whereas parent node is the node has connecting nodes below its priority level
and these nodes can also be a child node of other higher-level nodes, Finally child nodes are
nodes which are connected to the node higher than its hierarchy level, These nodes can also be a
parent node for the lower level nodes. Leaf nodes are the bottom-most node which doesn’t have
any child nodes. All the nodes are connected through lines called the Edges or paths.

One reason to use trees might be because you want to store information that naturally
forms a hierarchy. For example, the file system on a computer.

TREE TERMINOLOGIES:
Before Getting started with the types, let us see some of the important terms we need to
remember in tree data structure.

● Path − Path refers to the sequence of nodes along the edges of a tree.
● Root − The node at the top of the tree is called root. There is only one root per tree
and one path from the root node to any node.
● Parent − Any node except the root node has one edge upward to a node called parent.
● Child − The node below a given node connected by its edge downward is called its
child node.
● Leaf − The node which does not have any child node is called the leaf node.
● Subtree − Subtree represents the descendants of a node.
● Visiting − Visiting refers to checking the value of a node when control is on the node.
● Traversing − Traversing means passing through nodes in a specific order.
● Levels − Level of a node represents the generation of a node. If the root node is at
level 0, then its next child node is at level 1, its grandchild is at level 2, and so on.
● keys − Key represents the value of a node based on which a search operation is to be
carried out for a node.
● Depth of a node- Number of edges from the root to the node.
● Height of a node- number of edges from the node to the deepest leaf. Height of the
tree is the height of the root.

Types of tree:

Now in trees, there are several types of trees available, following are some of those:

1. GENERAL TREE:
A general tree is a tree data structure where there are no constraints on the
hierarchical structure. If it has the following properties then it is a general tree:
● Follow properties of a tree.
● A node can have any number of children.

2. BINARY TREE:
Binary Tree is a special data structure used for data storage purposes. A binary
tree has a special condition that each node can have a maximum of two children (i.e.) every
node or vertex has either no child node or one child node or two child nodes. A binary tree
has the benefits of both an ordered array and a linked list as search is as quick as in a sorted
array and insertion or deletion operation are as fast as in linked list.
This Binary tree has following types they are:
● Rooted binary tree: It has a root node and every node has utmost two
children.
● Full binary tree: It is a tree in which every node in the tree has either 0 or 2
children.

● Complete binary tree: It is a binary tree in which every level, except


possibly the last, is completely filled, and all nodes are as far left as possible.

● Perfect binary tree: It is a binary tree in which all interior nodes have two
children and all leaves have the same depth or same level.

● Balanced binary tree: A binary tree is height-balanced if it satisfies the


following constraints:
1. The left and right subtrees' heights differ by at most one.
2. The left subtree is balanced.
3. The right subtree is balanced.
4. An empty tree is height-balanced.

What is Binary Tree Data Structure?

Binary Tree is defined as a tree data structure where each node has at most 2 children. Since each
element in a binary tree can have only 2 children, we typically name them the left and right child.

Binary Tree Representation

A Binary tree is represented by a pointer to the topmost node (commonly known as the “root”) of
the tree. If the tree is empty, then the value of the root is NULL. Each node of a Binary Tree
contains the following parts:
1. Data
2. Pointer to left child
3. Pointer to right child
Basic Operation On Binary Tree:
● Inserting an element.
● Removing an element.
● Searching for an element.
● Traversing the tree.

Implementation of Binary tree using Array


#include <iostream>

using namespace std;

class BinaryTree {
private:
int tree[100]; // Array to hold the tree (max size 100)
int size; // Current number of nodes

public:
BinaryTree() {
for (int i = 0; i < 100; i++) {
tree[i] = -1; // Initialize all nodes as empty
}
size = 0;
}

void insert(int value) {


if (size == 100) {
cout << "Tree is full!" << endl;
return;
}

// Find an empty slot


int i;
for (i = 0; i < 100; i++) {
if (tree[i] == -1) {
break;
}
}

tree[i] = value;
size++;
}

void preOrder(int index = 0) {


if (index >= 100 || tree[index] == -1) {
return;
}

cout << tree[index] << " ";


preOrder(2 * index + 1); // Left subtree
preOrder(2 * index + 2); // Right subtree
}

// Similarly implement inOrder and postOrder traversals


};

int main() {
BinaryTree tree;
tree.insert(10);
tree.insert(5);
tree.insert(15);

cout << "Pre-order traversal: ";


tree.preOrder();
cout << endl;

return 0;
}

Pre-order traversal: 10 5 15

Explanation:
● The code inserts three values: 10, 5, and 15.
● It then performs a pre-order traversal, which visits nodes in the order: root, left child,
right child.
● Therefore, the output displays the nodes in the following order:
1. 10 (root)
2. 5 (left child of 10)
3. 15 (right child of 10)
Key points:
● Pre-order traversal: Visits the root first, then recursively traverses the left subtree, and
finally the right subtree.
● In-order traversal: Visits the left subtree, then the root, and finally the right subtree.
● Post-order traversal: Visits the left subtree, then the right subtree, and finally the root.

You might also like