πŸ”’ Private Site

This site is password-protected.

Unit I β€” Fundamental Concepts

Syllabus Coverage: Introduction to Data Structures β€” Data, Data Objects, Data Types, Abstract Data Type (ADT) and data structure, Data structure applications, Concepts of static and dynamic, linear and non-linear data structures. Introduction to Algorithms β€” Definition, Characteristics of an algorithm. Algorithm design tools β€” Flowcharts and Pseudo code, notations. Algorithm Analysis β€” Time and Space Complexity, Big O, Omega Ξ©, Theta Θ, Best, Average and Worst case analysis. (07 Hours)


Table of Contents


Glossary β€” Key Terms at a Glance

Term Meaning
Data Raw facts or values with no meaning on their own β€” numbers, characters, symbols, etc.
Information Processed, organized data that is meaningful and useful.
Data Object A set of elements that share a common type (e.g., integers, characters).
Data Type Defines a set of values AND the operations permitted on them (e.g., int, float).
Primitive Data Type Built-in data type provided by the language (int, char, float, double).
Non-Primitive Data Type User-defined or derived types (arrays, structures, linked lists, trees, etc.).
Abstract Data Type (ADT) A mathematical model describing data + operations, independent of implementation.
Data Structure A specific, concrete way of organizing and storing data in memory.
Static Data Structure Fixed-size structure β€” memory allocated at compile time (e.g., arrays).
Dynamic Data Structure Flexible-size structure β€” memory allocated at runtime (e.g., linked lists).
Linear Data Structure Elements arranged in a sequential order with at most one predecessor and one successor.
Non-Linear Data Structure Elements arranged hierarchically or in a network β€” one element can connect to multiple others.
Algorithm A finite, step-by-step procedure to solve a specific problem.
Flowchart A graphical/visual representation of an algorithm using standard symbols.
Pseudo Code An informal, language-independent description of an algorithm using structured English.
Time Complexity A measure of how the running time of an algorithm grows as input size increases.
Space Complexity A measure of how much extra memory an algorithm needs as input size increases.
Big O β€” $O(f(n))$ Asymptotic upper bound β€” worst-case growth rate of an algorithm.
Omega β€” $\Omega(f(n))$ Asymptotic lower bound β€” best-case growth rate.
Theta β€” $\Theta(f(n))$ Tight bound β€” the algorithm grows at exactly this rate (both upper and lower).
Best Case The input configuration that causes the algorithm to run fastest.
Worst Case The input configuration that causes the algorithm to run slowest.
Average Case The expected running time over all possible inputs.

1 β€” Introduction to Data Structures


1.1 What is Data?

Definition: Data refers to raw, unprocessed facts β€” numbers, characters, symbols, images, sounds β€” that have no inherent meaning on their own.

Examples:

  • The number 42 β€” on its own, it’s just a number.
  • The string "Alice" β€” on its own, just a sequence of characters.
  • 42 + context (β€œAlice’s age is 42”) β†’ now it becomes information.

Data β†’ (Processing) β†’ Information

The purpose of a computer program is to process data and produce information.


1.2 Data Objects

Definition: A Data Object is a set (or collection) of elements that belong to the same type. It defines the domain of values.

Examples:

Data Object Domain (Set of Values)
Integer ${…, -2, -1, 0, 1, 2, …}$
Boolean ${true, false}$
Character ${$ 'a', 'b', …, 'z', '0', …, '9', … $}$
Student Marks ${0, 1, 2, …, 100}$

A data object can be thought of as the β€œuniverse” of legal values for a particular data entity.


1.3 Data Types

Definition: A Data Type defines:

  1. A set of values (the domain), AND
  2. A set of operations that can be performed on those values.

Primitive (Built-in) Data Types

These are directly supported by the programming language:

Data Type Size (typical in C) Range (typical 32-bit) Operations
int 4 bytes $-2^{31}$ to $2^{31}-1$ +, -, *, /, %, comparisons
float 4 bytes β‰ˆ $\pm 3.4 \times 10^{38}$ +, -, *, /, comparisons
double 8 bytes β‰ˆ $\pm 1.7 \times 10^{308}$ +, -, *, /, comparisons
char 1 byte 0 to 255 (unsigned) comparisons, arithmetic (ASCII)

Non-Primitive (Derived/User-Defined) Data Types

These are built from primitive types:

Category Examples
Arrays Collection of elements of the same type stored contiguously
Structures / Records Collection of elements of different types grouped together
Pointers Variables that store memory addresses
Strings Array of characters terminated by '\0'
Linked Lists, Stacks, Queues, Trees, Graphs Complex data structures built using pointers + nodes

Key Insight: Data Type = Values + Operations.
int is not just β€œwhole numbers” β€” it includes +, -, *, /, %, <, >, etc. as part of its definition.


1.4 Abstract Data Type (ADT)

Definition: An Abstract Data Type (ADT) is a mathematical/logical model that defines:

  1. Data β€” what kind of data is stored
  2. Operations β€” what can be done with the data
  3. Error conditions β€” what happens when an operation is misused

…WITHOUT specifying HOW the data is stored or how the operations are implemented.

Why ADTs Matter

ADTs separate the β€œwhat” from the β€œhow”:

Aspect ADT (What) Data Structure (How)
Focus Logical behavior Physical implementation
Specifies Operations & constraints Memory layout & algorithms
Example Stack ADT: push, pop, top, isEmpty Stack using array OR linked list

Example: Stack ADT

ADT Stack:
    Data:
        A collection of elements with LIFO (Last-In, First-Out) ordering
    
    Operations:
        push(element)   β†’ Add element to top
        pop()           β†’ Remove and return top element
        top()           β†’ Return top element without removing
        isEmpty()       β†’ Return true if stack has no elements
        isFull()        β†’ Return true if stack is at maximum capacity
    
    Error Conditions:
        pop() on empty stack  β†’ "Underflow"
        push() on full stack  β†’ "Overflow"

Example: List ADT

ADT List:
    Data:
        An ordered collection of elements of the same type
    
    Operations:
        insert(pos, element)  β†’ Insert element at position
        delete(pos)           β†’ Delete element at position
        search(element)       β†’ Find position of element
        get(pos)              β†’ Retrieve element at position
        length()              β†’ Return number of elements
        isEmpty()             β†’ Return true if list has no elements

Think of it this way: An ADT is like a β€œcontract” or β€œinterface.” It says what operations are available, not how they work internally. A Stack ADT can be implemented using an array or a linked list β€” the user doesn’t care as long as push and pop work correctly.


1.5 What is a Data Structure?

Definition: A Data Structure is a particular way of organizing, storing, and managing data in a computer’s memory so that it can be used efficiently.

A data structure is the concrete implementation of an ADT.

ADT vs Data Structure β€” Clear Distinction

Β  ADT Data Structure
Nature Abstract / Logical Concrete / Physical
Defines What operations are possible How operations are implemented
Implementation Independent of programming language Language-specific code
Example β€œList” β€” supports insert, delete, search β€œArray-based list” or β€œLinked list”

Classification of Data Structures

Data Structures
β”œβ”€β”€ Primitive
β”‚   β”œβ”€β”€ int
β”‚   β”œβ”€β”€ float
β”‚   β”œβ”€β”€ char
β”‚   └── double
β”‚
└── Non-Primitive
    β”œβ”€β”€ Linear
    β”‚   β”œβ”€β”€ Arrays
    β”‚   β”œβ”€β”€ Linked Lists
    β”‚   β”œβ”€β”€ Stacks
    β”‚   └── Queues
    β”‚
    └── Non-Linear
        β”œβ”€β”€ Trees
        β”œβ”€β”€ Graphs
        └── Hash Tables

1.6 Operations on Data Structures

Regardless of the specific data structure, the following fundamental operations can be performed:

Operation Description Example
Traversing Accessing each element exactly once in a systematic order Printing all elements of an array
Searching Finding the location of a given element Binary search in a sorted array
Insertion Adding a new element to the structure Inserting a node in a linked list
Deletion Removing an existing element from the structure Deleting from a BST
Sorting Arranging elements in a specific order (ascending/descending) Quick sort on an array
Merging Combining two sorted structures into one sorted structure Merging two sorted linked lists

Additional operations for specific structures:

Operation Applies To Meaning
Overflow Stack, Queue (array-based) Attempting to insert when the structure is full
Underflow Stack, Queue Attempting to remove when the structure is empty
Push / Pop Stack Insert at top / Remove from top
Enqueue / Dequeue Queue Insert at rear / Remove from front

Key Insight: Every data structure is ultimately about providing efficient implementations of these operations. The choice of data structure determines which operations are fast ($O(1)$ or $O(\log n)$) and which are slow ($O(n)$).

Operation Array Linked List BST (balanced) Hash Table
Access by index $O(1)$ $O(n)$ $O(\log n)$ β€”
Search $O(n)$ $O(n)$ $O(\log n)$ $O(1)$ avg
Insertion $O(n)$ $O(1)$* $O(\log n)$ $O(1)$ avg
Deletion $O(n)$ $O(1)$* $O(\log n)$ $O(1)$ avg

* $O(1)$ if position/pointer is known; $O(n)$ if searching is needed first.


1.7 Applications of Data Structures

Data Structure Real-World Applications
Arrays Image pixels, lookup tables, matrix operations, storing sensor data
Linked Lists Music playlists, undo/redo in editors, memory allocation
Stacks Browser back button, expression evaluation, function call management, undo operation
Queues Printer job scheduling, CPU task scheduling, BFS traversal, ticket booking
Trees File systems, HTML DOM, database indexing (B-trees), decision making
Graphs Social networks, Google Maps (shortest path), web page ranking, network routing
Hash Tables Dictionaries, caching (DNS lookup), database indexing, symbol tables in compilers

Why does the choice of data structure matter?
The right data structure can reduce the time complexity of operations dramatically. For example:

  • Searching in an unsorted array: $O(n)$
  • Searching in a sorted array (binary search): $O(\log n)$
  • Searching in a hash table: $O(1)$ average

Choosing the right structure is one of the most important decisions in software engineering.


1.8 Static vs Dynamic Data Structures

Static Data Structure: Memory size is fixed and allocated at compile time. Cannot grow or shrink during execution.

Dynamic Data Structure: Memory size is flexible and allocated at runtime. Can grow or shrink as needed.

Feature Static (e.g., Array) Dynamic (e.g., Linked List)
Memory Allocation Compile time Runtime
Size Fixed (must be known in advance) Flexible (grows/shrinks)
Memory Utilization May waste memory (if over-sized) Efficient (allocates as needed)
Access Speed Fast β€” direct/random access $O(1)$ Slower β€” sequential access $O(n)$
Insertion/Deletion Expensive β€” shifting required $O(n)$ Efficient β€” pointer adjustment $O(1)$
Memory Location Contiguous (one block) Non-contiguous (scattered)
Example (C) int arr[100]; malloc(sizeof(struct Node))

Example β€” Static:

int marks[50];  // fixed size array β€” always 50 elements
// If only 10 students? 40 slots wasted!
// If 60 students? Cannot accommodate!

πŸ” Why This Logic: The size 50 is hardcoded at compile time. The compiler reserves exactly 50 Γ— sizeof(int) bytes on the stack. This memory cannot grow or shrink. If you need more, you must recompile with a larger number β€” hence β€œstatic.”

Example β€” Dynamic:

struct Node {
    int data;
    struct Node* next;
};
// Create nodes as needed β€” no waste, no overflow
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

πŸ” Why This Logic:

  • struct Node β€” Defines a self-referential structure: each node holds data and a pointer to the next node. This is the building block of linked lists.
  • struct Node* next β€” The pointer that β€œchains” nodes together. It stores the address of the next node, not the node itself. This allows nodes to be scattered in memory (non-contiguous).
  • malloc(sizeof(struct Node)) β€” Allocates exactly enough bytes for ONE node on the heap at runtime. We can call this repeatedly to add as many nodes as memory allows β€” hence β€œdynamic.”

1.9 Linear vs Non-Linear Data Structures

Linear Data Structure: Elements are arranged in a sequential (one-after-another) manner. Each element has at most one predecessor and one successor (except the first and last elements).

Non-Linear Data Structure: Elements are arranged in a hierarchical or network manner. One element can be connected to multiple elements.

Feature Linear Non-Linear
Arrangement Sequential / One-dimensional Hierarchical / Multi-dimensional
Traversal Single pass (one run covers all) Multiple passes may be needed
Memory Utilization Less efficient for complex relationships Better for complex relationships
Implementation Simpler More complex
Levels All data on a single level Data on multiple levels
Examples Array, Linked List, Stack, Queue Tree, Graph, Heap

Visual Comparison

LINEAR (Array):
β”Œβ”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”
β”‚ 1 β”‚ 2 β”‚ 3 β”‚ 4 β”‚ 5 β”‚  ← one-after-another
β””β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”˜

LINEAR (Linked List):
[1] β†’ [2] β†’ [3] β†’ [4] β†’ [5] β†’ NULL

NON-LINEAR (Tree):
        [1]
       /   \
     [2]   [3]
    /   \     \
  [4]   [5]   [6]

NON-LINEAR (Graph):
  [1] --- [2]
   |  \    |
   |   \   |
  [3] --- [4]

Practice Questions β€” Data Structures Basics

Q1. Define the following: (a) Data (b) Data Object (c) Data Type (d) Data Structure (e) ADT.

Q2. Differentiate between a Data Type and an Abstract Data Type with an example.

Q3. Give the ADT definition for a Queue. Specify data, operations, and error conditions.

Q4. Classify data structures with a neat diagram. Give two examples of each category.

Q5. Differentiate between static and dynamic data structures. When would you prefer one over the other?

Q6. Differentiate between linear and non-linear data structures. Give three examples each.

Q7. Explain the relationship: Data β†’ Data Type β†’ Data Structure β†’ ADT.

Q8. Why is choosing the right data structure important? Give a real-world example where a poor choice leads to inefficiency.

Q9. Define ADT for a β€œSet” with operations: union, intersection, difference, membership, insert, delete.

Q10. Match the application with the most suitable data structure: (a) Browser history β€” (b) File system β€” (c) Social network β€” (d) Printer queue β€” (e) Undo feature
Options: Stack, Queue, Tree, Graph, Stack


2 β€” Introduction to Algorithms


2.1 What is an Algorithm?

Definition: An Algorithm is a finite, well-defined sequence of computational steps that transforms input into output to solve a specific problem.

The word β€œalgorithm” comes from the name of the Persian mathematician Muhammad ibn Musa al-Khwarizmi (9th century).

Properties of an Algorithm

Every algorithm must satisfy:

  1. Input β€” Zero or more well-defined inputs
  2. Output β€” At least one well-defined output
  3. Finiteness β€” Must terminate after a finite number of steps
  4. Definiteness β€” Each step must be precisely and unambiguously defined
  5. Effectiveness β€” Each step must be basic enough to be carried out (in principle) by a person with pencil and paper

Example β€” Algorithm to find the maximum of three numbers:

Algorithm: FindMax(a, b, c)
Input: Three numbers a, b, c
Output: The largest of the three

Step 1: Set max = a
Step 2: If b > max, then set max = b
Step 3: If c > max, then set max = c
Step 4: Return max

βœ… Finite β€” exactly 4 steps
βœ… Definite β€” each step is clear
βœ… Has input (a, b, c) and output (max)
βœ… Effective β€” only comparisons and assignments

Not an algorithm:

Step 1: Start
Step 2: Print all prime numbers
Step 3: Stop

❌ This is NOT finite β€” there are infinitely many primes!


2.2 Characteristics of an Algorithm

Characteristic Description Example
Input External data supplied before the algorithm starts Array of numbers to sort
Output Result produced after algorithm finishes Sorted array
Finiteness Must terminate after a finite number of steps A loop must have a termination condition
Definiteness Each instruction is clear and unambiguous β€œSet x = a + b” (not β€œcompute something”)
Effectiveness Each step is feasible and performable Basic arithmetic, comparisons β€” not β€œfind x such that xΒ² = -1 in reals”
Generality Should work for all valid inputs, not just specific cases Sorting algorithm should sort any array, not just [3,1,2]
Independence The logic should not depend on a specific programming language Can be expressed in pseudocode, flowchart, or any language

2.3 Algorithm Design Tools β€” Flowcharts

Definition: A Flowchart is a graphical (pictorial) representation of an algorithm using standardized symbols connected by arrows (flow lines).

Standard Flowchart Symbols

Symbol Shape Purpose
Terminal Oval / Rounded Rectangle Start or End of the algorithm
Process Rectangle Computation, assignment, operation
Decision Diamond Conditional branching (Yes/No or True/False)
Input/Output Parallelogram Read input or display output
Flow Lines Arrows Direction of flow
Connector Small circle Connect different parts of flowchart
Predefined Process Rectangle with double sides Calling a subroutine / function

Advantages of Flowcharts

  • Easy to understand visually
  • Useful for communicating logic to non-programmers
  • Helps identify logical errors before coding

Disadvantages of Flowcharts

  • Can become messy for complex algorithms
  • Difficult to modify once drawn
  • No standard for showing data structures

Example β€” Flowchart logic for finding the largest of two numbers:

[START]
   ↓
[Read a, b]
   ↓
<Is a > b?> ──Yes──→ [Print a]
   β”‚                      ↓
   No                  [STOP]
   ↓
[Print b]
   ↓
[STOP]

2.4 Algorithm Design Tools β€” Pseudo Code

Definition: Pseudo code is an informal, high-level description of an algorithm that uses structured English mixed with programming-like constructs. It is not tied to any specific programming language.

Common Pseudo Code Conventions

Construct Syntax
Assignment SET x = value or x ← value
Input READ x or INPUT x
Output PRINT x or OUTPUT x
Condition IF condition THEN ... ELSE ... ENDIF
Loop (while) WHILE condition DO ... ENDWHILE
Loop (for) FOR i = start TO end DO ... ENDFOR
Function FUNCTION name(params) ... RETURN value

Example β€” Pseudo code for Linear Search:

ALGORITHM LinearSearch(A, n, key)
// Input:  Array A of n elements, search key
// Output: Index of key in A, or -1 if not found

BEGIN
    FOR i = 0 TO n-1 DO
        IF A[i] == key THEN
            RETURN i
        ENDIF
    ENDFOR
    RETURN -1
END

Example β€” Pseudo code for finding Factorial:

ALGORITHM Factorial(n)
// Input:  Non-negative integer n
// Output: n! (factorial of n)

BEGIN
    SET result = 1
    FOR i = 1 TO n DO
        SET result = result * i
    ENDFOR
    RETURN result
END

2.5 Algorithm Notations

When writing algorithms formally, the following notation conventions are commonly used:

Algorithm Header

Algorithm: name(parameters)
Input: Description of inputs
Output: Description of outputs

Conditions and Selection

IF condition THEN
    statements
ELSE IF condition THEN
    statements
ELSE
    statements
ENDIF

Loops

// Counted loop
FOR variable = start TO end [STEP increment] DO
    statements
ENDFOR

// Conditional loop
WHILE condition DO
    statements
ENDWHILE

// Post-test loop
REPEAT
    statements
UNTIL condition

Procedures and Sub-Algorithms

PROCEDURE name(parameters)
    statements
END PROCEDURE

CALL name(arguments)

Flowchart vs Pseudo Code β€” When to Use Which?

Aspect Flowchart Pseudo Code
Best for Simple algorithms, visual learners Complex algorithms, developers
Readability Very high for simple logic High for all levels of complexity
Modifiability Hard to edit Easy to edit
Detail Level Low (can hide complexity) High (closer to actual code)
Used in Documentation, presentations Design phase, exam answers

Practice Questions β€” Algorithms

Q1. Define an algorithm. List and explain its five essential characteristics.

Q2. Write the pseudo code for finding the GCD (Greatest Common Divisor) of two numbers using the Euclidean algorithm.

Q3. Draw a flowchart to check whether a given number is prime or not.

Q4. Write pseudo code to reverse an array of $n$ elements.

Q5. Explain the difference between a flowchart and pseudo code. When would you prefer each?

Q6. Write an algorithm (in pseudo code) to find the second largest element in an array.

Q7. Identify which of the following are valid algorithms and why:

  • (a) β€œKeep printing 1 forever”
  • (b) β€œTake two numbers, add them, print the result”
  • (c) β€œSort the array somehow”

Q8. Write pseudo code for the Tower of Hanoi problem with $n$ disks.

Q9. Draw a flowchart for binary search on a sorted array.

Q10. Write an algorithm using proper header, purpose, conditions, and loop notations to find all even numbers in an array.


3 β€” Algorithm Analysis


3.1 Why Analyze Algorithms?

When we have multiple algorithms to solve the same problem, we need a way to compare them and pick the best one. We don’t compare them by running them on specific inputs (that’s empirical analysis); instead, we use theoretical analysis that works for ALL inputs.

Two Key Resources We Measure:

  1. Time β€” How long does the algorithm take? (measured in number of operations, not seconds)
  2. Space β€” How much extra memory does it need? (measured in units of memory)

Why not measure actual running time (seconds)?
Because running time depends on:

  • Hardware (CPU speed, cache size)
  • Operating system
  • Programming language
  • Other programs running simultaneously

Instead, we count the number of fundamental operations (comparisons, assignments, arithmetic) as a function of input size $n$.


3.2 Time Complexity

Definition: Time complexity of an algorithm is a function $T(n)$ that describes the number of fundamental operations performed as a function of the input size $n$.

What Counts as a β€œFundamental Operation”?

Operation Type Examples
Comparisons if (a > b), while (i < n)
Assignments x = 5, max = a
Arithmetic a + b, i++, n / 2
Array Access A[i], A[i] = x
Function Calls swap(a, b)

How to Count Operations β€” Simple Rules

Code Pattern Number of Operations
Single statement $O(1)$ β€” constant
Simple loop (for i = 1 to n) $O(n)$ β€” linear
Nested loops (2 levels, both up to $n$) $O(n^2)$ β€” quadratic
Nested loops (3 levels) $O(n^3)$ β€” cubic
Loop where variable doubles (i *= 2) $O(\log n)$ β€” logarithmic
Loop Γ— halving loop $O(n \log n)$ β€” linearithmic

3.3 Space Complexity

Definition: Space complexity of an algorithm is the total amount of memory it requires, expressed as a function of the input size $n$.

Components of Space Complexity

\[S(n) = \text{Fixed Part} + \text{Variable Part}\]
Component Description Examples
Fixed Part Memory that doesn’t depend on input size Code storage, simple variables, constants
Variable Part Memory that depends on input size Arrays of size $n$, recursion stack depth, temporary storage

Example 1 β€” Constant space $O(1)$:

int sum(int a, int b) {
    return a + b;  // Only uses fixed variables
}
// Space = O(1) β€” no extra data structures

πŸ” Line-by-Line Logic:

  • int sum(int a, int b) β€” Takes two parameters stored in the function’s stack frame. A fixed number of variables regardless of their values.
  • return a + b; β€” One addition, one return. No arrays, no malloc, no recursion. The only memory used is for a, b, and the return value β€” all fixed-size.
  • Why $O(1)$ space? β€” Memory consumption does NOT depend on input values. Whether a = 5 or a = 1000000, the same number of variables is used. This is the definition of constant space.

Example 2 β€” Linear space $O(n)$:

int* copyArray(int arr[], int n) {
    int* copy = (int*)malloc(n * sizeof(int));  // n extra elements
    for (int i = 0; i < n; i++)
        copy[i] = arr[i];
    return copy;
}
// Space = O(n) β€” creates array of size n

πŸ” Line-by-Line Logic:

  • int* copyArray(int arr[], int n) β€” Returns a pointer to a new array. The original arr[] is passed by reference (pointer to first element), so no copy of the input is made here.
  • int* copy = (int*)malloc(n * sizeof(int)) β€” The KEY line. malloc allocates n Γ— sizeof(int) bytes on the heap at runtime. This single line is responsible for the $O(n)$ space. We multiply by sizeof(int) because malloc works in bytes, and (int*) casts the raw void* to an integer pointer.
  • for (int i = 0; i < n; i++) copy[i] = arr[i]; β€” Copies elements one-by-one. The loop variable i is $O(1)$ extra space β€” it doesn’t change the overall $O(n)$ analysis.
  • return copy; β€” Returns the heap pointer to the caller. The caller must eventually call free(copy) to avoid a memory leak.
  • Why $O(n)$ space? β€” We created a brand-new array of n elements. The extra memory grows linearly with input size.

Example 3 β€” Recursion space $O(n)$:

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);  // n function calls on stack
}
// Space = O(n) β€” recursion depth is n

πŸ” Line-by-Line Logic:

  • if (n <= 1) return 1; β€” Base case: $0! = 1$ and $1! = 1$. Without this, the recursion would never stop β†’ stack overflow. Every recursive function MUST have a base case.
  • return n * factorial(n - 1); β€” Recursive case: uses the mathematical definition $n! = n \times (n-1)!$. Each call creates a NEW stack frame containing its own copy of n and a return address.
  • Why $O(n)$ space? β€” When we call factorial(5), the call stack builds up: factorial(5) β†’ factorial(4) β†’ factorial(3) β†’ factorial(2) β†’ factorial(1). At peak, there are 5 stack frames simultaneously alive (none can return until the one it called returns). For input n, that’s n frames β†’ $O(n)$ space.
  • Contrast with iterative version: A loop for (int i = 1; i <= n; i++) result *= i; uses $O(1)$ space because it reuses the same variables. The recursive version trades space efficiency for code elegance.

3.4 Asymptotic Notations β€” Big O, Omega, Theta

Asymptotic notations describe the growth rate of an algorithm’s time/space as $n \to \infty$. They ignore constants and lower-order terms.

3.4.1 Big O Notation β€” $O(f(n))$ β€” Upper Bound

Definition: $T(n) = O(f(n))$ if there exist positive constants $c$ and $n_0$ such that:

\[T(n) \leq c \cdot f(n) \quad \text{for all } n \geq n_0\]

Meaning: $f(n)$ is an upper bound on $T(n)$ for large $n$. The algorithm takes at most this much time.

Example: If $T(n) = 3n^2 + 5n + 2$, then $T(n) = O(n^2)$.

Proof: Choose $c = 4$ and $n_0 = 6$:
For $n \geq 6$: $3n^2 + 5n + 2 \leq 3n^2 + n^2 = 4n^2$ βœ”

We say the algorithm has β€œorder $n^2$” or β€œquadratic time.”

3.4.2 Omega Notation β€” $\Omega(f(n))$ β€” Lower Bound

Definition: $T(n) = \Omega(f(n))$ if there exist positive constants $c$ and $n_0$ such that:

\[T(n) \geq c \cdot f(n) \quad \text{for all } n \geq n_0\]

Meaning: $f(n)$ is a lower bound on $T(n)$. The algorithm takes at least this much time.

Example: If $T(n) = 3n^2 + 5n + 2$, then $T(n) = \Omega(n^2)$.

Proof: Choose $c = 3$ and $n_0 = 1$:
For $n \geq 1$: $3n^2 + 5n + 2 \geq 3n^2$ βœ”

3.4.3 Theta Notation β€” $\Theta(f(n))$ β€” Tight Bound

Definition: $T(n) = \Theta(f(n))$ if there exist positive constants $c_1$, $c_2$, and $n_0$ such that:

\[c_1 \cdot f(n) \leq T(n) \leq c_2 \cdot f(n) \quad \text{for all } n \geq n_0\]

Meaning: $f(n)$ is a tight bound β€” both upper and lower. This is the most precise characterization.

Example: $T(n) = 3n^2 + 5n + 2 = \Theta(n^2)$

Because: $T(n) = O(n^2)$ AND $T(n) = \Omega(n^2)$, therefore $T(n) = \Theta(n^2)$.

Summary Table

Notation Symbol Bound Type Analogy
Big O $O(f(n))$ Upper bound (≀) β€œAt most” / ceiling
Omega $\Omega(f(n))$ Lower bound (β‰₯) β€œAt least” / floor
Theta $\Theta(f(n))$ Tight bound (=) β€œExactly” / sandwich

Visual Analogy:

  • $O(n^2)$ β†’ β€œI will finish in AT MOST $n^2$ steps” (could be faster)
  • $\Omega(n^2)$ β†’ β€œI will need AT LEAST $n^2$ steps” (could be slower)
  • $\Theta(n^2)$ β†’ β€œI will take EXACTLY around $n^2$ steps” (tight)

In practice: Big O is used most frequently because we care about the worst-case guarantee.


3.5 Best, Average and Worst Case Analysis

For the same algorithm, different inputs can cause different running times:

Case Definition When It Occurs
Best Case Minimum number of operations Input is in the most favorable configuration
Worst Case Maximum number of operations Input is in the least favorable configuration
Average Case Expected number of operations Averaged over all possible inputs (with assumed probability distribution)

Example β€” Linear Search in an array of $n$ elements:

int linearSearch(int arr[], int n, int key) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == key)
            return i;
    }
    return -1;
}

πŸ” Line-by-Line Logic:

  • int linearSearch(int arr[], int n, int key) β€” Takes the array, its size n, and the target key. We pass n separately because C arrays don’t carry their size.
  • for (int i = 0; i < n; i++) β€” Scans every element from index 0 to n-1. This is the brute-force approach β€” no assumptions about data ordering.
  • if (arr[i] == key) return i; β€” Early exit: the moment we find the key, we return its index immediately. This is why the best case is $O(1)$ β€” if the key is at index 0, we check once and leave.
  • return -1; β€” If the loop finishes without finding key, we return -1 (a sentinel value meaning β€œnot found”). This is the worst case β€” we checked ALL n elements.
  • Why linear? β€” We have no way to skip elements. Unlike binary search (which requires sorted data), linear search works on any array but must potentially examine every element β†’ $O(n)$.
Case Scenario Comparisons Complexity
Best Key is the first element 1 $O(1)$
Worst Key is the last element or not present $n$ $O(n)$
Average Key is equally likely anywhere $\frac{n}{2}$ $O(n)$

Example β€” Insertion Sort:

Case Scenario Comparisons Complexity
Best Array is already sorted $n - 1$ $O(n)$
Worst Array is sorted in reverse order $\frac{n(n-1)}{2}$ $O(n^2)$
Average Random order $\frac{n(n-1)}{4}$ $O(n^2)$

Common Misconception: Big O is NOT the same as worst case!

  • Big O, Omega, Theta are mathematical bounds on a function.
  • Best, Average, Worst are input scenarios.

You can have the β€œBig O of the best case” or the β€œOmega of the worst case.” However, in practice:

  • We usually say β€œBig O = Worst Case” because we use Big O to express the worst-case bound.

3.6 Common Time Complexities β€” Comparison Chart

Listed from fastest (best) to slowest (worst):

Complexity Name $n = 10$ $n = 100$ $n = 1000$ Example Algorithm
$O(1)$ Constant 1 1 1 Array access, hash table lookup
$O(\log n)$ Logarithmic 3 7 10 Binary search
$O(n)$ Linear 10 100 1,000 Linear search, traversal
$O(n \log n)$ Linearithmic 33 664 9,966 Merge sort, Quick sort (avg)
$O(n^2)$ Quadratic 100 10,000 1,000,000 Bubble sort, Insertion sort
$O(n^3)$ Cubic 1,000 1,000,000 $10^9$ Matrix multiplication (naive)
$O(2^n)$ Exponential 1,024 $10^{30}$ $10^{301}$ Subsets, brute-force TSP
$O(n!)$ Factorial 3,628,800 $10^{157}$ ∞ Permutations, brute-force

Rule of Thumb (for competitive programming / exams):

  • $O(n)$ or $O(n \log n)$ β†’ βœ… works for $n$ up to $10^6$ to $10^7$
  • $O(n^2)$ β†’ ⚠️ works for $n$ up to about $10^4$
  • $O(n^3)$ β†’ works for $n$ up to about $500$
  • $O(2^n)$ β†’ works for $n$ up to about $20$-$25$
  • $O(n!)$ β†’ works for $n$ up to about $10$-$12$

Growth Rate Ordering

\[O(1) < O(\log n) < O(\sqrt{n}) < O(n) < O(n \log n) < O(n^2) < O(n^3) < O(2^n) < O(n!)\]

3.7 How to Calculate Time Complexity β€” Step by Step

Rule 1: Simple Statements β†’ $O(1)$

int a = 5;         // O(1)
int b = a + 3;     // O(1)
printf("%d", b);   // O(1)
// Total: O(1) + O(1) + O(1) = O(1)

πŸ” Why $O(1)$? Each line is a single operation (assignment, addition, print). No loops, no recursion. The number of operations is fixed regardless of any input size. Three constant-time operations in sequence is still constant time.

Rule 2: Sequential Statements β†’ Add

// Block A: O(n)
for (int i = 0; i < n; i++) { ... }

// Block B: O(nΒ²)
for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++) { ... }

// Total: O(n) + O(nΒ²) = O(nΒ²)  (keep the dominant term)

πŸ” Why add, then keep dominant? The blocks run sequentially (one after the other, NOT nested). Total = $O(n) + O(n^2)$. For large $n$, the $n^2$ term dominates β€” the $O(n)$ block becomes negligible. We always keep only the fastest-growing term.

Rule 3: Simple Loop β†’ $O(n)$

for (int i = 0; i < n; i++) {   // loop runs n times
    // O(1) work inside
}
// Total: O(n)

πŸ” Why $O(n)$? The loop runs from 0 to n-1 β€” exactly n iterations. Each iteration does $O(1)$ work. By the summation rule: $n \times O(1) = O(n)$. This is the most fundamental loop complexity pattern.

Rule 4: Nested Loops β†’ Multiply

for (int i = 0; i < n; i++) {       // outer: n times
    for (int j = 0; j < n; j++) {   // inner: n times
        // O(1) work
    }
}
// Total: O(n Γ— n) = O(nΒ²)

πŸ” Why multiply? For EACH of the n outer iterations, the inner loop runs n times. The inner loop is entirely contained within the outer, so total work = $n \times n = n^2$. This is different from sequential blocks (Rule 2) where we ADD.

Rule 5: Loop Variable Doubles/Halves β†’ $O(\log n)$

for (int i = 1; i < n; i *= 2) {   // i = 1, 2, 4, 8, ..., n
    // O(1) work                     // iterations = logβ‚‚(n)
}
// Total: O(log n)

πŸ” Why $O(\log n)$? i doubles each step: $1, 2, 4, 8, …, 2^k$. After $k$ steps, $2^k \geq n$, so $k = \lceil \log_2 n \rceil$. Doubling is the inverse of halving β€” both give logarithmic iteration counts. This is the pattern behind binary search.

for (int i = n; i > 0; i /= 2) {   // i = n, n/2, n/4, ..., 1
    // O(1) work                     // iterations = logβ‚‚(n)
}
// Total: O(log n)

πŸ” Why same as doubling? Halving from n down to 1 takes the same number of steps as doubling from 1 up to n. Both are $\log_2 n$ steps. Halving appears in binary search (mid = (lo + hi) / 2), divide-and-conquer algorithms, and tree traversals.

Rule 6: Linear Loop Γ— Logarithmic Loop β†’ $O(n \log n)$

for (int i = 0; i < n; i++) {         // O(n)
    for (int j = 1; j < n; j *= 2) {  // O(log n)
        // O(1) work
    }
}
// Total: O(n log n)

πŸ” Why $O(n \log n)$? The outer loop is linear ($n$ iterations). The inner loop doubles j each time, giving $\log n$ iterations. Since the inner is nested inside the outer, we multiply: $n \times \log n$. This complexity class is the sweet spot for efficient sorting algorithms (merge sort, heap sort).

Rule 7: If-Else β†’ Take the Worse Branch

if (condition) {
    // Block A: O(n)
} else {
    // Block B: O(nΒ²)
}
// Total: O(nΒ²)  (worst case)

πŸ” Why take the worse branch? In worst-case analysis, we assume the most expensive path executes. We don’t know which branch will run at compile time, so we must prepare for the costlier one. If Block A is $O(n)$ and Block B is $O(n^2)$, the worst case is $O(n^2)$.

Rule 8: Drop Constants and Lower-Order Terms

Before Simplification After (Big O)
$5n^2 + 3n + 7$ $O(n^2)$
$100n + 500$ $O(n)$
$2^n + n^3$ $O(2^n)$
$3 \log n + 2n$ $O(n)$

3.8 Worked Examples β€” Complexity Analysis

Example 1 β€” Analyze this code:

for (int i = 0; i < n; i++) {
    for (int j = 0; j < i; j++) {
        printf("*");
    }
}

πŸ” Why This Logic:

  • j < i (not j < n) β€” This is the critical detail. The inner loop runs a variable number of times that depends on the outer loop’s current value of i. When i = 0, inner runs 0 times; when i = 5, inner runs 5 times.
  • Why does this produce $\frac{n(n-1)}{2}$? β€” The total work is $0 + 1 + 2 + … + (n-1)$, which is the triangular number formula. This pattern appears everywhere: bubble sort’s comparisons, selection sort’s comparisons, printing triangle patterns.
  • Still $O(n^2)$ β€” Even though it’s roughly half of $n^2$, constants don’t matter in Big O. $\frac{n^2}{2}$ and $n^2$ are both $O(n^2)$.

Analysis:

  • When $i = 0$: inner loop runs 0 times
  • When $i = 1$: inner loop runs 1 time
  • When $i = 2$: inner loop runs 2 times
  • …
  • When $i = n-1$: inner loop runs $n-1$ times

Total operations = $0 + 1 + 2 + … + (n-1) = \frac{n(n-1)}{2} = \frac{n^2 - n}{2}$

\[T(n) = O(n^2)\]

Example 2 β€” Analyze this code:

for (int i = 1; i <= n; i *= 2) {
    for (int j = 0; j < n; j++) {
        printf("*");
    }
}

πŸ” Why This Logic:

  • i *= 2 β€” The outer loop doubles i each iteration: $1, 2, 4, 8, 16, …$. It reaches n after $\log_2 n$ steps (because $2^k = n \implies k = \log_2 n$). This is the hallmark of logarithmic behavior.
  • j < n β€” The inner loop ALWAYS runs exactly n times, regardless of i. It doesn’t depend on the outer variable (unlike Example 1).
  • Why $O(n \log n)$? β€” The outer loop runs $\log n$ times, the inner runs $n$ times per outer iteration. By the multiplication rule: $\log n \times n = n \log n$. This is the same complexity as merge sort and quick sort (average case).

Analysis:

  • Outer loop: $i = 1, 2, 4, 8, …, n$ β†’ runs $\log_2 n$ times
  • Inner loop: always runs $n$ times

Total = $n \times \log n$

\[T(n) = O(n \log n)\]

Example 3 β€” Analyze this code:

int i = 1, s = 0;
while (s < n) {
    s = s + i;
    i++;
}

πŸ” Why This Logic:

  • s = s + i; i++; β€” In each iteration, we add an increasing value to s. First we add 1, then 2, then 3, etc. So after $k$ iterations, $s = 1 + 2 + 3 + … + k = \frac{k(k+1)}{2}$.
  • Why $O(\sqrt{n})$? β€” The loop stops when $s \geq n$, i.e., $\frac{k(k+1)}{2} \geq n$, so $k \approx \sqrt{2n}$. Dropping the constant: $O(\sqrt{n})$. This is between $O(\log n)$ and $O(n)$ in the growth hierarchy.
  • Key insight: Any loop where the β€œprogress per step” increases (here: $+1, +2, +3, …$) tends to finish faster than linear. The accumulating sum reaches n in $\sqrt{n}$ steps rather than n steps.

Analysis:

  • After $k$ iterations: $s = 1 + 2 + 3 + … + k = \frac{k(k+1)}{2}$
  • Loop stops when $s \geq n$, i.e., $\frac{k(k+1)}{2} \geq n$
  • So $k^2 \approx 2n$, meaning $k \approx \sqrt{2n}$
\[T(n) = O(\sqrt{n})\]

Example 4 β€” Analyze this recursive function:

void fun(int n) {
    if (n <= 0) return;
    printf("%d ", n);
    fun(n - 1);
}

πŸ” Why This Logic:

  • if (n <= 0) return; β€” Base case. Stops the recursion when n hits 0. Without this: infinite recursion β†’ stack overflow.
  • printf("%d ", n); β€” $O(1)$ work done before the recursive call. This means it prints in descending order: $n, n-1, …, 1$.
  • fun(n - 1); β€” Tail recursion: the recursive call is the LAST statement. Each call reduces n by exactly 1, so there are exactly n calls before hitting the base case.
  • Why $O(n)$? β€” Each call does $O(1)$ work + makes exactly ONE recursive call with n-1. The recurrence $T(n) = T(n-1) + O(1)$ unrolls to $T(n) = n \cdot O(1) = O(n)$. This is the simplest form of linear recursion.

Analysis:

  • Recurrence: $T(n) = T(n-1) + O(1)$, with $T(0) = O(1)$
  • Unrolling: $T(n) = T(n-1) + 1 = T(n-2) + 2 = … = T(0) + n = n + 1$
\[T(n) = O(n)\]

Example 5 β€” Analyze this recursive function:

void fun(int n) {
    if (n <= 1) return;
    fun(n / 2);
    fun(n / 2);
}

πŸ” Why This Logic:

  • if (n <= 1) return; β€” Base case when the problem size is trivially small.
  • Two calls to fun(n / 2) β€” Each call halves the input, and there are two such calls. This creates a binary tree of recursive calls.
  • Why $O(n)$ and not $O(\log n)$? β€” A single call to fun(n/2) would give $O(\log n)$. But with TWO calls, the recursion tree has $1 + 2 + 4 + … + n = 2n - 1$ nodes (a complete binary tree of height $\log n$). The total work across ALL nodes is $O(n)$.
  • Master Theorem: $T(n) = 2T(n/2) + O(1)$ falls into Case 1: $a = 2, b = 2$, so $\log_b a = 1 > 0$ (exponent of $f(n) = n^0$). Result: $T(n) = \Theta(n^{\log_b a}) = \Theta(n)$.
  • Key lesson: Branching factor matters enormously. One recursive call β†’ $O(\log n)$. Two recursive calls β†’ $O(n)$. Three calls of fun(n/2) would give $O(n^{\log_2 3}) \approx O(n^{1.58})$!

Analysis:

  • Recurrence: $T(n) = 2T(n/2) + O(1)$
  • By Master theorem (Case 1): $a = 2, b = 2, f(n) = O(1)$
  • $\log_b a = \log_2 2 = 1$, and $f(n) = O(n^0)$ where $0 < 1$
\[T(n) = O(n)\]

Practice Questions β€” Algorithm Analysis

Q1. Define time complexity and space complexity. Why is time complexity more commonly discussed?

Q2. Explain Big O, Omega, and Theta notations with formal definitions and examples.

Q3. Prove that $5n^2 + 3n + 100 = O(n^2)$. Find suitable values of $c$ and $n_0$.

Q4. Prove that $n^2 \neq O(n)$. (Hint: assume it is and derive a contradiction.)

Q5. Arrange the following in increasing order of growth rate: $O(n \log n)$, $O(n^2)$, $O(1)$, $O(n)$, $O(2^n)$, $O(\log n)$, $O(n!)$, $O(n^3)$

Q6. Find the time complexity of:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j += i) {
        printf("*");
    }
}

πŸ” Why This Logic:

  • j += i β€” This is the KEY detail. The inner loop’s step size equals the outer variable i. When i = 1, j steps by 1 β†’ n iterations. When i = 2, j steps by 2 β†’ $n/2$ iterations. When i = 3, steps by 3 β†’ $n/3$ iterations.
  • Total work = $\frac{n}{1} + \frac{n}{2} + \frac{n}{3} + … + \frac{n}{n} = n \cdot (1 + \frac{1}{2} + \frac{1}{3} + … + \frac{1}{n}) = n \cdot H_n$
  • $H_n$ is the Harmonic series, which grows as $\ln n + \gamma$ (where $\gamma \approx 0.5772$ is the Euler-Mascheroni constant). Therefore total = $n \cdot O(\log n) = O(n \log n)$.
  • Why it’s not $O(n^2)$? β€” If j always stepped by 1, the inner loop would always run n times β†’ $n \times n = O(n^2)$. But the increasing step size makes later iterations dramatically cheaper.

(Hint: This is the Harmonic series β€” answer is $O(n \log n)$)

Q7. Find the time complexity of:

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        for (int k = 0; k < n; k++) {
            // O(1) work
        }
    }
}

πŸ” Why This Logic:

  • Three independent loops, each running exactly n times. By the multiplication rule: $n \times n \times n = n^3$.
  • Why $O(n^3)$? β€” None of the loop bounds depend on the other loop variables (all use < n). This is the simplest form of cubic complexity. Practical example: naive matrix multiplication uses this exact triple-loop pattern.
  • Feasibility: For $n = 1000$, this makes $10^9$ operations β€” roughly 1 second on a modern CPU. For $n = 500$, it’s $1.25 \times 10^8$ β€” still feasible.

Q8. What is the time and space complexity of this recursive Fibonacci:

int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

πŸ” Why This Logic:

  • if (n <= 1) return n; β€” Base cases: $F(0) = 0$ and $F(1) = 1$. Handles both in one line since n itself is the answer for $n \leq 1$.
  • return fib(n-1) + fib(n-2); β€” Direct translation of the Fibonacci definition: $F(n) = F(n-1) + F(n-2)$. But this creates a binary tree of calls with MASSIVE redundancy.
  • Time: $O(2^n)$ β€” Each call spawns TWO more calls, and the tree depth is n. The recursion tree has roughly $2^n$ nodes. fib(5) calls fib(4) + fib(3), and fib(4) calls fib(3) + fib(2) β€” notice fib(3) is computed twice. For large n, the repeated work grows explosively.
  • Space: $O(n)$ β€” Despite $2^n$ total calls, at any point only ONE branch is active on the stack. The maximum depth is n (the leftmost path: fib(n) β†’ fib(n-1) β†’ … β†’ fib(1)), so the stack holds at most n frames.
  • Why so slow? β€” The same subproblems are recalculated exponentially many times. This is precisely the problem that dynamic programming (memoization or tabulation) solves, reducing it to $O(n)$.

Q9. Differentiate between Best, Average, and Worst case analysis with examples from Linear Search and Bubble Sort.

Q10. Explain why we drop constants in Big O notation. Is $O(100n)$ the same as $O(n)$?

Q11. Find the time complexity of:

int i = n;
while (i > 0) {
    i = i / 3;
}

πŸ” Why This Logic:

  • i = i / 3 β€” Divides i by 3 each iteration: $n, n/3, n/9, n/27, …, 1$.
  • How many iterations? β€” After $k$ iterations, $i = n / 3^k$. The loop stops when $i = 0$ (integer division), which happens when $3^k > n$, i.e., $k = \lceil \log_3 n \rceil$.
  • Time: $O(\log_3 n) = O(\log n)$ β€” Since $\log_3 n = \frac{\log_2 n}{\log_2 3}$, the base doesn’t matter in Big O (it’s just a constant factor). Dividing by ANY constant $> 1$ gives logarithmic complexity.

Q12. An algorithm takes $T(n) = 3n^2 + 7n \log n + 100$ time. Express this in Big O, Big Omega, and Big Theta notations.


Comprehensive Unit I Practice Set

Short Answer Questions

1. What is the difference between data and information?

2. Define: (a) Data Type (b) Abstract Data Type (c) Data Structure

3. Give two real-world examples each of linear and non-linear data structures.

4. Write the ADT for a β€œBag” (unordered collection that allows duplicates).

5. What are the five characteristics of an algorithm?

6. Differentiate between flowchart and pseudo code.

Analytical Questions

7. Determine the time complexity of each code fragment:

(a)

int sum = 0;
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= i * i; j++)
        sum++;

πŸ” Why This Logic:

  • j <= i * i β€” The inner loop runs $i^2$ times for each value of i. This is more aggressive than j < i (which gives $O(n^2)$).
  • Total work = $\sum_{i=1}^{n} i^2 = 1^2 + 2^2 + 3^2 + … + n^2 = \frac{n(n+1)(2n+1)}{6} \approx \frac{n^3}{3}$
  • Time: $O(n^3)$ β€” The sum of squares formula gives a cubic result. This is a common exam trick: the inner loop bound being $i^2$ (not i or n) changes the complexity class entirely.

(b)

for (int i = n; i >= 1; i /= 2)
    for (int j = 1; j <= n; j++)
        count++;

πŸ” Why This Logic:

  • i /= 2 β€” Outer loop halves i each time: $n, n/2, n/4, …, 1$. That’s $\log_2 n$ iterations.
  • j <= n β€” Inner loop ALWAYS runs n times, regardless of i. It doesn’t depend on the outer variable.
  • Time: $O(n \log n)$ β€” By the multiplication rule: $\log n$ outer iterations $\times$ $n$ inner iterations. Same pattern as Rule 6.

(c)

int i = 0, j = 0;
while (i < n) {
    while (j < n) {
        j++;
    }
    i++;
}

(Careful! j is not reset β€” this is $O(n)$, not $O(n^2)$!)

πŸ” Why This Logic (The Classic Trick):

  • j is declared OUTSIDE both loops and is NEVER reset to 0 inside the outer loop. This is the crucial observation.
  • First iteration of outer loop (i = 0): The inner while (j < n) runs n times, incrementing j from 0 to n.
  • All subsequent iterations (i = 1, 2, ..., n-1): When we re-enter the inner loop, j is already n. The condition j < n is immediately false, so the inner loop runs 0 times.
  • Total inner loop executions: n (only in the first outer iteration) + 0 + 0 + … + 0 = n.
  • Time: $O(n)$ β€” NOT $O(n^2)$! This is the most common exam trap. If j = 0; were placed inside the outer loop (resetting it), THEN it would be $O(n^2)$. Always check whether inner loop variables are reset.

8. Prove that $O(n^2) + O(n) = O(n^2)$.

9. If Algorithm A takes $100n$ operations and Algorithm B takes $n^2$ operations, for what values of $n$ is A better? When does B become better?

10. Write an algorithm (in pseudo code with proper notations) to:

  • (a) Find the sum of digits of a number
  • (b) Check if a string is a palindrome
  • (c) Find all prime numbers up to $n$ (Sieve of Eratosthenes)

Long Answer / Essay Type

11. Explain the classification of data structures with a detailed tree diagram. Give real-life applications of each.

12. Compare and contrast Big O, Big Omega, and Big Theta notations. Illustrate with graphical representations and examples.

13. Explain Best, Average, and Worst case analysis with a detailed example of Insertion Sort, showing the exact number of comparisons in each case.


πŸ“ View Complete Solutions for Unit I β†’

← Back to DSA Index