Skip to main content

Algorithms and Problem Solving

Subject: Computer Science
Topic: 6
Cambridge Code: 0478


Algorithm Concept​

Algorithm - Step-by-step procedure to solve problem

Algorithm Properties​

  • Finiteness: Must end
  • Definiteness: Each step clear
  • Input: Receives data
  • Output: Produces result
  • Effectiveness: Achievable

Searching Algorithms​

Linear search - Check each element sequentially

def linear_search(list, target):
for i in range(len(list)):
if list[i] == target:
return i
return -1 # Not found

Characteristics:

  • Works on unsorted lists
  • Time: O(n) - checks all elements
  • Simple but slow for large lists

Example: [3, 1, 4, 1, 5], search for 4

  • Check 3: No
  • Check 1: No
  • Check 4: Yes → Return position

Binary search - Divide and conquer approach

Requirement: List must be sorted

def binary_search(list, target):
left = 0
right = len(list) - 1

while left <= right:
mid = (left + right) // 2
if list[mid] == target:
return mid
elif list[mid] < target:
left = mid + 1 # Search right half
else:
right = mid - 1 # Search left half

return -1 # Not found

Characteristics:

  • Only works on sorted lists
  • Time: O(log n) - much faster
  • Example: [1, 3, 4, 5, 8], search for 4
    • Check middle (4): Found → Return

Comparison​

FeatureLinearBinary
Sorted requiredNoYes
Time complexityO(n)O(log n)
Data structureAnyArray
ImplementationSimpleMore complex
SpeedSlowFast

Sorting Algorithms​

Bubble Sort​

Bubble sort - Compare adjacent elements, swap if needed

def bubble_sort(list):
n = len(list)
for i in range(n):
for j in range(0, n - i - 1):
if list[j] > list[j + 1]:
# Swap
list[j], list[j + 1] = list[j + 1], list[j]
return list

Process:

  1. Compare adjacent pairs
  2. Swap if out of order
  3. Repeat until sorted
  4. Each pass bubbles largest to end

Example: [3, 1, 4, 2]

  • Pass 1: [1, 3, 2, 4]
  • Pass 2: [1, 2, 3, 4]

Characteristics:

  • Time: O(n²)
  • Space: O(1)
  • Simple but inefficient
  • Stable sort

Insertion Sort​

Insertion sort - Build sorted array one element at time

def insertion_sort(list):
for i in range(1, len(list)):
key = list[i]
j = i - 1
while j >= 0 and list[j] > key:
list[j + 1] = list[j]
j -= 1
list[j + 1] = key
return list

Process:

  1. Start with second element
  2. Compare with sorted portion
  3. Insert in correct position
  4. Move to next element

Characteristics:

  • Time: O(n²) worst case
  • Space: O(1)
  • Efficient for small lists
  • Stable sort

Quick Sort​

Quick sort - Divide and conquer

def quick_sort(list):
if len(list) <= 1:
return list

pivot = list[len(list) // 2]
left = [x for x in list if x < pivot]
middle = [x for x in list if x == pivot]
right = [x for x in list if x > pivot]

return quick_sort(left) + middle + quick_sort(right)

Process:

  1. Choose pivot
  2. Partition into smaller/larger
  3. Recursively sort partitions
  4. Combine

Characteristics:

  • Time: O(n log n) average
  • Space: O(log n) recursive calls
  • Very efficient for large lists
  • Not stable

Merge Sort​

Merge sort - Divide and merge

def merge_sort(list):
if len(list) <= 1:
return list

mid = len(list) // 2
left = merge_sort(list[:mid])
right = merge_sort(list[mid:])

return merge(left, right)

def merge(left, right):
result = []
i = j = 0

while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1

result.extend(left[i:])
result.extend(right[j:])
return result

Characteristics:

  • Time: O(n log n) always
  • Space: O(n)
  • Reliable, predictable
  • Stable sort

Time Complexity (Big O Notation)​

Big O - Measure of algorithm efficiency relative to input size

Common Complexities​

NotationNameOperations for n=1000
O(1)Constant1
O(log n)Logarithmic~10
O(n)Linear1,000
O(n log n)Linearithmic~10,000
O(n²)Quadratic1,000,000
O(n³)Cubic10⁹
O(2ⁿ)Exponential10³⁰
O(n!)FactorialHuge

Analysis​

Worst case: Maximum operations Best case: Minimum operations Average case: Typical scenario

Example (Bubble sort):

  • Worst case: O(n²)
  • Best case: O(n) if already sorted
  • Average: O(n²)

Algorithm Choosing​

Consider:

  1. Speed: Time complexity
  2. Memory: Space complexity
  3. Implementation: Coding complexity
  4. Stability: Order preservation
  5. Input data: Sorted or unique

Guidelines:

  • Small lists: Bubble or insertion sort
  • Large lists: Quick or merge sort
  • Nearly sorted: Insertion sort
  • Stability needed: Merge or insertion
  • Unknown data: Quick sort (good average)

Recursion​

Recursion - Function calling itself

def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n - 1)

Elements:

  • Base case: Stops recursion
  • Recursive case: Calls itself
  • Termination: Must eventually reach base

Example: factorial(5)

  • 5 × factorial(4)
  • 5 × 4 × factorial(3)
  • 5 × 4 × 3 × 2 × 1 = 120

Key Points​

  1. Linear search: Simple, O(n), unsorted data
  2. Binary search: Fast O(log n), requires sorted data
  3. Bubble sort: O(n²), simple, inefficient
  4. Insertion sort: O(n²), good for small lists
  5. Quick sort: O(n log n) average, very efficient
  6. Merge sort: O(n log n) guaranteed, stable
  7. Big O notation measures complexity
  8. Choose algorithm based on data and requirements

Practice Questions​

  1. Implement linear search
  2. Trace binary search
  3. Sort by hand (bubble, insertion)
  4. Analyze time complexity
  5. Choose appropriate algorithm
  6. Optimize algorithm
  7. Trace recursive function

Revision Tips​

  • Know sorting algorithms thoroughly
  • Practice tracing algorithms
  • Understand Big O notation
  • Know algorithm trade-offs
  • Compare search methods
  • Recognize ideal use cases
  • Practice implementations