how-to-implement-a-stack-data-structure-in-c.html

How to Implement a Stack Data Structure in C++

Stacks are fundamental data structures used in computer science for managing data in a specific order. They follow the Last In, First Out (LIFO) principle, meaning that the last element added to the stack is the first to be removed. In this article, we will dive deep into how to implement a stack in C++, exploring its definitions, use cases, and actionable coding insights.

What is a Stack?

A stack can be visualized as a collection of elements stacked on top of each other, much like a stack of plates. The operations associated with a stack are:

  • Push: Add an item to the top of the stack.
  • Pop: Remove the item from the top of the stack.
  • Peek (or Top): Retrieve the top item without removing it.
  • IsEmpty: Check whether the stack is empty.

Use Cases of Stack Data Structure

Stacks are widely used in various programming scenarios, including:

  • Function Call Management: Stacks help maintain function calls in programming languages. Each function call is pushed onto the stack, and when it returns, it is popped off.
  • Expression Evaluation: Stacks are useful in evaluating expressions, especially in converting infix expressions to postfix notation.
  • Backtracking Algorithms: Algorithms like Depth-First Search (DFS) utilize stacks to track visited nodes.
  • Undo Mechanisms: Applications often use stacks to implement undo features.

Now that we understand the fundamentals of stacks, let’s look at how to implement a stack data structure in C++.

Implementing a Stack in C++

Step 1: Define the Stack Class

We will create a simple stack class using a linked list for dynamic memory management. This approach allows the stack to grow and shrink as needed.

#include <iostream>
using namespace std;

template <typename T>
class StackNode {
public:
    T data;
    StackNode* next;

    StackNode(T value) : data(value), next(nullptr) {}
};

template <typename T>
class Stack {
private:
    StackNode<T>* top;

public:
    Stack() : top(nullptr) {}

    ~Stack() {
        while (!isEmpty()) {
            pop();
        }
    }

    void push(T value) {
        StackNode<T>* newNode = new StackNode<T>(value);
        newNode->next = top;
        top = newNode;
    }

    T pop() {
        if (isEmpty()) {
            throw out_of_range("Stack Underflow: No elements to pop.");
        }
        StackNode<T>* temp = top;
        T poppedValue = top->data;
        top = top->next;
        delete temp;
        return poppedValue;
    }

    T peek() const {
        if (isEmpty()) {
            throw out_of_range("Stack is empty: No elements to peek.");
        }
        return top->data;
    }

    bool isEmpty() const {
        return top == nullptr;
    }
};

Step 2: Stack Operations Explained

  1. Push Operation: This method creates a new node containing the value to be added, sets its next pointer to the current top node, and updates the top pointer to the new node.

  2. Pop Operation: This method checks if the stack is empty. If not, it stores the value of the top node, updates the top pointer to the next node, and deletes the old top node to free memory.

  3. Peek Operation: This method retrieves the value of the top node without modifying the stack.

  4. IsEmpty Operation: This method simply checks if the top pointer is null, indicating that the stack is empty.

Step 3: Testing the Stack Implementation

Now that we have our stack class set up, let’s test its functionality using a simple driver program:

int main() {
    Stack<int> myStack;

    myStack.push(10);
    myStack.push(20);
    myStack.push(30);

    cout << "Top element is: " << myStack.peek() << endl; // 30

    cout << "Popped element: " << myStack.pop() << endl; // 30
    cout << "Top element after pop: " << myStack.peek() << endl; // 20

    while (!myStack.isEmpty()) {
        cout << "Popping element: " << myStack.pop() << endl;
    }

    try {
        myStack.pop(); // This will throw an exception
    } catch (const out_of_range& e) {
        cout << e.what() << endl; // Stack Underflow message
    }

    return 0;
}

Performance Considerations

When implementing a stack, consider the following:

  • Time Complexity: All operations (push, pop, peek) run in O(1) time, making stacks efficient for their intended use.
  • Space Complexity: The space used by the stack is proportional to the number of elements stored in it.

Troubleshooting Common Issues

  • Stack Overflow: This can happen if you try to push onto a stack that has reached its limit (if using a fixed-size array).
  • Stack Underflow: Attempting to pop from an empty stack will lead to this error, which we handle in our implementation.

Conclusion

Implementing a stack data structure in C++ is straightforward and offers valuable insight into memory management and data structures. With its diverse applications ranging from function call management to expression evaluation, mastering stacks is crucial for any programmer. By following the steps outlined above, you can efficiently create and manage a stack in your C++ applications, paving the way for more complex data structures and algorithms. Happy coding!

SR
Syed
Rizwan

About the Author

Syed Rizwan is a Machine Learning Engineer with 5 years of experience in AI, IoT, and Industrial Automation.