Introduction to Stack Data Structure

Share this Content

In computer science, data structures are fundamental concepts that enable us to organize and manage data efficiently. One of the most commonly used data structures is the Stack, which is a simple and efficient way to store and manipulate data.

The stack follows the Last-In-First-Out (LIFO) principle, which means that the last element added to the stack is the first one to be removed. This principle is similar to a stack of plates, where the last plate added to the stack is the first one to be taken off. The stack data structure is used in various programming languages and applications, including compilers, operating systems, web browsers, and database management systems. It is a powerful tool that enables developers to solve various problems efficiently.

In this article, we will discuss the stack data structure in detail, including its history, working, and applications. We will also cover the various operations that can be performed on a stack, such as push, pop, and peek. By the end of this article, you will have a good understanding of how the stack works and how it can be used to solve various problems.

Definition:

A stack is a fundamental data structure in computer science that stores and organizes data in a particular way. It is a collection of elements with two main operations, Push and Pop, that add and remove elements from the top of the stack. The stack follows the LIFO (Last-In-First-Out) principle, meaning that the most recently added element is the first one to be removed.

Brief history of stack data structure

a girl standing and thinking something

The stack data structure was first introduced in the 1950s by computer scientists Allen Newell and Herbert Simon. They were working on a computer program called the Logic Theory Machine (LTW) that could prove mathematical theorems. To solve the problem of managing the program’s memory, they introduced the concept of a “last-in, first-out” (LIFO) data structure, which they called a “stack.” The stack data structure was essential for keeping track of the program’s state and for performing the backtracking necessary for proving theorems.

In the following years, the stack data structure became widely used in computer science and programming. In the 1960s and 1970s, stack-based languages such as Forth and Postscript were developed, which used the stack data structure as the primary means of storing data and executing instructions. The stack-based approach allowed for a simple and efficient implementation of these languages and enabled them to be used in embedded systems and other resource-constrained environments.

In the 1980s and 1990s, object-oriented programming languages such as C++ and Java became popular, and the stack data structure remained an essential concept in these languages. In C++, the stack is used for storing local variables and function call frames, while in Java, the stack is used for managing the execution of Java Virtual Machine (JVM) instructions.

Today, the stack data structure continues to be used extensively in programming and computer science. It is a fundamental concept that every programmer should be familiar with, and its efficient implementation is crucial for designing efficient algorithms and applications.

Working of stack

working of Stack datastructure

A stack is a simple data structure that follows the LIFO (Last-In-First-Out) principle, which means that the last element added to the stack is the first one to be removed. The stack has three main operations: push, pop, and peek.

A. Push Operation

The push operation adds an element to the top of the stack. To perform a push operation, we need to follow the following steps:

  1. Check if the stack is full or not. If the stack is full, then it is not possible to add any new element to the stack.
  2. If the stack is not full, then increment the top pointer to point to the next empty location.
  3. Add the new element to the location pointed by the top pointer.
pop operation

B. Pop Operation

pop operation

The pop operation removes the element from the top of the stack. To perform a pop operation, we need to follow the following steps:

  1. Check if the stack is empty or not. If the stack is empty, then it is not possible to remove any element from the stack.
  2. If the stack is not empty, then remove the element at the top of the stack.
  3. Decrement the top pointer to point to the next element in the stack.

C. Peek Operation

The peek operation returns the value of the element at the top of the stack without removing it. To perform a peek operation, we need to follow the following steps:

  1. Check if the stack is empty or not. If the stack is empty, then there is no element to peek.
  2. If the stack is not empty, then return the value of the element at the top of the stack.
peek operation

Example:

Let’s take an example to understand the working of the stack operations. Consider a stack of integers with a maximum size of 5.

  1. Initially, the stack is empty, and the top pointer points to -1.
  2. We perform the push operation to add the elements 10, 20, 30, and 40 to the stack. The stack now looks like:[40] [30] [20] [10] top = 3
  3. We perform the peek operation to get the value of the element at the top of the stack, which is 40.
  4. We perform the pop operation to remove the element at the top of the stack, which is 40. The stack now looks like:[30] [20] [10] top = 2
  5. We perform the push operation to add the element 50 to the stack. The stack now looks like:[50] [30] [20] [10] top = 3
  6. We perform the pop operation to remove the element at the top of the stack, which is 50. The stack now looks like:[30] [20] [10] top = 2
  7. We perform the push operation to add the element 60 to the stack. The stack now looks like:[60] [30] [20] [10] top = 3
  8. We perform the pop operation to remove all the elements from the stack. The stack is now empty, and the top pointer points to -1.

The push, pop, and peek operations are the basic building blocks of the stack data structure. They enable us to store, retrieve and manipulate data in a LIFO manner, which is useful in various real-life applications.

Stack implementation:

There are two common ways to implement a stack: using an array or a linked list. Both approaches have their own advantages and disadvantages, and the choice of implementation depends on the specific use case.

Array:

In the array-based implementation, the stack is implemented using a fixed-size array that holds the elements. The top of the stack is tracked by a variable that points to the index of the last added element. The push operation adds the element to the top of the stack by incrementing the top variable and storing the element in the array at the new index. The pop operation removes the element at the top index and decrements the top variable.

Example:

In this implementation, we use an array to represent the stack. The top element of the stack is represented by the index of the last element in the array. Here’s a sample Java code snippet that shows how to implement a stack using an array:

public class ArrayStack {
    private int[] arr;
    private int top;
    private int size;

    public ArrayStack(int size) {
        this.size = size;
        this.arr = new int[size];
        this.top = -1;
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public boolean isFull() {
        return top == size - 1;
    }

    public void push(int data) {
        if (isFull()) {
            System.out.println("Stack Overflow");
            return;
        }
        arr[++top] = data;
    }

    public int pop() {
        if (isEmpty()) {
            System.out.println("Stack Underflow");
            return -1;
        }
        return arr[top--];
    }

    public int peek() {
        if (isEmpty()) {
            System.out.println("Stack Underflow");
            return -1;
        }
        return arr[top];
    }

    public static void main(String[] args) {
        ArrayStack stack = new ArrayStack(5);

        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);

        System.out.println(stack.pop()); // Output: 5
        System.out.println(stack.peek()); // Output: 4
        System.out.println(stack.pop()); // Output: 4
        System.out.println(stack.isEmpty()); // Output: false
        System.out.println(stack.isFull()); // Output: false

        stack.push(6); // Output: Stack Overflow
    }
}
Java

In the above code snippet, we define a ArrayStack class that represents a stack using an array. We use the top variable to keep track of the top element of the stack.

The push operation adds an element to the top of the stack by incrementing the top variable and setting the element at the top position in the array. The pop operation removes the top element from the stack by returning the element at the top position and decrementing the top variable. The peek operation returns the top element of the stack without removing it.

Complexity:
  • Space complexity: In this implementation, we create an array of size n to store n elements. Hence, the space complexity of this implementation is O(n).
  • Time complexity: All stack operations, such as push, pop, and peek, take constant time, O(1), as they involve accessing the top of the stack, which is stored in a variable.

Overall, the array-based implementation has a space complexity of O(n) and a time complexity of O(1) for all stack operations.

Subscribe to Tech Break

Linked List:

In the linked list-based implementation, the stack is implemented using a linked list data structure where each node holds an element and a pointer to the next node. The top of the stack is represented by the head of the linked list. The push operation adds a new node to the head of the linked list, and the pop operation removes the head node and updates the head pointer to the next node in the list. The advantage of using a linked list is that it can grow or shrink dynamically, while the array-based implementation has a fixed size.

Example:

In this implementation, we use a linked list to represent the stack. Each node in the linked list represents an element in the stack. The top element of the stack is represented by the head of the linked list. Here’s a sample Java code snippet that shows how to implement a stack using a linked list:

public class LinkedListStack {
    private Node top;

    private static class Node {
        private int data;
        private Node next;

        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }

    public boolean isEmpty() {
        return top == null;
    }

    public void push(int data) {
        Node node = new Node(data);
        node.next = top;
        top = node;
    }

    public int pop() {
        if (isEmpty()) {
            System.out.println("Stack Underflow");
            return -1;
        }
        int data = top.data;
        top = top.next;
        return data;
    }

    public int peek() {
        if (isEmpty()) {
            System.out.println("Stack Underflow");
            return -1;
        }
        return top.data;
    }

    public static void main(String[] args) {
        LinkedListStack stack = new LinkedListStack();

        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);

        System.out.println(stack.pop()); // Output: 5
        System.out.println(stack.peek()); // Output: 4
        System.out.println(stack.pop()); // Output: 4
        System.out.println(stack.isEmpty()); // Output: false

        stack.push(6);
        System.out.println(stack.pop()); // Output: 6
    }
}
Java

In the above code snippet, we define a LinkedListStack class that represents a stack using a linked list. We use the top variable to keep track of the top element of the stack.

The push operation adds an element to the top of the stack by creating a new node and setting its next pointer to the current top node. The top variable is then updated to point to the new node. The pop operation removes the top element from the stack by returning the element at the head of the linked list and updating the top variable to point to the next node. The peek operation returns the top element of the stack without removing it.

Both implementations have their own advantages and disadvantages. The array implementation is more space-efficient as it uses a contiguous block of memory, but it has a fixed size that cannot be changed dynamically. The linked list implementation is more flexible as it can grow or shrink dynamically, but it requires extra memory for the next pointer in each node.

Complexity:
  • Space complexity: In this implementation, we create a linked list that grows as elements are added to it. Hence, the space complexity of this implementation is O(n).
  • Time complexity: Push and pop operations take constant time, O(1), as we only need to add or remove a node from the top of the linked list. Peek operation also takes constant time, O(1), as we only need to access the data of the top node.

Overall, the linked list-based implementation has a space complexity of O(n) and a time complexity of O(1) for all stack operations.

Applications of Stack:

Now, let’s discuss some of the most common applications of stack data structure.

1. Expression evaluation: One of the most common applications of stack data structure is in evaluating expressions. In computer science, expressions are usually written in infix notation, where operators are placed between operands. For example, the expression (5 + 2) * 3 is written in infix notation. However, to evaluate expressions, they are usually converted to postfix notation, where the operator comes after the operands. The postfix notation of the same expression would be 5 2 + 3 *. To evaluate expressions in postfix notation, we can use a stack to store the operands and perform the required operations.

2. Function call management: In many programming languages, function calls are managed using a stack. When a function is called, its parameters and return address are pushed onto the stack, and when the function returns, its return value is popped from the stack, and the program jumps back to the return address.

3. Backtracking algorithms: Backtracking algorithms are a class of algorithms that involve exploring all possible solutions to a problem by trying different paths until a solution is found. Backtracking algorithms typically use a stack to store the current path, and if the current path leads to a dead end, the algorithm backtracks and tries a different path.

4. Undo/Redo functionality: Undo/redo functionality in computer applications is implemented using a stack. Each time a user performs an action, it is pushed onto the stack, and when the user clicks the undo button, the most recent action is popped from the stack and reversed.

5. Parsing: Parsing is the process of analyzing a sequence of characters to determine its grammatical structure. In programming, parsing is often used to convert code written in one language to another. A stack can be used to keep track of the opening and closing brackets, parentheses, and other delimiters, making it easier to parse the code.

6. Compiler and interpreter design: Compilers and interpreters are programs that convert code written in one language to another. Both compilers and interpreters use stacks to manage the function call hierarchy, local variables, and other resources.

7. Memory management: In computer programming, memory is typically managed using a stack. Each time a new variable is declared, it is pushed onto the stack, and when the variable is no longer needed, it is popped from the stack to free up memory.

These are just a few examples of the many applications of stack data structure in computer science. The versatility and simplicity of the stack data structure make it an essential tool for developers in many different fields.

Conclusion

In conclusion, the stack data structure is an essential tool for developers in computer science. Whether you are building a web application, a game, or a mobile app, understanding the principles and applications of the stack can help you write more efficient, scalable, and robust code.

As you continue to explore the world of computer science, keep in mind the power and versatility of the stack data structure. By mastering this fundamental concept, you can gain a deeper understanding of algorithms and data structures, improve your problem-solving skills, and become a more effective programmer.

So, keep learning, keep growing, and don’t hesitate to experiment with different approaches to solving problems with stacks. Who knows – you may just discover a new and innovative way to use this simple yet powerful data structure in your next project!

Share this Content
Snehasish Konger
Snehasish Konger

Snehasish Konger is the founder of Scientyfic World. Besides that, he is doing blogging for the past 4 years and has written 400+ blogs on several platforms. He is also a front-end developer and a sketch artist.

Articles: 191

Newsletter Updates

Join our email-newsletter to get more insights