Loading...

DESIGN & ANALYSIS OF ALGORITHMS  

>

LEARNING OUTCOME 5

Stack: A Last-In-First-Out (LIFO) Data Structure

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.

Key Operations on a Stack:

  1. Push: Adds an element to the top of the stack.
  2. Pop: Removes the top element from the stack.
  3. Peek: Returns the value of the top element without removing it.
  4. IsEmpty: Checks if the stack is empty.

Implementation of a Stack

We can implement a stack using an array or a linked list. Here's a simple implementation using an array in C++:

Code Example


// 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;
}

StackLinkedList

Stack Operations

Applications of Stacks

QUEUE

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.

Distinction between Stack and Queue

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)

Queue Operations

#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;
	}
	

`exampleStdQueue()` function:

`exampleFixedSizeQueue()` function:

`main()` function:

Key Differences and When to Use Which:

Enqueue and Dequeue Operations

Enqueue

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.

Dequeue

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.

Circular Queue

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.

Priority Queue

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.

Applications of Queues

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.

End of Outcome Quiz

1 of 20

    Quiz Score

    Percentage: 0%

    Answered Questions: 0

    Correct Answers: 0

    Faults: