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
1 β Introduction to Data Structures
- 1.1 What is Data?
- 1.2 Data Objects
- 1.3 Data Types
- 1.4 Abstract Data Type (ADT)
- 1.5 What is a Data Structure?
- 1.6 Operations on Data Structures
- 1.7 Applications of Data Structures
- 1.8 Static vs Dynamic Data Structures
- 1.9 Linear vs Non-Linear Data Structures
- Practice Questions β Data Structures Basics
2 β Introduction to Algorithms
- 2.1 What is an Algorithm?
- 2.2 Characteristics of an Algorithm
- 2.3 Algorithm Design Tools β Flowcharts
- 2.4 Algorithm Design Tools β Pseudo Code
- 2.5 Algorithm Notations
- Practice Questions β Algorithms
3 β Algorithm Analysis
- 3.1 Why Analyze Algorithms?
- 3.2 Time Complexity
- 3.3 Space Complexity
- 3.4 Asymptotic Notations β Big O, Omega, Theta
- 3.5 Best, Average and Worst Case Analysis
- 3.6 Common Time Complexities β Comparison Chart
- 3.7 How to Calculate Time Complexity β Step by Step
- 3.8 Worked Examples β Complexity Analysis
- Practice Questions β Algorithm Analysis
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:
- A set of values (the domain), AND
- 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:
- Data β what kind of data is stored
- Operations β what can be done with the data
- 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
50is hardcoded at compile time. The compiler reserves exactly50 Γ 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 holdsdataand 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:
- Input β Zero or more well-defined inputs
- Output β At least one well-defined output
- Finiteness β Must terminate after a finite number of steps
- Definiteness β Each step must be precisely and unambiguously defined
- 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:
- Time β How long does the algorithm take? (measured in number of operations, not seconds)
- 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, nomalloc, no recursion. The only memory used is fora,b, and the return value β all fixed-size.- Why $O(1)$ space? β Memory consumption does NOT depend on input values. Whether
a = 5ora = 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 originalarr[]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.mallocallocatesn Γ sizeof(int)bytes on the heap at runtime. This single line is responsible for the $O(n)$ space. We multiply bysizeof(int)becausemallocworks in bytes, and(int*)casts the rawvoid*to an integer pointer.for (int i = 0; i < n; i++) copy[i] = arr[i];β Copies elements one-by-one. The loop variableiis $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 callfree(copy)to avoid a memory leak.- Why $O(n)$ space? β We created a brand-new array of
nelements. 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 ofnand 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 inputn, thatβsnframes β $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 sizen, and the targetkey. We passnseparately because C arrays donβt carry their size.for (int i = 0; i < n; i++)β Scans every element from index0ton-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 findingkey, we return-1(a sentinel value meaning βnot foundβ). This is the worst case β we checked ALLnelements.- 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
0ton-1β exactlyniterations. 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
nouter iterations, the inner loop runsntimes. 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)$?
idoubles 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
ndown to 1 takes the same number of steps as doubling from 1 up ton. 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
jeach 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(notj < n) β This is the critical detail. The inner loop runs a variable number of times that depends on the outer loopβs current value ofi. Wheni = 0, inner runs 0 times; wheni = 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 doublesieach iteration: $1, 2, 4, 8, 16, β¦$. It reachesnafter $\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 exactlyntimes, regardless ofi. 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 tos. 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
nin $\sqrt{n}$ steps rather thannsteps.
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}$
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 whennhits 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 reducesnby exactly 1, so there are exactlyncalls 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$
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$
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 variablei. Wheni = 1,jsteps by 1 βniterations. Wheni = 2,jsteps by 2 β $n/2$ iterations. Wheni = 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
jalways stepped by 1, the inner loop would always runntimes β $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
ntimes. 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 sincenitself 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)callsfib(4)+fib(3), andfib(4)callsfib(3)+fib(2)β noticefib(3)is computed twice. For largen, 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 mostnframes.- 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β Dividesiby 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 ofi. This is more aggressive thanj < 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
iorn) 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 halvesieach time: $n, n/2, n/4, β¦, 1$. Thatβs $\log_2 n$ iterations.j <= nβ Inner loop ALWAYS runsntimes, regardless ofi. 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):
jis 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 innerwhile (j < n)runsntimes, incrementingjfrom 0 ton.- All subsequent iterations (
i = 1, 2, ..., n-1): When we re-enter the inner loop,jis alreadyn. The conditionj < nis 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.