A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. This means that the last element added to the stack is the first one to be removed. A stack is like a pile of books.
So, the last book you added is the first one you take. This is the Last-In-First-Out (LIFO) principle.
We can implement a stack using an array or a linked list. Here's a simple implementation using an array in C++:
// Stack implementation using an array
#include <iostream>
#include <vector> // For dynamic array resizing (optional, but good practice)
class StackArray {
private:
int top;
std::vector<int> arr; // Dynamic array (more flexible)
int maxSize; // Maximum size of the stack
public:
StackArray(int size = 100) : top(-1), maxSize(size) {}
bool isEmpty() const {
return top == -1;
}
bool isFull() const {
return top == maxSize - 1;
}
void push(int value) {
if (isFull()) {
std::cerr << "Stack Overflow!" << std::endl;
return;
}
arr.push_back(value);
top++;
std::cout << value << " pushed onto the stack" << std::endl;
}
int pop() {
if (isEmpty()) {
std::cerr << "Stack Underflow!" << std::endl;
return -1;
}
int poppedValue = arr[top];
arr.pop_back();
top--;
return poppedValue;
}
int peek() const {
if (isEmpty()) {
std::cerr << "Stack is Empty!" << std::endl;
return -1;
}
return arr[top];
}
int size() const {
return top + 1;
}
};
// Stack implementation using a linked list
class Node {
public:
int data;
Node* next;
Node(int value) : data(value), next(nullptr) {}
};
class StackLinkedList {
private:
Node* head;
public:
StackLinkedList() : head(nullptr) {}
bool isEmpty() const {
return head == nullptr;
}
void push(int value) {
Node* newNode = new Node(value);
newNode->next = head;
head = newNode;
std::cout << value << " pushed onto the stack" << std::endl;
}
int pop() {
if (isEmpty()) {
std::cerr << "Stack Underflow!" << std::endl;
return -1;
}
int poppedValue = head->data;
Node* temp = head;
head = head->next;
delete temp;
return poppedValue;
}
int peek() const {
if (isEmpty()) {
std::cerr << "Stack is Empty!" << std::endl;
return -1;
}
return head->data;
}
int size() const {
int count = 0;
Node* current = head;
while(current != nullptr) {
count++;
current = current->next;
}
return count;
}
};
int main() {
StackArray stackArr;
stackArr.push(10);
stackArr.push(20);
std::cout << "Top element: " << stackArr.peek() << std::endl;
std::cout << "Popped element: " << stackArr.pop() << std::endl;
std::cout << "Stack size: " << stackArr.size() << std::endl;
StackLinkedList stackList;
stackList.push(100);
stackList.push(200);
std::cout << "Top element: " << stackList.peek() << std::endl;
std::cout << "Popped element: " << stackList.pop() << std::endl;
std::cout << "Stack size: " << stackList.size() << std::endl;
return 0;
}
std::vector
instead of a fixed-size array. This is much more flexible. You don't need to predefine a maximum size, and the vector will grow as needed. I've left the fixed-size array version commented out in case you want to see the difference.top
and (optionally) resize the vector.std::cerr
for error messages (better than std::cout
for errors).push
and pop
operations are now more concise and use the vector's methods directly.size()
method to get the current number of elements in the stack.Node
class to represent elements in the linked list.head
to point to the top of the stack.std::cerr
for error messages.size()
method to get the current number of elements in the stack.A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. This means that the first element added to the queue is the first one to be removed.
Feature | Stack | Queue |
---|---|---|
Data Structure | Linear | Linear |
Access Order | LIFO (Last-In-First-Out) | FIFO (First-In-First-Out) |
Operations | Push, Pop, Peek | Enqueue, Dequeue, Peek |
Real-world Analogy | Stack of plates | Queue of people |
Common Use Cases | Function calls, undo/redo, expression evaluation | Print queues, task scheduling, breadth-first search |
Data Structure | Array, Linked List | Array, Linked List |
Implementation Time Complexity for Basic Operations | O(1) | O(1) |
#include <iostream>
#include <queue> // For using the standard queue (or you can implement your own)
#include <vector> // If you want to use a vector for a fixed-size queue
// Example using std::queue (dynamic size)
void exampleStdQueue() {
std::queue<int> q;
// Enqueue
q.push(10);
q.push(20);
q.push(30);
// Peek
std::cout << "Front element: " << q.front() << std::endl; // Output: 10
// Dequeue
std::cout << "Dequeued element: " << q.front() << std::endl;
q.pop();
// IsEmpty
if (q.empty()) {
std::cout << "Queue is empty" << std::endl;
} else {
std::cout << "Queue is not empty" << std::endl; // Output: Queue is not empty
}
// Size
std::cout << "Queue size: " << q.size() << std::endl; // Output: 2
// Dequeue remaining elements
while (!q.empty()) {
std::cout << "Dequeued element: " << q.front() << std::endl;
q.pop();
}
if (q.empty()) {
std::cout << "Queue is empty" << std::endl; // Output: Queue is empty
}
}
// Example of a fixed-size queue using a vector (more complex, less common)
void exampleFixedSizeQueue() {
const int maxSize = 5;
std::vector<int> q(maxSize); // Use a vector as the underlying storage
int front = 0;
int rear = -1;
int size = 0;
// IsFull
auto isFull = [&]() { return size == maxSize; };
// IsEmpty
auto isEmpty = [&]() { return size == 0; };
// Enqueue
auto enqueue = [&](int value) {
if (isFull()) {
std::cerr << "Queue is full!" << std::endl;
return;
}
rear = (rear + 1) % maxSize; // Wrap around if necessary
q[rear] = value;
size++;
std::cout << value << " enqueued" << std::endl;
};
// Dequeue
auto dequeue = [&]() {
if (isEmpty()) {
std::cerr << "Queue is empty!" << std::endl;
return -1; // Or throw an exception
}
int value = q[front];
front = (front + 1) % maxSize;
size--;
return value;
};
// Peek
auto peek = [&]() {
if (isEmpty()) {
std::cerr << "Queue is empty!" << std::endl;
return -1;
}
return q[front];
};
enqueue(10);
enqueue(20);
enqueue(30);
std::cout << "Front element: " << peek() << std::endl;
std::cout << "Dequeued element: " << dequeue() << std::endl;
std::cout << "Is queue full? " << isFull() << std::endl;
std::cout << "Is queue empty? " << isEmpty() << std::endl;
std::cout << "Dequeued element: " << dequeue() << std::endl;
std::cout << "Is queue full? " << isFull() << std::endl;
std::cout << "Is queue empty? " << isEmpty() << std::endl;
}
int main() {
std::cout << "Example using std::queue:" << std::endl;
exampleStdQueue();
std::cout << "\nExample using fixed-size queue:" << std::endl;
exampleFixedSizeQueue();
return 0;
}
The enqueue operation adds an element to the rear end of the queue. Imagine a queue of people waiting for a service. When a new person arrives, they join the queue at the end. Similarly, in a queue data structure, a new element is added to the rear.
The dequeue operation removes an element from the front end of the queue. Continuing the analogy of a queue of people, the person at the front of the queue is the first one to be served and leaves the queue. In a queue data structure, the element at the front is removed.
A circular queue is a linear data structure that operates like a queue but with a circular array implementation. In a circular queue, the last position is connected to the first position, forming a circular structure. This allows for efficient use of space and avoids the need for frequent memory reallocations.
A priority queue is a queue where each element has a priority associated with it. Elements are dequeued in order of their priority, with the highest-priority element being dequeued first. It's often implemented using a heap data structure.
Application | Description |
---|---|
BFS | Explores graph nodes level by level. |
Print Queue | Manages print jobs in a printer. |
Task Scheduling | Schedules tasks in operating systems. |
Simulation | Simulates real-world systems. |
Keyboard Buffer | Stores keystrokes temporarily. |
Call Center Queues | Manages incoming calls. |
Producer-Consumer Problem | Coordinates between processes or threads. |
Percentage: 0%
Answered Questions: 0
Correct Answers: 0