Stacks and Queues
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Introduction to Stacks
The concept of a stack is similar to a stack of books or plates, where the last item placed on top is the first item to be removed. In computer science, a stack is a data structure that follows a similar last-in, first-out (LIFO) order.
Imagine you have a pile of plates, and you want to add more plates to it. You would place each new plate on top of the pile. When you want to remove a plate, you would start from the top of the pile and remove the topmost plate first.
#include <iostream>
using namespace std;
int main() {
// Creating a Stack
int stack[5];
int top = -1;
// Pushing elements into the stack
top++;
stack[top] = 1;
top++;
stack[top] = 2;
top++;
stack[top] = 3;
// Popping elements from the stack
int popped = stack[top];
top--;
// Accessing the top element of the stack
int topElement = stack[top];
// Printing the stack
cout << "Stack: ";
for (int i = 0; i <= top; i++) {
cout << stack[i] << " ";
}
cout << endl;
// Printing the popped and top element
cout << "Popped: " << popped << endl;
cout << "Top Element: " << topElement << endl;
return 0;
}
```c++
#include <iostream>
using namespace std;
int main() {
// Creating a Stack
int stack[5];
int top = -1;
// Pushing elements into the stack
top++;
stack[top] = 1;
top++;
stack[top] = 2;
top++;
stack[top] = 3;
// Popping elements from the stack
int popped = stack[top];
top--;
// Accessing the top element of the stack
int topElement = stack[top];
// Printing the stack
cout << "Stack: ";
for (int i = 0; i <= top; i++) {
cout << stack[i] << " ";
}
cout << endl;
// Printing the popped and top element
cout << "Popped: " << popped << endl;
cout << "Top Element: " << topElement << endl;
return 0;
}
```
True or False
The concept of a stack follows a first-in, first-out (FIFO) order.
Implementing a Stack
In this lesson, we will learn about implementing a stack data structure in C++. A stack is a data structure that follows the last-in, first-out (LIFO) order. This means that the last element added to the stack is the first one to be removed.
To implement a stack, we will use an array and a variable to keep track of the top of the stack. We will define a class called Stack which will have the following private members:
-
top: an integer variable to store the index of the top element -
stack: an array to store the elements of the stack
We will also define the following public member functions:
-
isEmpty(): a function to check if the stack is empty -
isFull(): a function to check if the stack is full -
push(element): a function to push an element onto the stack -
pop(): a function to pop an element from the stack -
peek(): a function to get the top element of the stack
To push an element onto the stack, we will first check if the stack is full. If it is, we will output 'Stack Overflow' and return. Otherwise, we will increment the top variable and assign the element to stack[top].
To pop an element from the stack, we will first check if the stack is empty. If it is, we will output 'Stack Underflow' and return -1. Otherwise, we will decrement the top variable and return the element at stack[top].
Here is an example code implementation of a stack in C++:
#include <iostream>
using namespace std;
// Define the maximum size of the stack
#define MAX_SIZE 100
// Define the class for the stack data structure
class Stack {
private:
int top;
int stack[MAX_SIZE];
public:
// Constructor
Stack() {
top = -1;
}
// Check if stack is empty
bool isEmpty() {
return (top == -1);
}
// Check if stack is full
bool isFull() {
return (top == MAX_SIZE - 1);
}
// Push element onto the stack
void push(int element) {
if (isFull()) {
cout << "Stack Overflow" << endl;
return;
}
stack[++top] = element;
cout << "Pushed " << element << " onto the stack" << endl;
}
// Pop element from the stack
int pop() {
if (isEmpty()) {
cout << "Stack Underflow" << endl;
return -1;
}
return stack[top--];
}
// Get the top element of the stack
int peek() {
return stack[top];
}
};
int main() {
// Create a stack object
Stack stack;
// Push elements onto the stack
stack.push(1);
stack.push(2);
stack.push(3);
// Pop elements from the stack
int popped = stack.pop();
// Get the top element of the stack
int topElement = stack.peek();
// Print the stack
cout << "Stack: ";
while (!stack.isEmpty()) {
cout << stack.pop() << " ";
}
cout << endl;
// Print the popped and top element
cout << "Popped: " << popped << endl;
cout << "Top Element: " << topElement << endl;
return 0;
}
```c++
#include <iostream>
using namespace std;
// Define the maximum size of the stack
#define MAX_SIZE 100
// Define the class for the stack data structure
class Stack {
private:
int top;
int stack[MAX_SIZE];
public:
// Constructor
Stack() {
top = -1;
}
// Check if stack is empty
bool isEmpty() {
return (top == -1);
}
// Check if stack is full
bool isFull() {
return (top == MAX_SIZE - 1);
}
// Push element onto the stack
void push(int element) {
if (isFull()) {
cout << "Stack Overflow" << endl;
return;
}
stack[++top] = element;
cout << "Pushed " << element << " onto the stack" << endl;
}
// Pop element from the stack
int pop() {
if (isEmpty()) {
cout << "Stack Underflow" << endl;
return -1;
}
return stack[top--];
}
// Get the top element of the stack
int peek() {
return stack[top];
}
};
int main() {
// Create a stack object
Stack stack;
// Push elements onto the stack
stack.push(1);
stack.push(2);
stack.push(3);
// Pop elements from the stack
int popped = stack.pop();
// Get the top element of the stack
int topElement = stack.peek();
// Print the stack
cout << "Stack: ";
while (!stack.isEmpty()) {
cout << stack.pop() << " ";
}
cout << endl;
// Print the popped and top element
cout << "Popped: " << popped << endl;
cout << "Top Element: " << topElement << endl;
return 0;
}
```
Implementing a Stack Fill In
To push an element onto the stack, we will first check if the stack is _____. If it is, we will output 'Stack Overflow' and return. Otherwise, we will increment the top variable and assign the element to stack[top].
Infix to Postfix Conversion
Infix to Postfix Conversion
In computer science, infix notation is a method of writing arithmetic expressions in which the operands are written before their operators. For example, the infix expression 2 + 3 * 4 means (2 + (3 * 4)).
On the other hand, postfix notation (also known as Reverse Polish Notation or RPN) is a method of writing arithmetic expressions in which each operator follows all of its operands.
Converting an infix expression to a postfix expression can be done using a stack.
Here is an example code implementation in C++: ```cpp
include
include
using namespace std;
bool isOperator(char c) { if (c == '+' || c == '-' || c == '*' || c == '/') { return true; } return false; }
int getPrecedence(char op) { if (op == '+' || op == '-') { return 1; } else if (op == '*' || op == '/') { return 2; } return 0; }
string infixToPostfix(string infix) { stack s; string postfix; for (char c : infix) { if (isalnum(c)) { postfix += c; } else if (c == '(') { s.push(c); } else if (c == ')') { while (!s.empty() && s.top() != '(') { postfix += s.top(); s.pop(); } s.pop(); } else if (isOperator(c)) { while (!s.empty() && getPrecedence(c) <= getPrecedence(s.top())) { postfix += s.top(); s.pop(); } s.push(c); } } while (!s.empty()) { if (s.top() != '(') { postfix += s.top(); } s.pop(); } return postfix; }
int main() { string infix; cout << "Enter an infix expression: "; cin >> infix; string postfix = infixToPostfix(infix); cout << "Postfix expression: " << postfix << endl; return 0; } ```
```c++
#include <iostream>
#include <stack>
using namespace std;
// Function to check if a character is an operator
bool isOperator(char c) {
if (c == '+' || c == '-' || c == '*' || c == '/') {
return true;
}
return false;
}
// Function to get the precedence of an operator
int getPrecedence(char op) {
if (op == '+' || op == '-') {
return 1;
}
else if (op == '*' || op == '/') {
return 2;
}
return 0;
}
// Function to convert an infix expression to postfix
string infixToPostfix(string infix) {
stack<char> s;
string postfix;
for (char c : infix) {
// If character is an operand, add it to the postfix string
if (isalnum(c)) {
postfix += c;
}
// If character is an opening parenthesis, push it to the stack
else if (c == '(') {
s.push(c);
}
// If character is a closing parenthesis, pop operators from the stack until an opening parenthesis is encountered
else if (c == ')') {
while (!s.empty() && s.top() != '(') {
postfix += s.top();
s.pop();
}
s.pop();
}
// If character is an operator, pop operators from the stack until an operator with lower precedence is encountered
else if (isOperator(c)) {
while (!s.empty() && getPrecedence(c) <= getPrecedence(s.top())) {
postfix += s.top();
s.pop();
}
s.push(c);
}
}
// Pop any remaining operators from the stack
while (!s.empty()) {
if (s.top() != '(') {
postfix += s.top();
}
s.pop();
}
return postfix;
}
int main() {
// Input the infix expression
string infix;
cout << "Enter an infix expression: ";
cin >> infix;
// Convert the infix expression to postfix
string postfix = infixToPostfix(infix);
// Print the postfix expression
cout << "Postfix expression: " << postfix << endl;
return 0;
}
```
Infix to Postfix Conversion Fill In
To convert an infix expression to a postfix expression, we use a stack. We iterate through each character in the infix expression and perform the following steps:
- If the character is an operand, we add it to the postfix expression.
- If the character is an opening parenthesis, we push it onto the stack.
- If the character is a closing parenthesis, we pop all the operators from the stack and add them to the postfix expression until we encounter an opening parenthesis.
- If the character is an operator, we pop operators from the stack and add them to the postfix expression until we encounter an operator with lower precedence or an opening parenthesis.
The resulting postfix expression is the converted form of the infix expression.
In the C++ code implementation given in the previous screen, the infixToPostfix function performs the conversion from infix to postfix. It uses a stack to store operators and follows the above steps to create the postfix expression. The main function takes an infix expression as input and displays the corresponding postfix expression as output.
Now, it's your turn to fill in the blank: To convert an infix expression to a postfix expression, we use a _________.
Evaluation of Postfix Expressions
Evaluation of Postfix Expressions
In the previous screen, we learned about converting infix expressions to postfix notation. Now, let's move on to evaluating postfix expressions using stacks.
In postfix notation, the operands appear before their operators. For example, the postfix expression 23+4* is equivalent to the infix expression (2 + 3) * 4.
To evaluate a postfix expression, we can use a stack to keep track of the operands. When we encounter an operand, we push it onto the stack. When we encounter an operator, we pop the top two operands from the stack, perform the operation, and push the result back onto the stack.
Here is an example code implementation in C++: ```cpp
include
include
using namespace std;
int evaluatePostfix(string postfix) { stack s; for (char c : postfix) { if (isdigit(c)) { s.push(c - '0'); } else { int operand2 = s.top(); s.pop(); int operand1 = s.top(); s.pop(); int result; switch (c) { case '+': result = operand1 + operand2; break; case '-': result = operand1 - operand2; break; case '*': result = operand1 * operand2; break; case '/': result = operand1 / operand2; break; } s.push(result); } } return s.top(); }
int main() { string postfix; cout << "Enter a postfix expression: "; cin >> postfix; int result = evaluatePostfix(postfix); cout << "Result: " << result << endl; return 0; } ```
In this code, we use a stack to store the operands. We iterate over each character in the postfix expression. If the character is a digit, we convert it to an integer and push it onto the stack. If the character is an operator, we pop the top two operands from the stack, perform the operation, and push the result back onto the stack. Finally, we return the top of the stack, which contains the result of the expression.
Now that we have learned about evaluating postfix expressions, let's move on to exploring the applications of stacks and queues in real-world scenarios.
```c++
#include <iostream>
#include <stack>
using namespace std;
int evaluatePostfix(string postfix) {
stack<int> s;
for (char c : postfix) {
if (isdigit(c)) {
s.push(c - '0');
}
else {
int operand2 = s.top();
s.pop();
int operand1 = s.top();
s.pop();
int result;
switch (c) {
case '+': result = operand1 + operand2; break;
case '-': result = operand1 - operand2; break;
case '*': result = operand1 * operand2; break;
case '/': result = operand1 / operand2; break;
}
s.push(result);
}
}
return s.top();
}
int main() {
string postfix;
cout << "Enter a postfix expression: ";
cin >> postfix;
int result = evaluatePostfix(postfix);
cout << "Result: " << result << endl;
return 0;
}
```
Evaluation of Postfix Expressions Multiple Choice
What data structure can be used to evaluate postfix expressions?
Applications of Stacks
Applications of Stacks
Stacks have a wide range of applications in various domains, including robotics and computer vision. Let's explore some real-world applications where stacks play a crucial role.
1. Robot Navigation
In the field of robotics, stacks are often used for robot navigation algorithms. When a robot moves through a maze or an unknown environment, it needs to remember the path it has taken. Stacks provide an efficient way to store the previous locations of the robot. The robot can easily backtrack by popping the stack and returning to the previous location.
Here's an analogy: Imagine a robot navigating a maze to find a target location. It puts a marker at each visited location and uses a stack to store the path it has taken. If it reaches a dead end, it pops the stack to go back to the previous location and explores another path.
#include <iostream>
#include <stack>
using namespace std;
void navigateMaze() {
stack<int> path;
// Code for robot navigation
}
int main() {
navigateMaze();
return 0;
}
```c++
#include <iostream>
using namespace std;
int main() {
// Replace with your C++ code here
return 0;
}
```
Applications of Stacks Fill In
Stacks are commonly used in browser history, where the _______________ is stored in a stack. This allows users to navigate back and forth between previously visited webpages.
Introduction to Queues
Introduction to Queues
Queues are another fundamental data structure that follows the FIFO (First-In-First-Out) principle. In a queue, the element that is added first is the first one to be removed. Just like waiting in a line, where the first person to join the line is the first one to be served.
Queues have a wide range of applications in computer science and real-world scenarios. Let's explore some of the common use cases of queues:
- Job Scheduling: In operating systems, queues are used to schedule different tasks or jobs. Each process is added to a queue and then executed based on its priority or arrival time.
- Breadth-First Search (BFS): Queues are used in graph algorithms like BFS to traverse the nodes level by level. The nodes are explored in the order they were added to the queue.
- Printers: Printers often use queues to manage print requests. Each print request is added to the queue and processed in the order of arrival.
Let's take a look at a simple C++ implementation of a queue:
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
// Enqueue elements
q.push(10);
q.push(20);
q.push(30);
// Print front element
cout << "Front element: " << q.front() << endl;
// Dequeue elements
q.pop();
q.pop();
// Print front element
cout << "Front element: " << q.front() << endl;
return 0;
}
In the code above, we include the <queue> header file to use the queue data structure in C++. We create a queue object q and perform operations like push to enqueue elements and pop to dequeue elements. The front function is used to retrieve the front element of the queue.
Queues are essential in many algorithms and provide an efficient way to manage and process data. In the upcoming lessons, we will dive deeper into queue operations and different implementations.
```c++
#include <iostream>
using namespace std;
int main() {
// Queue implementation goes here
return 0;
}
```
Introduction to Queues Multiple Choice
Which of the following best describes the characteristics of a queue?
Implementing a Queue
Implementing a Queue
In this lesson, we will explore how to create and implement a queue data structure in C++. We will discuss the concept of a queue and its basic operations.
What is a Queue?
A queue is a linear data structure that follows the FIFO (First-In-First-Out) principle. It is similar to a real-life queue or line, where the first person to join the queue is the first one to be served.
Operations on a Queue
A queue supports two main operations:
- Enqueue: This operation adds an element to the end of the queue.
- Dequeue: This operation removes an element from the front of the queue.
Let's take a look at an example of implementing a queue in C++:
#include <iostream>
#include <queue>
using namespace std;
int main() {
// Create a queue
queue<int> q;
// Enqueue elements
q.push(10);
q.push(20);
q.push(30);
// Print front element
cout << "Front element: " << q.front() << endl;
// Dequeue elements
q.pop();
q.pop();
// Print front element
cout << "Front element: " << q.front() << endl;
return 0;
}
In the code above, we include the <queue> header file to use the queue data structure in C++. We create a queue object q and perform operations like push to enqueue elements and pop to dequeue elements. The front function is used to retrieve the front element of the queue.
By implementing a queue, we can efficiently manage and process data in a first-in-first-out manner. In the upcoming lessons, we will explore more advanced concepts and operations related to queues.
```c++
#include <iostream>
#include <queue>
using namespace std;
int main() {
// Create a queue
queue<int> q;
// Enqueue elements
q.push(10);
q.push(20);
q.push(30);
// Print front element
cout << "Front element: " << q.front() << endl;
// Dequeue elements
q.pop();
q.pop();
// Print front element
cout << "Front element: " << q.front() << endl;
return 0;
}
```
True or False
A queue is a linear data structure that follows the LIFO (Last-In-First-Out) principle.
Circular Queue
Circular Queue
In this lesson, we will explore the concept of a circular queue and how it is implemented. A circular queue is a variation of a regular queue where the last element points to the first element making a circular link. This allows us to reuse the empty spaces in the queue efficiently.
Implementation
To implement a circular queue, we can use an array of fixed size and maintain two pointers: front and rear. The front pointer keeps track of the first element, and the rear pointer keeps track of the last element. Initially, both pointers are set to -1 to indicate an empty queue.
Here is a C++ implementation of a circular queue:
#include <iostream>
using namespace std;
const int SIZE = 5;
class CircularQueue {
private:
int front;
int rear;
int arr[SIZE];
public:
CircularQueue() {
front = -1;
rear = -1;
}
bool isEmpty() {
return front == -1 && rear == -1;
}
bool isFull() {
return (rear + 1) % SIZE == front;
}
void enqueue(int item) {
if (isFull()) {
cout << "Queue is full. Unable to enqueue." << endl;
} else if (isEmpty()) {
front = rear = 0;
arr[rear] = item;
} else {
rear = (rear + 1) % SIZE;
arr[rear] = item;
}
}
void dequeue() {
if (isEmpty()) {
cout << "Queue is empty. Unable to dequeue." << endl;
} else if (front == rear) {
cout << "Dequeued " << arr[front] << endl;
front = rear = -1;
} else {
cout << "Dequeued " << arr[front] << endl;
front = (front + 1) % SIZE;
}
}
};
int main() {
// Create a circular queue object
CircularQueue q;
// Enqueue elements
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.enqueue(40);
q.enqueue(50);
q.enqueue(60); // Should produce an error message
// Dequeue elements
q.dequeue();
q.dequeue();
q.dequeue();
// Enqueue element
q.enqueue(70);
return 0;
}
In the code above, we define a CircularQueue class with functions such as isEmpty, isFull, enqueue, and dequeue. The isEmpty function checks whether both front and rear are -1, indicating an empty queue. The isFull function checks if the next index of rear would be equal to front, indicating a full queue.
We enqueue elements using the enqueue function, and dequeue elements using the dequeue function. If the queue is full and we try to enqueue another element, an error message will be displayed. Similarly, if the queue is empty and we try to dequeue an element, an error message will be displayed as well.
The output of the code above will be:
Enqueuing 10...
Enqueuing 20...
Enqueuing 30...
Enqueuing 40...
Enqueuing 50...
Enqueuing 60...
Queue is full. Unable to enqueue.
Dequeuing...
Dequeuing...
Dequeuing...
Enqueuing 70...
By implementing a circular queue, we can efficiently utilize the circular link and ensure all the elements are utilized in the queue. It provides an optimization over a regular queue in terms of space utilization.
```c++
#include <iostream>
using namespace std;
const int SIZE = 5;
class CircularQueue {
private:
int front;
int rear;
int arr[SIZE];
public:
CircularQueue() {
front = -1;
rear = -1;
}
bool isEmpty() {
return front == -1 && rear == -1;
}
bool isFull() {
return (rear + 1) % SIZE == front;
}
void enqueue(int item) {
if (isFull()) {
cout << "Queue is full. Unable to enqueue." << endl;
} else if (isEmpty()) {
front = rear = 0;
arr[rear] = item;
} else {
rear = (rear + 1) % SIZE;
arr[rear] = item;
}
}
void dequeue() {
if (isEmpty()) {
cout << "Queue is empty. Unable to dequeue." << endl;
} else if (front == rear) {
cout << "Dequeued " << arr[front] << endl;
front = rear = -1;
} else {
cout << "Dequeued " << arr[front] << endl;
front = (front + 1) % SIZE;
}
}
};
int main() {
CircularQueue q;
cout << "Enqueuing 10..." << endl;
q.enqueue(10);
cout << "Enqueuing 20..." << endl;
q.enqueue(20);
cout << "Enqueuing 30..." << endl;
q.enqueue(30);
cout << "Enqueuing 40..." << endl;
q.enqueue(40);
cout << "Enqueuing 50..." << endl;
q.enqueue(50);
cout << "Enqueuing 60..." << endl;
q.enqueue(60); // Should produce an error message
cout << "Dequeuing..." << endl;
q.dequeue();
cout << "Dequeuing..." << endl;
q.dequeue();
cout << "Dequeuing..." << endl;
q.dequeue();
cout << "Enqueuing 70..." << endl;
q.enqueue(70);
return 0;
}
```
Circular Queue Fill In
In a circular queue, the _______________ pointer keeps track of the first element, and the _______________ pointer keeps track of the last element.
Priority Queue
Priority Queue
In this lesson, we will explore the concept of a priority queue and how to implement it using arrays and heaps.
A priority queue is a special type of queue where each element has an associated priority. The element with the highest priority is dequeued first.
Implementation
To implement a priority queue, we can use an array of fixed size. In this implementation, we assume that higher values indicate higher priorities. The enqueue operation inserts an element into the priority queue based on its priority, and the dequeue operation removes the element with the highest priority.
Here is a C++ implementation of a priority queue using arrays:
```c++
#include <iostream>
using namespace std;
const int MAX_SIZE = 100;
class PriorityQueue {
private:
int arr[MAX_SIZE];
int size;
public:
PriorityQueue() {
size = 0;
}
bool isEmpty() {
return size == 0;
}
bool isFull() {
return size == MAX_SIZE;
}
void enqueue(int item) {
if (isFull()) {
cout << "Priority Queue is full. Unable to enqueue." << endl;
} else {
int i;
for (i = size - 1; i >= 0; i--) {
if (item > arr[i]) {
arr[i + 1] = arr[i];
} else {
break;
}
}
arr[i + 1] = item;
size++;
}
}
void dequeue() {
if (isEmpty()) {
cout << "Priority Queue is empty. Unable to dequeue." << endl;
} else {
cout << "Dequeued " << arr[size - 1] << endl;
size--;
}
}
};
int main() {
// Create a priority queue object
PriorityQueue pq;
// Enqueue elements
pq.enqueue(10);
pq.enqueue(30);
pq.enqueue(20);
pq.enqueue(50);
// Dequeue elements
pq.dequeue();
pq.dequeue();
pq.dequeue();
// Enqueue element
pq.enqueue(40);
return 0;
}
```
Priority Queue Fill In
A priority queue is a special type of ________ where each element has an associated priority. The element with the highest priority is dequeued first.
Applications of Queues
Applications of Queues
Queues are used in a wide range of applications across different domains, including robotics and computer vision. The FIFO (First-In-First-Out) nature of queues makes them suitable for scenarios where strict ordering is required.
In the field of robotics, queues are often used to manage tasks or commands that need to be executed in a sequential manner. For example, in a robot navigation system, a queue can be used to store a series of waypoints that the robot needs to visit. The robot will process each waypoint in the order they are enqueued, ensuring a smooth and systematic traversal.
Queues are also extensively used in computer vision applications. In image processing, a common task is to apply various filters and transformations to an image. By using a queue, the images can be processed in the same order they are received, ensuring that the transformations are applied consistently.
Let's take a look at a simple example that demonstrates the usage of queues in a robotics scenario:
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<string> tasks;
tasks.push("Move to waypoint 1");
tasks.push("Perform task 1");
tasks.push("Move to waypoint 2");
tasks.push("Perform task 2");
while (!tasks.empty()) {
string task = tasks.front();
tasks.pop();
cout << "Executing task: " << task << endl;
}
return 0;
}
In this example, we have a queue named tasks that stores a series of tasks to be executed. The robot will move to waypoint 1, perform task 1, move to waypoint 2, and perform task 2 in a sequential manner.
By leveraging the power of queues, we can ensure that the tasks are executed in the desired order, providing a reliable and efficient solution for robotics and computer vision applications.
```c++
#include <iostream>
using namespace std;
int main() {
// Replace with relevant C++ logic here
}
```
Applications of Queues Swipe
Queues are commonly used in situations where strict ordering is required, such as in a robot navigation system.
Generating Complete
Generating complete for this lesson!