KOLA VINAY KUMAR
2403B05107
ADVANCE DATA STRUCTURES
ASSIGNMENT-03
AIM: Implement a stack using arrays and linked lists.
Description: Write code for stack operations and use the stack for infix-to-postfix conversion.
Expected Outcome: Understand stack operations and its applications in expression conversion.
Stack Operations in Java:
1. Push: Adds an element to the top of the stack. If the stack is implemented with an array, it
checks for overflow (full stack) before adding.
2. Pop: Removes and returns the top element of the stack. It checks for underflow (empty
stack) before removal.
3. Peek: Returns the top element of the stack without removing it.
4. IsEmpty: Checks if the stack is empty, returning a boolean value.
5. Implementation Options: Can be implemented using arrays or linked lists, each with its own
trade-offs in terms of memory and flexibility.
Infix and Postfix Conversion:
1. Infix Notation: Operators are placed between operands (e.g., A + B), which requires
parentheses and operator precedence rules for evaluation.
2. Postfix Notation (Reverse Polish Notation): Operators follow their operands (e.g., AB+),
eliminating the need for parentheses and making evaluation simpler.
3. Conversion Algorithm: Uses a stack to handle operators, ensuring correct precedence and
associativity.
4. Applications: Postfix expressions are used in compilers, calculators, and expression
evaluations for simpler parsing.
5. Advantage: Postfix eliminates ambiguity and allows evaluation in a single left-to-right pass
without backtracking.
Stack using arrays and linked lists:
To implement a stack using arrays and linked lists in Java, I’ll provide code for both
implementations. We’ll then use the stack to convert an infix expression to postfix
notation.
Code
Here is the Java code to implement stacks with arrays and linked lists, followed by the infix-
to-postfix conversion function.
import java.util.Stack;
// Stack implementation using an array
class ArrayStack {
private int top;
private int maxSize;
private char[] stackArray;
public ArrayStack(int size) {
maxSize = size;
stackArray = new char[maxSize];
top = -1;
public void push(char value) {
if (top == maxSize - 1) {
System.out.println("Stack is full");
} else {
stackArray[++top] = value;
public char pop() {
return (top == -1) ? '\0' : stackArray[top--];
public char peek() {
return (top == -1) ? '\0' : stackArray[top];
public boolean isEmpty() {
return top == -1;
}
// Stack implementation using a linked list
class LinkedListStack {
private class Node {
char data;
Node next;
Node(char data) {
this.data = data;
next = null;
private Node top;
public LinkedListStack() {
top = null;
public void push(char value) {
Node newNode = new Node(value);
newNode.next = top;
top = newNode;
public char pop() {
if (top == null) {
System.out.println("Stack is empty");
return '\0';
} else {
char value = top.data;
top = top.next;
return value;
public char peek() {
return (top == null) ? '\0' : top.data;
public boolean isEmpty() {
return top == null;
}
}
// Infix to Postfix Conversion using Stack
class InfixToPostfix {
private ArrayStack stack;
public InfixToPostfix(int size) {
stack = new ArrayStack(size);
public String convert(String infix) {
StringBuilder postfix = new StringBuilder();
for (int i = 0; i < infix.length(); i++) {
char ch = infix.charAt(i);
// If character is an operand, add it to output
if (Character.isLetterOrDigit(ch)) {
postfix.append(ch);
}
// If character is '(', push it to the stack
else if (ch == '(') {
stack.push(ch);
// If character is ')', pop and output from the stack until '(' is found
else if (ch == ')') {
while (!stack.isEmpty() && stack.peek() != '(') {
postfix.append(stack.pop());
stack.pop(); // remove '('
// If an operator is encountered
else {
while (!stack.isEmpty() && precedence(ch) <= precedence(stack.peek())) {
postfix.append(stack.pop());
stack.push(ch);
// pop all operators from the stack
while (!stack.isEmpty()) {
postfix.append(stack.pop());
return postfix.toString();
private int precedence(char ch) {
switch (ch) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
return 3;
return -1;
// Testing the code
public class Main {
public static void main(String[] args) {
String infixExpression = "a+b*(c^d-e)^(f+g*h)-i";
InfixToPostfix converter = new InfixToPostfix(infixExpression.length());
System.out.println("Infix Expression: " + infixExpression);
String postfixExpression = converter.convert(infixExpression);
System.out.println("Postfix Expression: " + postfixExpression);
Explanation of the Code:
1. ArrayStack: Implements a stack using an array. Supports push, pop, peek, and isEmpty.
2. LinkedListStack: Implements a stack using a linked list with push, pop, peek, and isEmpty.
3. InfixToPostfix: Uses an ArrayStack to convert an infix expression to postfix notation. The
algorithm handles:
o Operands: Directly added to the output.
o Operators: Managed based on their precedence.
o Parentheses: Used to control the precedence explicitly.
Output
For an input of a+b*(c^d-e)^(f+g*h)-i, the output will be:
Infix Expression: a+b*(c^d-e)^(f+g*h)-i
Postfix Expression: abcd^e-fgh*+^*+i-
AIM: Demonstrate recursion and implement recursion with an explicit stack.
Description: Convert a recursive function (e.g., factorial) to use an iterative approach with a stack.
Expected Outcome: Compare recursive and iterative methods in terms of space and time
efficiency.
What is Recursion in Java?
Recursion in Java is a process where a method calls itself to solve a problem.
It is widely used in algorithms to solve problems that can be broken down into smaller,
similar sub-problems.
Structure of a Recursive Method
A recursive method in Java typically includes:
1. Base Case: Terminates the recursion.
2. Recursive Case: Calls the method itself with smaller input.
Example:
public int factorial(int n) {
if (n == 0) { // Base case
return 1;
return n * factorial(n - 1); // Recursive case
How Recursion Works in Java
Each recursive call is pushed onto the call stack.
The program waits until the base case is reached, then resolves each call in reverse order.
Recursive calls consume memory, which may lead to a StackOverflowError if too many calls
are made.
Types of Recursion
1. Direct Recursion: A method calls itself direct
public void directRecursion() {
directRecursion();
2. Indirect Recursion: A method calls another method, which in turn calls the first method.
public void methodA() {
methodB();
public void methodB() {
methodA();
3. Tail Recursion: The recursive call is the last statement in the method.
public int tailRecursion(int n, int result) {
if (n == 0) return result;
return tailRecursion(n - 1, n * result);
Advantages of Recursion in Java
Simplifies code for problems like traversing trees, graphs, or solving puzzles.
Reduces complex iterative loops into elegant and readable methods.
Useful for algorithms following a divide-and-conquer strategy (e.g., MergeSort, QuickSort).
6. Disadvantages of Recursion
Memory Usage: Each recursive call uses stack space, leading to O(n)O(n)O(n) space
complexity.
Performance Overhead: Function calls involve overhead for parameter passing and context
switching.
Risk of StackOverflowError: Excessive recursion depth may exceed the call stack limit.
8. Applications of Recursion in Java
1. Mathematical Problems:
o Factorial, Fibonacci sequence, GCD, power calculation.
2. Data Structures:
o Tree Traversals (Inorder, Preorder, Postorder).
o Graph Traversals (DFS).
3. Sorting Algorithms:
o MergeSort, QuickSort.
4. Dynamic Programming:
o Problems solved using recursion with memoization.
5. Puzzles:
o Tower of Hanoi, N-Queens Problem.
Implementation of recursion with an explicit stack:
Here’s a Java program demonstrating both a recursive and an iterative (using an explicit stack)
implementation of the factorial function. The code will highlight the differences in implementation
and allow for a comparison of space and time efficiency.
import java.util.Stack;
public class RecursionVsIteration {
// Recursive implementation of factorial
public static int factorialRecursive(int n) {
if (n == 0 || n == 1) {
return 1;
return n * factorialRecursive(n - 1);
// Iterative implementation of factorial using an explicit stack
public static int factorialUsingStack(int n) {
Stack<Integer> stack = new Stack<>();
int result = 1;
// Push numbers onto the stack
while (n > 0) {
stack.push(n--);
// Pop numbers and multiply
while (!stack.isEmpty()) {
result *= stack.pop();
return result;
public static void main(String[] args) {
int number = 5; // Change this number to test with other values
// Recursive factorial
System.out.println("Factorial of " + number + " (Recursive): " + factorialRecursive(number));
// Iterative factorial using explicit stack
System.out.println("Factorial of " + number + " (Using Stack): " + factorialUsingStack(number));
// Space and time complexity comparison
System.out.println("\nSpace and Time Comparison:");
System.out.println("Recursive: Uses system stack for recursion, space complexity O(n).");
System.out.println("Iterative with Stack: Uses an explicit stack, space complexity O(n), but avoids
system overhead.");
Output
For an input of number = 5, the output will be:
Factorial of 5 (Recursive): 120
Factorial of 5 (Using Stack): 120
Space and Time Comparison:
Recursive: Uses system stack for recursion, space complexity O(n).
Iterative with Stack: Uses an explicit stack, space complexity O(n), but avoids system overhead.
Explanation
1. Recursive Method:
o Uses the system's call stack for each recursive call.
o Space complexity is O(n)O(n)O(n) due to the call stack.
o Elegant but limited by stack depth for large inputs.
2. Iterative Method with Stack:
o Uses an explicit stack to simulate the recursion.
o Space complexity remains O(n)O(n)O(n), but no risk of stack overflow.
o Slightly more verbose but suitable for large inputs where recursion may fail.
Comparison
Space Efficiency: Both methods use O(n)O(n)O(n) space, but iterative avoids system stack
overhead.
Time Efficiency: Similar for both, but recursion involves function call overhead.
Use Cases: Recursive methods are simpler for small inputs, while iterative methods handle
larger inputs more safely.