Data Structures & Algorithms · Section 04

Stack Data Structure: Array & Linked List Implementations

Complete study of Stacks and the LIFO principle: C++ built-in stack demonstration, array-based stack implementation with overflow and underflow tests, and generic linked-list stack implementation.

1. What is a Stack? (LIFO Concept)

A Stack is a linear data structure that operates under the Last In, First Out (LIFO) principle. The last element placed on the stack is the first one to be removed.

Example 1: Stack Behavior Demonstration

cpp
#include <iostream>
#include <stack>
using namespace std;
int main() {
    // Using C++ built-in stack to demonstrate LIFO
    stack<int> s;
    // Push elements
    s.push(10);
    s.push(20);
    s.push(30);
    cout << "Top element: " << s.top() << endl;  // 30
    // Pop elements (Last In, First Out)
    cout << "Popping elements:" << endl;
    while (!s.empty()) {
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;  // 30 20 10
    return 0;
}

Output:

Output
Top element: 30
Popping elements:
30 20 10

LIFO Visualization:

Output
Push 10 →  Push 20 →  Push 30 →  Pop →  Pop →  Pop
                                    ↓      ↓      ↓
[10]      [10]       [10]          [10]   []     []
          [20]       [20]          []     
                     [30]          Pop returns 30, then 20, then 10

2. Stack Implementation Using Array

A fixed-size array can be used to implement a stack where an integer top tracks the index of the top element.

Example 2: Complete Stack Class (Array-Based)

cpp
#include <iostream>
using namespace std;
const int MAX = 5;  // Maximum size of the stack
class Stack {
private:
    int arr[MAX];   // Array to store stack elements
    int top;        // Index of the top element

public:
    // Constructor
    Stack() {
        top = -1;   // Stack is initially empty
    }

    // Check if stack is full
    bool isFull() {
        return (top == MAX - 1);
    }

    // Check if stack is empty
    bool isEmpty() {
        return (top == -1);
    }

    // Push operation
    void push(int value) {
        if (isFull()) {
            cout << "Stack Overflow! Cannot push " << value << endl;
        } else {
            top++;
            arr[top] = value;
            cout << value << " pushed into stack." << endl;
        }
    }

    // Pop operation
    void pop() {
        if (isEmpty()) {
            cout << "Stack Underflow! Cannot pop." << endl;
        } else {
            cout << arr[top] << " popped from stack." << endl;
            top--;
        }
    }

    // Peek operation: get top element without removing
    void peek() {
        if (isEmpty()) {
            cout << "Stack is empty." << endl;
        } else {
            cout << "Top element is: " << arr[top] << endl;
        }
    }
    // Display all elements (top to bottom)
    void display() {
        if (isEmpty()) {
            cout << "Stack is empty." << endl;
        } else {
            cout << "Stack elements (top to bottom): ";
            for (int i = top; i >= 0; i--) {
                cout << arr[i] << " ";
            }
            cout << endl;
        }
    }
};

int main() {
    Stack s;

    cout << "=== Push Operations ===" << endl;
    s.push(10);
    s.push(20);
    s.push(30);
    s.display();  // 30 20 10

    cout << "\n=== Peek ===" << endl;
    s.peek();     // 30

    cout << "\n=== Pop Operations ===" << endl;
    s.pop();      // 30
    s.pop();      // 20
    s.display();  // 10

    cout << "\n=== Stack Overflow Test ===" << endl;
    s.push(40);
    s.push(50);
    s.push(60);
    s.push(70);
    s.push(80);   // Overflow (MAX = 5, but we only have 3 after pops)
    s.push(90);   // Overflow

    cout << "\n=== Stack Underflow Test ===" << endl;
    s.pop(); s.pop(); s.pop(); s.pop(); s.pop();  // Pop all
    s.pop();      // Underflow

    return 0;
}

Output:

Output
=== Push Operations ===
10 pushed into stack.
20 pushed into stack.
30 pushed into stack.
Stack elements (top to bottom): 30 20 10
=== Peek ===
Top element is: 30
=== Pop Operations ===
30 popped from stack.
20 popped from stack.
Stack elements (top to bottom): 10
=== Stack Overflow Test ===
40 pushed into stack.
50 pushed into stack.
60 pushed into stack.
70 pushed into stack.
80 pushed into stack.
Stack Overflow! Cannot push 90
=== Stack Underflow Test ===
80 popped from stack.
70 popped from stack.
60 popped from stack.
50 popped from stack.
40 popped from stack.
10 popped from stack.
Stack Underflow! Cannot pop.

3. Stack Implementation Using Linked List

A linked list provides dynamic sizing, allowing the stack to grow and shrink without compile-time capacity limitations.

Example 3: Complete Stack Class (Linked List-Based)

cpp
#include <iostream>
using namespace std;

// Node structure
template <class T>
class Node {
public:
    T data;
    Node* next;

    Node(T value) {
        data = value;
        next = nullptr;
    }
};

// Stack class using Linked List
template <class T>
class Stack {
private:
    Node<T>* top;   // Pointer to top node

public:
    // Constructor
    Stack() {
        top = nullptr;
    }

    // Destructor (clean up memory)
    ~Stack() {
        while (!isEmpty()) {
            pop();
        }
    }

    // Check if stack is empty
    bool isEmpty() {
        return (top == nullptr);
    }

    // Push operation
    void push(T value) {
        Node<T>* newNode = new Node<T>(value);
        newNode->next = top;   // New node points to old top
        top = newNode;         // Update top
        cout << value << " pushed into stack." << endl;
    }

    // Pop operation
    void pop() {
        if (isEmpty()) {
            cout << "Stack Underflow! Cannot pop." << endl;
        } else {
            cout << top->data << " popped from stack." << endl;
            Node<T>* temp = top;
            top = top->next;
            delete temp;
        }
    }

    // Peek operation
    void peek() {
        if (isEmpty()) {
            cout << "Stack is empty." << endl;
        } else {
            cout << "Top element is: " << top->data << endl;
        }
    }

    // Display all elements
    void display() {
        if (isEmpty()) {
            cout << "Stack is empty." << endl;
        } else {
            cout << "Stack elements (top to bottom): ";
            Node<T>* temp = top;
            while (temp != nullptr) {
                cout << temp->data << " ";
                temp = temp->next;
            }
            cout << endl;
        }
    }
};

int main() {
    Stack<int> s;

    cout << "=== Integer Stack ===" << endl;
    s.push(10);
    s.push(20);
    s.push(30);
    s.display();  // 30 20 10
    s.peek();     // 30
    s.pop();      // 30
    s.display();  // 20 10

    cout << "\n=== String Stack ===" << endl;
    Stack<string> strStack;
    strStack.push("Alice");
    strStack.push("Bob");
    strStack.push("Carol");
    strStack.display();  // Carol Bob Alice

    return 0;
}

Output:

Output
=== Integer Stack ===
10 pushed into stack.
20 pushed into stack.
30 pushed into stack.
Stack elements (top to bottom): 30 20 10
Top element is: 30
30 popped from stack.
Stack elements (top to bottom): 20 10

=== String Stack ===
Alice pushed into stack.
Bob pushed into stack.
Carol pushed into stack.
Stack elements (top to bottom): Carol Bob Alice

4. Implementation Comparison: Array vs. Linked List

FeatureArray-Based Stack (Example 2)Linked List-Based Stack (Example 3)
Storage StructureFixed-size array (int arr[MAX])Dynamic heap nodes (Node<T>*)
CapacityFixed compile-time bound (MAX = 5)Dynamic (grows on demand)
Top PointerInteger index (int top)Node pointer (Node<T>* top)
Empty Checktop == -1top == nullptr
Full Checktop == MAX - 1Not required (no static limit)
Overflow ConditionTriggers "Stack Overflow!" on 6th pushDoes not overflow
Underflow ConditionTriggers "Stack Underflow!" on empty popTriggers "Stack Underflow!" on empty pop
Memory CleanupAutomatic stack memoryExplicit ~Stack() destructor calling pop()
Generic TypesHardcoded for intGeneric template <class T> (int, string, etc.)