Introduction to Algorithm Design Basics
Introduction to Algorithm Design Basics
Rajan Adhikari
November 8, 2025
Outline
The Algorithm: A Formal Definition
Defining the Algorithm: More than Code
The Foundational Properties of Algorithms
A Practical Analogy: Recipes and Heuristics
Illustrative Example: Find the Largest Number
Performance Analysis: The ”Cost” of Computation
Why We Analyze Algorithms
Primary Metrics
Best, Worst, and Average-Case Scenarios
Asymptotic Notations: The Language of Algorithmic Growth
The Need for Asymptotic Analysis
The Notations: O, Ω, Θ
Critical Insight: Clarifying Notations vs. Cases
A Guided Tour of Common Complexity Classes
Common Classes
Amortized Analysis: A Guaranteed Average for Sequences
Defining Amortization
In-Depth Example: The Dynamic Array
A Practical Guide to Computing Algorithmic Complexity
Defining the Algorithm: More than Code
Formal Definition
In mathematics and computer science, an algorithm is a formal, effective method expressed as
a finite sequence of well-defined, mathematically rigorous instructions. It is fundamentally a
specification for performing a computation or solving a class of specific problems.
▶ An algorithm is not a piece of code; rather, it is the abstract, logical method that the code
implements.
▶ This definition carries a critical distinction from a heuristic, which is a problem-solving
approach that is not guaranteed to produce a correct or optimal result.
▶ An algorithm, in contrast, must be correct.
Defining the Algorithm: More than Code
Hierarchical Distinction
▶ The Problem: The abstract task to be accomplished (e.g., ”Given a list of numbers, find
the largest one”).
▶ The Algorithm: A specific, formal method for solving the problem (e.g., ”Assume the
first number is the largest, then iterate...”).
▶ The Program: A concrete implementation of an algorithm in a specific programming
language.
The Foundational Properties of Algorithms
For i = 1 to length(A) - 1:
If A[i] > max_value:
max_value = A[i]
Return max_value
▶ The formal properties, particularly Correctness (from the Output property), are the
foundation for performance analysis.
▶ For the Find-Max algorithm to be correct, its output must be the largest value.
▶ The Adversary Argument: If an algorithm claims to find the maximum but does not
inspect every element (in the worst case), an adversary can hide the true maximum in the
one un-inspected location. The algorithm would then fail.
Lower Bound
Therefore, any correct, deterministic algorithm for the Find-Max problem must inspect all n
elements in the worst case.
▶ This establishes a problem complexity or a lower bound for this task.
▶ The problem itself is Ω(n).
▶ Our algorithm inspects every element once, so it runs in O(n) time.
▶ Because its runtime (O(n)) matches the problem’s lower bound (Ω(n)), this algorithm is
asymptotically optimal.
Why We Analyze Algorithms
Example: Is-Prime(k)
▶ Consider an algorithm Is-Prime(k) that checks if an integer k is prime.
▶ A common mistake is to define the input size as k. An algorithm that performs trial
division from 2 to k would run in O(k) time and be called ”linear.” This is incorrect.
▶ In theoretical computer science, the input size n is the number of bits required to
represent the input.
▶ The number k can be represented in n = log2 k bits.
▶ From this, k = 2n .
▶ The O(k) algorithm is therefore O(2n ) in terms of its true input size n.
▶ It is an exponential-time algorithm, not a linear-time one. This distinction is the basis for
computational complexity theory (e.g., P vs. NP).
The Need for Asymptotic Analysis
▶ A precise count of operations, such as f (n) = 3n2 + 100n + 50, is overly complex and
hardware-dependent.
▶ Asymptotic analysis abstracts this by focusing on the limiting behavior of the function as
n → ∞.
▶ We ”drop constant coefficients and less significant terms”.
▶ In f (n) = 3n2 + 100n + 50, the n2 term is the dominant term.
▶ As n becomes large, the 100n and 50 terms become insignificant.
▶ The constant 3 is dropped as it’s a hardware-dependent speed-up.
▶ This leaves us with the algorithm’s intrinsic rate of growth, n2 .
Big O Notation (O): Asymptotic Upper Bound
Formal Definition
A function f (n) is in the set O(g (n)) if there exist positive constants c and n0 such that:
Interpretation
f (n) grows no faster than g (n), to within a constant factor. It’s an upper bound.
Big O Notation (O): Asymptotic Upper Bound (contd.)
Worked Example
▶ Let our algorithm’s exact runtime be f (n) = 3n2 + 10n + 4.
▶ We want to test if it’s O(n2 ), so g (n) = n2 .
▶ Claim: 3n2 + 10n + 4 is O(n2 ).
▶ Proof: We must find constants c, n0 that satisfy the definition.
▶ We need 3n2 + 10n + 4 ≤ c · n2 .
▶ Let’s test c = 4. Is 3n2 + 10n + 4 ≤ 4n2 ?
▶ This simplifies to 10n + 4 ≤ n2 . This is true for n = 11 (114 ≤ 121), so it holds for all
n ≥ 11.
▶ Result: We found c = 4 and n0 = 11. Since we found them, the claim is true.
Big Omega Notation (Ω): Asymptotic Lower Bound
Formal Definition
A function f (n) is in the set Ω(g (n)) if there exist positive constants c and n0 such that:
Interpretation
f (n) grows at least as fast as g (n). It’s a lower bound.
Big Omega Notation (Ω): Asymptotic Lower Bound(contd.)
Worked Example
▶ Let’s use the same function: f (n) = 3n2 + 10n + 4.
▶ We want to test if it’s Ω(n2 ), so g (n) = n2 .
▶ Claim: 3n2 + 10n + 4 is Ω(n2 ).
▶ Proof: We must find constants c, n0 .
▶ We need c · n2 ≤ 3n2 + 10n + 4.
▶ Since 10n + 4 is always positive for n ≥ 1, we know that 3n2 will always be less than
3n2 + 10n + 4.
▶ Let’s just pick c = 3.
▶ The inequality becomes 3n2 ≤ 3n2 + 10n + 4, which simplifies to 0 ≤ 10n + 4.
▶ This is true for all n ≥ 1.
▶ Result: We found c = 3 and n0 = 1. The claim is true.
Big Theta Notation (Θ): Asymptotic Tight Bound
Formal Definition
A function f (n) is in the set Θ(g (n)) if there exist positive constants c1 , c2 , and n0 such that:
Interpretation
f (n) grows at the same rate as g (n). It’s a tight bound. A function is Θ(g (n)) if and only if it
is both O(g (n)) and Ω(g (n)).
Big Theta Notation (Θ): Asymptotic Tight Bound(contd.)
Worked Example
▶ Again, let f (n) = 3n2 + 10n + 4 and g (n) = n2 .
▶ Claim: f (n) is Θ(n2 ).
▶ Proof: We need to ”sandwich” f (n) between two bounds.
▶ We need c1 · n2 ≤ 3n2 + 10n + 4 ≤ c2 · n2 .
▶ From our Ω proof, we know the lower bound works with c1 = 3 (for n ≥ 1).
▶ From our O proof, we know the upper bound works with c2 = 4 (for n ≥ 11).
▶ To use both constants, we must pick the larger n0 . So, let n0 = 11.
▶ Result: For n ≥ 11, we have 3n2 ≤ 3n2 + 10n + 4 ≤ 4n2 .
▶ Since we found c1 = 3, c2 = 4, and n0 = 11, the claim is true. f (n) is ”tightly bound” by
n2 .
Critical Insight: Notations vs. Cases
One can, and should, use any notation to describe any case.
Insertion Sort Analysis
▶ Best-Case Runtime: On an already-sorted array, the runtime function Tbest (n) is Θ(n).
▶ It is also (correctly) O(n) and Ω(n).
▶ Worst-Case Runtime: On a reverse-sorted array, the runtime function Tworst (n) is Θ(n2 ).
Description
The algorithm’s runtime is fixed and does not change, regardless of the size of the input n.
Description
The runtime grows exceptionally slowly. Each time the input size n doubles, the runtime
increases by only a constant amount. Typical of algorithms that reduce the problem size by a
constant factor (e.g., halving it).
The runtime grows directly proportional to the input size n. If the input size doubles, the
runtime (approximately) doubles.
Examples
▶ Linear Search: In the worst case, must check all n elements.
▶ Find Sum or Maximum: Must visit every element in the array at least once.
O(n log n) (Log-Linear Time)
This complexity class is highly efficient and common for optimal comparison-based sorting
algorithms. It represents performing O(n) work O(log n) times.
Example 1: Merge Sort
A classic ”Divide and Conquer” algorithm:
▶ Divide: Recursively splits the array in half. This creates a recursion tree of log n levels.
▶ Conquer & Combine: At each of the log n levels, the algorithm must merge all n
elements. This takes linear O(n) time per level.
▶ Total Time: (Cost per level) × (Number of levels) = O(n) × O(log n) = O(n log n).
Description
The runtime is proportional to the square of the input size. Common in algorithms that use
two nested loops, each iterating n times.
Description
The runtime doubles with each single addition to the input size n. These algorithms are
extremely slow and become computationally infeasible for even moderately large n.
Notation Common Name Growth Rate Example for n = 1, 000, 000 Canonical Algorithm(s)
O(1) Constant Independent of n ∼ 1 operation Array Access
O(log n) Logarithmic Increases by 1 when n doubles ∼ 20 operations Binary Search
O(n) Linear Proportional to n ∼ 1, 000, 000 operations Linear Search
O(n log n) Log-Linear Slightly worse than Linear ∼ 20, 000, 000 operations Merge Sort
O(n2 ) Quadratic Proportional to n × n ∼ 1, 000, 000, 000, 000 operations Bubble Sort
O(2n ) Exponential Doubles when n increases by 1 Computationally infeasible Naive Recursive Fibonacci
O(n!) Factorial Multiplies by n when n increases Computationally infeasible Brute-force Traveling Salesman
Defining Amortization
Amortized analysis is used for algorithms where an occasional operation is very slow, but most
other operations are much faster.
▶ It provides the average performance of each operation in the worst-case over a sequence of
operations.
▶ The high cost of the rare, expensive operation is ”amortized” (or ”spread out”) over the
more frequent, cheap operations.
”Expensive” Append
If the array is full (size == capacity), the algorithm must resize before appending:
1. Allocate a new, larger array (typically 2 × capacity).
2. Copy all n existing elements from the old array to the new one. (Actual Cost: O(n))
3. Add the new element. (Actual Cost: O(1))
The Actual Cost of this single operation is O(n).
▶ A simple worst-case analysis of a single append is O(n).
▶ An amortized analysis proves the cost is O(1).
Example: Dynamic Array (Aggregate Method Proof)
The O(1) amortized cost is entirely contingent on the geometric resizing strategy (e.g.,
multiplying by 2).
What if we use an arithmetic strategy?
(e.g., ”when full, add 10 more slots”)
▶ Insertion Cost: Still O(n).
▶ Resize Costs: 10 + 20 + 30 + · · · + O(n).
▶ This is an arithmetic series. An arithmetic series with O(n) terms sums to O(n2 ).
▶ Total Cost: O(n) + O(n2 ) = O(n2 ).
▶ Amortized Cost: O(n2 )/n = O(n).
This demonstrates that the doubling strategy is a deliberate and essential design choice to
ensure the O(1) amortized efficiency.
Part 1: Analyzing Iterative Code
Example Recurrences
▶ Binary Search: T (n) = 1T (n/2) + O(1)
▶ Merge Sort: T (n) = 2T (n/2) + O(n)
▶ Naive Fibonacci: T (n) = T (n − 1) + T (n − 2) + O(1)
Solving Recurrence Relations
Function Complex-Iterative(n)
sum = 0
Analysis
// Block 1: O(n)
for i = 1 to n: 1. Block 1 (Loop 1): Rule of Products:
sum += A[i] n × O(1) = O(n)
2. Block 2 (If/Else): Rule for Conditionals:
// Block 2: O(n^2) O(test) + max(O(Block A), O(Block B))
if (sum > 1000): O(1) + max(O(n2 ), O(1)) = O(n2 )
// Block A: O(n^2) 3. Total Function Cost: Rule of Sums:
for i = 1 to n: T (n) = O(Block 1) + O(Block 2)
for j = 1 to n: T (n) = O(n) + O(n2 )
print(A[i], A[j]) 4. Final Complexity: Drop non-dominant
term: T (n) = O(n2 )
else:
// Block B: O(1)
print("Sum is small")
Worked Example 2: Recursive (Merge Sort)
1. Formulate Recurrence Relation
▶ Splits n-element array into 2 sub-arrays of size n2 .
▶ Makes two recursive calls: 2T (n/2).
▶ Merges the two sorted halves, which takes linear O(n) time.
▶ Recurrence: T (n) = 2T (n/2) + O(n)
Function Logarithmic-Loop(n)
i = 1
while i < n: // Loop runs k times
i = i * 2 // Body: O(1)
Return i
Analysis
We need to find how many iterations, k, it takes for i to become ≥ n. The value of i follows
the sequence: 1, 2, 4, 8, 16, . . . , 2k . The loop terminates when 2k ≥ n.
Worked Example 4: Logarithmic Loop
Function Logarithmic-Loop(n)
i = 1
while i < n: // Loop runs k times
i = i * 2 // Body: O(1)
Return i
Analysis
We need to find how many iterations, k, it takes for i to become ≥ n. The value of i follows
the sequence: 1, 2, 4, 8, 16, . . . , 2k . The loop terminates when 2k ≥ n.
▶ To solve for k, take the logarithm of both sides:
▶ log2 (2k ) ≥ log2 (n)
▶ This simplifies to k ≥ log2 n.
Since the loop runs k times, its time complexity is O(log n).
Worked Example 5: Linearithmic Loop (Nested)
Function Linearithmic-Loop(n)
count = 0
for i = 1 to n: // Loop 1: Runs O(n) times
j = 1
while j < n: // Loop 2: Runs O(log n) times
j = j * 2 // Body: O(1)
count += 1
Return count
Analysis
We use the Rule of Products.
▶ The outer loop (line 2) is linear and runs O(n) times.
▶ The inner loop (line 4) is a logarithmic loop, identical to Example 4. It runs O(log n)
times.
▶ The total complexity is the product of the two loops:
▶ O(n) × O(log n) = O(n log n)
Analysis Exercises (1)
Exercise 1: Definitions
In plain English, what is the difference between an algorithm’s runtime being O(n2 ), Ω(n2 ),
and Θ(n2 )?
Solutions:
Solution 1: Definitions
▶ O(n2 ) (Big O): This is an upper bound. It means the runtime grows no faster than
quadratic. It is a guarantee that the algorithm is not worse.
▶ Ω(n2 ) (Big Omega): This is a lower bound. It means the runtime grows at least as fast as
quadratic. It is a guarantee that the algorithm is not better.
▶ Θ(n2 ) (Big Theta): This is a tight bound. It means the runtime grows at the same rate as
quadratic. It is both O(n2 ) and Ω(n2 ).
Analysis Exercises (2)
Function Exercise-1(n)
total = 0
for i = 1 to n:
for j = 1 to n:
total += i * j
for k = 1 to n:
total += k
Return total
Solutions: