Stack and Queue Data Structures Overview
Stack and Queue Data Structures Overview
Syllabus: Stacks: Definition, ADT, standard stack operations- array and linked list
implementations, applications - infix to postfix conversion, postfix expression evaluation,
parsing parenthesis, reverse of a string using stack, history of a browser, etc
Queues: Definition, ADT, standard queue operations - array and linked implementations,
Circular queues - Insertion and deletion operations. Real Time Applications of Queue:
Operating Systems and Task Scheduling, Networking and Message Queues, etc
========================================================================
Operations on stack
Push(): A data element is inserted into the stack.
Pop(): A data element is removed fron the stack.
isEmpty(): The function says that elements are in the stack present or no elements.
isFull(): It says that the stack is full of elements or not.
Peek(): Access the element from a required position.
Count(): Counts the number of elements from the stack.
Change(): Changes the particular element of the stack.
Display(): It prints all the elements of the stack.
Balancing of symbols
Conversion of Postfix into Prefix vice versa.
Redo-undo features at many places like editors, photoshop.
Forward and backward feature in web browsers.
Used in many algorithms like Towers of Hanoi, Knight tour problem, rat in a maze
N – queen and sudoku solver.
In graph algorithm kike Topological Sorting and Strongly Connected Components.
Variable: Top holds the location of the last element that enters into the stack.
Increment the variable Top so that it can now referee to the next memory location.
Add element at the position of incremented top. This is referred to as adding 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.
#include<iostream>
#define SIZE 5
class Stack
{
public:
int top = -1;
int arr[SIZE];
int pop()
{
if (top == -1)
{
cout << "Stack underflow" << endl;
exit(1);
}
else
{
int popValue = arr[top];
arr[top] = 0;
top--;
return popValue;
}
}
int count()
{
return (top + 1);
}
void display()
{
cout << "Elements in the Stack are ...";
for (int i = SIZE; i >= 0; i--)
{
cout << arr[i] <<" ";
}
cout<<endl;
}
};
int main()
{
Stack s1;
int option, postion, value;
do
{
cout << endl<<"Choose the below to continue or 0 to exit ...";
cin >> option;
switch (option)
{
case 1:
cout << "Enter an item to push in the stack ...";
cin >> value;
[Link](value);
break;
case 2:
cout << "Popped Element ...";
cout<< [Link]() << endl;
break;
case 3:
[Link]();
break;
case 4:
cout << "Number of elements in the Stack are: ";
cout<< [Link]() << endl;
break;
case 5:
cout << "Enter position of the element to be changed ... ";
cin >> postion;
cout << "Enter a new element ... ";
cin >> value;
[Link](postion, value);
break;
case 6:
cout << "Enter position of element you want to see ... ";
cin >> postion;
cout << "Value at position is ..." ;
cout<< [Link](postion) << endl;
break;
default:
cout << "Enter Correct Option number " << endl;
}
} while (option != 0);
return 0;
}
Implementation of Stack using Linked List:
A linked list is a linear data structure consisting of nodes, where each node contains data
and a reference (link) to the next node in the sequence. The last node typically points to
null or None, indicating the end of the list.
In the context of a stack implemented with a linked list, each node represents an element
in the stack.
Each node contains two components: data to store the actual element, and next to
reference the next node in the sequence.
Stack Class
The stack class has a reference to the top node.
The push operation adds a new node to the top of the stack.
The pop operation removes the top node from the stack.
The peek operation returns the data of the top node without removing it.
The isEmpty operation checks if the stack is empty.
#include <iostream>
using namespace std;
class Node
{
public:
int data;
Node *link;
};
class Stack
{
public:
Node *top = NULL;
void pop ( )
{
if ( top == NULL )
cout<<"Stack is Empty";
else
{
cout<<"The deleted element ..."<<top->data<<endl;
top = top -> link;
}
void peek()
{
if ( top == NULL )
cout<<"Stack is Empty"<<endl;
else
cout<<"Element at top ... "<< top->data<<endl;
}
void display()
{
if (top == NULL)
cout<<"Stack is Empty"<<endl;
else
{
Node *temp=top;
while(temp!=NULL)
{
cout<<temp->data<<"-->";
temp=temp->link;
}
cout<<endl;
}
}
};
int main()
{
Stack s;
int option, value;
cout<<"1. Push"<<endl;
cout<<"2. Pop"<<endl;
cout<<"3. Peek"<<endl;
cout<<"4. Display"<<endl;
cout<<"5. Exit"<<endl;
do
{
cout<<"Enter an option ...";
cin>>option;
switch (option)
{
case 1: cout<<"Enter Value ...";
cin>>value;
[Link](value);
break;
case 2: cout<<endl;
[Link]();
break;
case 3: cout<<endl;
[Link]();
break;
case 5: exit(0);
break;
default:
cout << "Enter Correct Option number " << endl;
}
}while(option != 0);
return 0;
}
Algorithm
Initialize an empty stack to hold operators.
Scan the infix expression from left to right.
For each symbol in the infix expression:
If it is an operand, output it directly.
If it is an operator:
While the stack is not empty and the precedence of the top operator is greater than
or equal to the current operator, pop the top operator from the stack and output it.
Push the current operator onto the stack.
If it is an open parenthesis '(', push it onto the stack.
If it is a close parenthesis ')':
Pop and output operators from the stack until an open parenthesis '(' is
encountered. Pop and discard the '('.
After scanning the entire expression, pop and output any remaining operators from
the stack.
Example:
Let's consider the infix expression: A + B * C - D / E
Explanation
The stack helps maintain the order of operations by storing operators.
Operators with higher precedence are popped before pushing a new operator onto the
stack.
Parentheses control the order of evaluation; they are pushed onto the stack and popped
when a matching close parenthesis is encountered.
This algorithm ensures that the postfix expression, when evaluated, produces the same
result as the original infix expression. In postfix notation, there is no need for parentheses,
making the expression unambiguous and easier to evaluate.
#include<iostream>
#include<stack>
#include<string>
using namespace std;
class ITFConversion
{
public:
int prec(char c)
{
if(c == '+' || c == '-')
{
return 1;
}
if(c == '*' || c == '/')
{
return 2;
}
}
bool isOperator(char c)
{
if(c == '+'|| c =='-'|| c =='*' || c =='/')
{
return true;
}
else
{
return false;
}
}
string infixToPostfix(string s)
{
stack<char> st;
string res;
for(int i=0;i<[Link]();i++)
{
if(!isOperator(s[i]))
{
res = res+s[i];
continue;
}
if([Link]())
{
[Link](s[i]);
}
else
{
while(![Link]() && prec([Link]())>=prec(s[i]))
{
res = res + [Link]();
[Link]();
}
[Link](s[i]);
}
}
while(![Link]())
{
res = res + [Link]();
[Link]();
}
return res;
}
};
int main()
{
ITFConversion itf;
string exp ;
cout<<"Enter expression ...";
cin>>exp;
cout<<"Postfix form ..."<<[Link](exp)<<endl;
return 0;
}
#include <iostream>
#include<stack>
using namespace std;
class BracesBalance
{
public:
else if ([Link]()== '(' && c == ')' || [Link]() == '{' && c == '}'|| [Link]() == '[' && c == ']')
//else if ([Link]() == ')' && c == '(' || [Link]() == '}' && c == '{'|| [Link]() == ']' && c == '[]')
[Link]();
else
[Link](c);
}
if ([Link]())
return true;
else
return false;
}
};
int main()
{
BracesBalance bb;
string parenthesis;
cout<<"Enter set of braces ...";
cin>>parenthesis;
if ([Link](parenthesis))
cout << "Braces are balanced";
else
cout << "Braces are NOT balanced";
return 0;
}
#include<iostream>
#include<stack>
using namespace std;
bool isPalindrome(string s)
{
int length = [Link]();
stack<char> st;
int i, mid = length / 2;
for (i = 0; i < mid; i++)
{
[Link](s[i]);
}
if (length % 2 != 0)
{
i++;
}
char ele;
if (ele != s[i])
return false;
else
{
i++;
return true;
}
}
int main()
{
string s;
cout << "Enter the string ...";
cin>>s;
if (isPalindrome(s))
cout << "String is palindrome";
else
cout << "String is NOT palindrome";
return 0;
}
It is named as queue as it behaves like real world queue. Example people waiting in a line
to get a ticket.
It is a simple data structure that allows to enter elements in the rear end also called
enqueue operation and elements from the front end also called dequeue operation.
Standard Operations
Enqueue: Elements are added into the queue from rear end.
Dequeue
Removes the element from the front of the queue.
The front pointer is incremented to the next available position.
isEmpty
Checks if the queue is empty by comparing the front and rear pointers.
isFull
Checks if the queue is full, especially in a fixed-size array.
Peek
Retrieves the element at the front of the queue without removing it.
Algorithm - Initialization:
Create an array with a fixed size to hold the elements.
Initialize front and rear pointers to -1 to indicate an empty queue.
Enqueue Operation
Check if the queue is full.
If not, increment the rear pointer and add the element at the new rear position.
Dequeue Operation:
Check if the next position of rear is equal to front, indicating a full queue.
Peek Operation
Return the element at the front position without removing it.
class Queue
{
private:
int myqueue[MAX], front, rear;
public:
Queue()
{
front = -1;
rear = -1;
}
bool isFull()
{
if(front == 0 && rear == MAX - 1)
return true;
else
return false;
}
bool isEmpty()
{
if(front == -1)
return true;
else
return false;
}
int deQueue()
{
int value;
if(isEmpty())
{
cout << "Queue is empty!!" << endl;
return(-1);
}
else
{
value = myqueue[front];
if(front >= rear)
{
//only one element in queue
front = -1;
rear = -1;
}
else
{
front++;
}
cout << endl << "Deleted => " << value << " from myqueue";
return(value);
}
}
void displayQueue()
{
int i;
if(isEmpty())
{
cout << endl << "Queue is Empty!!" << endl;
}
else
{
cout << endl << "Front = " << front;
cout << endl << "Queue elements : ";
for(i = front; i <= rear; i++)
cout << myqueue[i] << "\t";
}
}
};
int main()
{
Queue myq;
case 2: [Link]();
break;
case 3: [Link]();
break;
case 4: exit(0);
}
}
return 0;
}
OUTPUT
Key Operations
Enqueue
Adds an element to the rear of the queue.
The new element is added as the last node in the linked list.
isEmpty
Checks if the queue is empty by examining if the linked list is empty.
Peek
Retrieves the element at the front of the queue without removing it.
Algorithm - Initialization:
Create an empty linked list to represent the queue.
Maintain references to the front and rear nodes.
Enqueue Operation
Create a new node with the given data.
If the queue is empty, set both the front and rear references to the new node.
Otherwise, update the rear node's next reference to the new node and update the rear
reference to the new node.
Dequeue Operation
If the queue is empty, raise an exception or return an indication of an empty queue.
Retrieve the data from the front node.
If the front node is also the rear node (single node in the queue), set both front and rear
references to None.
Otherwise, update the front reference to the next node.
isEmpty Operation
Return true if both front and rear references are None, indicating an empty queue;
otherwise, return false.
Peek Operation:
If the queue is empty, raise an exception or return an indication of an empty queue.
Return the data from the front node without removing it.
#include <iostream>
using namespace std;
class Node
{
public:
int data;
Node *next;
Node(int value)
{
data = value;
next = NULL;
}
};
class Queue
{
private:
Node* front;
Node* rear;
public:
Queue()
{
front = NULL;
rear = NULL;
}
void enqueue(int item)
{
Node* newNode = new Node(item);
if (front == NULL)
{
front = rear = newNode;
}
else
{
rear->next = newNode;
rear = newNode;
}
void dequeue()
{
if (front == NULL)
{
cout <<"Queue is empty"<<endl;
return;
}
if (front == NULL)
{
rear = NULL;
}
}
void display()
{
if (front == NULL)
{
cout << "Queue is empty"<<endl;
return;
}
int main()
{
Queue queue;
switch (option)
{
case 1:
cout << "Enter an element ...";
cin >> element;
[Link](element);
break;
case 2:
[Link]();
break;
case 3:
[Link]();
break;
case 4:
exit(0);
default:
cout << "Enter correct option"<<endl;
}
} while (option != 4);
return 0;
}
Circular Queue
class CircularQueue
{
private:
int front;
int rear;
int arr[5];
int itemCount;
public:
CircularQueue()
{
itemCount = 0;
front = -1;
rear = -1;
for (int i = 0; i < 5; i++)
{
arr[i] = 0;
}
}
bool isEmpty()
{
if (front == -1 && rear == -1)
return true;
else
return false;
}
bool isFull()
{
if ((rear + 1) % 5 == front)
return true;
else
return false;
}
} else
{
rear = (rear + 1) % 5;
arr[rear] = val;
}
itemCount++;
int dequeue()
{
int x = 0;
if (isEmpty())
{
cout << "\nQueue is Empty" << endl;
return x;
}
else if (rear == front)
{
x = arr[rear];
rear = -1;
front = -1;
itemCount--;
return x;
}
else
{
cout << "\nFront value: " << front << endl;
x = arr[front];
arr[front] = 0;
front = (front + 1) % 5;
itemCount--;
return x;
}
}
int count()
{
return (itemCount);
}
void display()
{
cout << "\nElements in the Queue are ..." << endl;
for (int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
}
};
int main()
{
CircularQueue cq;
int value, option;
do
{
cout << "\n\nSelect Option number to continue or Enter 0 to exit." << endl;
switch (option)
{
case 0:
break;
case 1:
cout << "\nEnter an Element to Enqueue in the Queue" << endl;
cin >> value;
[Link](value);
break;
case 2:
cout << "\nDequeued Value : " << [Link]() << endl;
break;
case 3:
if ([Link]())
cout << "\nQueue is Empty" << endl;
else
cout << "\nQueue is not Empty" << endl;
break;
case 4:
if ([Link]())
cout << "\nQueue is Full" << endl;
else
cout << "Queue is not Full" << endl;
break;
case 5:
cout << "\nNumber of elements in Queue : " << [Link]() << endl;
break;
case 6:
[Link]();
break;
case 7:
system("cls");
break;
default:
cout << "Enter Correct Option number " << endl;
}
return 0;
}
OUTPUT
1
Enter an Element to Enqueue in the Queue 22
Telecommunications
Call Centres: customers are served based on a first-come, first-served basis. Calls are
placed in a queue, and agents attend to them in the order they are received.
Data Packet Queues: In computer networks, data packets are often queued for
transmission. Queuing theory helps optimize the performance of network protocols.
Transportation:
Traffic Flow: Queuing theory is used to model and analyse traffic flow at intersections
and on highways. It helps in optimizing traffic signal timings and managing congestion.
Airport Security: Passengers waiting in security lines at airports are essentially forming
a queue. Understanding queue dynamics helps optimize security processes and reduce
wait times.
Print Job Queues: Printers often have queues to manage multiple print jobs. Queuing
theory helps optimize the order in which print jobs are processed.
Finance:
Vaccine Distribution: During mass vaccination campaigns, queuing theory can be used
to optimize the distribution of vaccines and manage the flow of people.
Retail:
Checkout Lines: Queuing theory is applied to optimize the number of checkout counters
open at a retail store, minimizing wait times for customers.
Supply Chain: Queuing theory is used in supply chain management to optimize the flow
of goods through various stages of production and distribution.
Entertainment:
Amusement Parks: Queuing theory is applied to optimize ride queues, ensuring that
visitors have an enjoyable experience with minimal waiting times.
Online Streaming: In video streaming services, queuing theory is used to manage the
distribution of content efficiently, reducing buffering times.
Asynchronous Communication
Definition: Asynchronous communication refers to a communication model where
messages can be sent and received independently of each other. In networking, this is
crucial for systems with varying processing speeds or delays.
Application in Message Queues: Message queues allow components to send and receive
messages asynchronously. Producers can enqueue messages without waiting for
consumers to process them immediately.
Load Balancing
Definition: Load balancing involves distributing workloads across multiple servers to
ensure optimal resource utilization and prevent overload on any single server.
Application in Message Queues: Message queues can be used to implement load
balancing. Work units are enqueued and distributed among available workers, ensuring
a more balanced distribution of tasks.
Fault Tolerance
Definition: Fault tolerance is the ability of a system to continue operating in the presence
of faults or failures.
Application in Message Queues: Message queues contribute to fault tolerance by providing
a buffer for messages. If a component fails temporarily, the messages remain in the queue,
and the system can recover gracefully when the component is restored.
Scalability
Definition: Scalability is the ability of a system to handle an increasing amount of workload
or to be easily expanded.
Application in Message Queues: Message queues support scalability by allowing for the
distribution of tasks among multiple components. As the load increases, additional
consumers can be added to process messages concurrently.
Protocol Flexibility
Definition: Protocol flexibility refers to the ability of systems using different
communication protocols to interact seamlessly.
Application in Message Queues: Message queues provide a middleware layer that abstracts
the communication between components. This abstraction allows systems using different
protocols or technologies to exchange messages without direct coupling.
Message Persistence
Definition: Message persistence refers to the ability of a system to store messages even in
the face of system failures or restarts.
Application in Message Queues: Message queues often support message persistence,
ensuring that important messages are not lost in the event of system failures. Persisted
messages can be retrieved when the system is back online.
Security
Definition: Security is a critical aspect of networked systems, ensuring that
communication remains confidential, authentic, and protected against unauthorized
access.
Application in Message Queues: Message queues often incorporate security features such
as encryption, authentication, and access controls to safeguard the communication
between components.
Real-Time Processing
Definition: Real-time processing involves handling and responding to events or data as
they occur in near real-time.
Application in Message Queues: Message queues can facilitate real-time processing by
enabling components to react to events as soon as they are enqueued.
==00oo00==