Unit 1: Arrays, Stacks, and Recursion

Table of Contents

1. Arrays

An array is a linear data structure that stores a collection of elements of the same data type in contiguous memory locations. Each element is identified by at least one array index or key.

Single-dimensional Arrays

A single-dimensional (1D) array is the simplest form, represented as a single row or column of elements.

Multi-dimensional Arrays

A multi-dimensional array has more than one dimension. The most common is the two-dimensional (2D) array, which can be visualized as a table or grid with rows and columns.

Sparse Matrices

A sparse matrix is a matrix in which most of the elements are zero. It is inefficient to store this matrix in a standard 2D array because a huge amount of memory is wasted on storing zeros.

Array Representation (Triplet Representation)

This method stores only the non-zero elements. A 2D array is created with three columns:

The first row of this representation is often used to store the metadata: (Total Rows, Total Columns, Total Non-Zero Elements).

Example:

Original Matrix (4x4):
[ 0, 0, 3, 0 ]
[ 5, 0, 0, 0 ]
[ 0, 1, 0, 0 ]
[ 0, 0, 0, 2 ]

Array (Triplet) Representation (5x3):

RowColValue
444
023
105
211
332

Linked Representation

This method uses a linked list to store the non-zero elements. Each node in the linked list contains:

A header node can store the matrix dimensions. This is more flexible than the array representation as it doesn't require pre-allocating space for the non-zero elements.

2. Stacks

A Stack is a linear data structure that follows the LIFO (Last-In, First-Out) principle. This means the element added last is the first one to be removed. Think of it as a stack of plates.

The primary operations are:

Implementing Stacks in an Array

A stack can be easily implemented using a 1D array and a variable top that keeps track of the index of the last element added.

Common Mistake: Forgetting to check for Overflow (before push) and Underflow (before pop). This can lead to undefined behavior or program crashes.

Implementing Multiple Stacks in an Array

It is possible to implement two stacks in a single array efficiently. One stack starts from index 0 (grows right), and the second stack starts from index MAX_SIZE - 1 (grows left).

Prefix, Infix, and Postfix Expressions

These are different notations for writing arithmetic expressions.

Utility: Prefix and Postfix notations do not require parentheses or precedence rules. They are ideal for computer evaluation, which is why stacks are used.

InfixPrefixPostfix
A + B+ A BA B +
(A + B) * C* + A B CA B + C *
A + (B * C)+ A * B CA B C * +

Utility and Conversion of Expressions

Algorithm: Infix to Postfix Conversion

This is a classic stack application. We use a stack to hold operators and parentheses.

  1. Create an empty stack (for operators) and an empty string (for the postfix result).
  2. Scan the infix expression from left to right.
  3. If the character is an operand (e.g., A, B, 1, 2): Append it directly to the postfix string.
  4. If the character is an open parenthesis '(': Push it onto the stack.
  5. If the character is a close parenthesis ')': Pop operators from the stack and append them to the postfix string until an open parenthesis '(' is encountered. Pop and discard the '('.
  6. If the character is an operator:
    • While the stack is not empty AND the top of the stack is an operator with higher or equal precedence than the current operator:
    • Pop the operator from the stack and append it to the postfix string.
    • After the loop, push the current operator onto the stack.
  7. After scanning the entire infix expression, pop any remaining operators from the stack and append them to the postfix string.
Exam Tip: Be sure to manage precedence. (e.g., * and / have higher precedence than + and -). Associativity (left-to-right) is handled by the "equal precedence" part of the rule.

Algorithm: Evaluation of Postfix Expression

This also uses a stack, but this time to hold operands (numbers).

  1. Create an empty stack (for operands).
  2. Scan the postfix expression from left to right.
  3. If the character is an operand: Push its value onto the stack.
  4. If the character is an operator:
    1. Pop the top two operands from the stack (e.g., operand2 = pop(), operand1 = pop()).
    2. Perform the operation: result = operand1 operator operand2.
    3. Push the result back onto the stack.
  5. After scanning the entire expression, the final result will be the only value left in the stack. Pop and return it.

Example: Evaluate 2 3 + 5 *

  1. Scan '2': Push 2. Stack: [2]
  2. Scan '3': Push 3. Stack: [2, 3]
  3. Scan '+': Pop 3, Pop 2. Calculate 2 + 3 = 5. Push 5. Stack: [5]
  4. Scan '5': Push 5. Stack: [5, 5]
  5. Scan '*': Pop 5, Pop 5. Calculate 5 * 5 = 25. Push 25. Stack: [25]
  6. End of expression. Pop 25. Result is 25.

Applications of Stack

Limitations of Array Representation of Stack

(These limitations are solved by using a Linked List to implement a stack, as seen in Unit 2).

3. Recursion

Recursion is a programming technique where a function calls itself, either directly or indirectly. A recursive function solves a problem by breaking it down into smaller, simpler versions of the same problem.

Recursive Definitions and Implementation

A recursive definition (and its implementation) must have two parts:

  1. Base Case (or Terminating Condition): This is the simplest version of the problem, which can be solved directly without further recursion. It stops the function from calling itself infinitely.
  2. Recursive Step: This part breaks the problem into a smaller subproblem and calls the function itself to solve that subproblem. It must make progress toward the base case.
Common Mistake: Forgetting the base case. This leads to infinite recursion and eventually a Stack Overflow error, as the call stack runs out of memory.

Example: Factorial

Problem: Find the factorial of a non-negative integer n (n!).

Example: Fibonacci Series

Problem: Find the n-th Fibonacci number (0, 1, 1, 2, 3, 5, 8...)

Advantages and Limitations of Recursion

Advantages:

Limitations:

Exam Tip: Any problem that can be solved recursively can also be solved iteratively (using loops). Often, an iterative solution is preferred for performance, but a recursive solution is easier to design and understand first.