Skip to the content.

3.4 Big O and Algorithm Efficiency

Big Idea 3.4

Big Idea3

Big O and Algorithmic Efficiency – Notes

Why Efficiency Matters

  • Faster applications = better user experience.
  • Efficient algorithms conserve memory, CPU, and power.
  • Essential for scalability and high-performance systems.

Big O Basics

  • O(1) – Constant time (e.g., dictionary access).
  • O(log n) – Logarithmic time (e.g., binary search).
  • O(n) – Linear time (e.g., loop through list).
  • O(n log n) – Linearithmic time (e.g., merge sort).
  • O(n²) – Quadratic time (e.g., bubble sort).

Popcorn Hack – Even/Odd Check

Best methods (O(1)):

  • n % 2 == 0
  • Bitwise: n & 1 == 0 (not mentioned but also efficient)

String Reversal

  • s[::-1]: Fast, more memory (new string copy)
  • Manual insert method: Less memory-efficient, slower

Searching

  • Linear Search: O(n), works on unsorted
  • Binary Search: O(log n), requires sorted list

Sorting

  • Bubble Sort: O(n²), inefficient for large lists
  • Merge Sort: O(n log n), efficient, uses extra memory

Trade-Offs

  • Faster runtime often requires more memory
  • Energy matters for mobile & embedded systems

Real-World Applications

  • Database indexing, machine learning, network routing

Diagrams

  • Algorithm Efficiency Flow: Input → Time/Space Complexity
  • Optimization Trade-off: Faster Time ↔ More Resources

✅ Completed Hacks

Popcorn Hack – Even/Odd Check

Best strategies:

  1. Use n % 2 == 0 → Constant time check
  2. Bitwise alternative: n & 1 == 0 → Also O(1), fast and efficient

Explanation:

  • Both avoid loops or conversions.
  • Fastest way to determine even/odd since they’re hardware-level operations.

Homework Hack #1: Sorting Showdown

import random, time

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

def merge_sort(arr):
    if len(arr) > 1:
        mid = len(arr) // 2
        left = arr[:mid]
        right = arr[mid:]
        merge_sort(left)
        merge_sort(right)
        i = j = k = 0
        while i < len(left) and j < len(right):
            if left[i] < right[j]:
                arr[k] = left[i]
                i += 1
            else:
                arr[k] = right[j]
                j += 1
            k += 1
        while i < len(left):
            arr[k] = left[i]
            i += 1
            k += 1
        while j < len(right):
            arr[k] = right[j]
            j += 1
            k += 1

lst = [random.randint(1, 1000) for _ in range(100)]

a = lst.copy()
start = time.time()
bubble_sort(a)
print("Bubble Sort Time:", time.time() - start)

b = lst.copy()
start = time.time()
merge_sort(b)
print("Merge Sort Time:", time.time() - start)
Bubble Sort Time: 0.0008630752563476562
Merge Sort Time: 0.0005192756652832031

Conclusion:

Merge Sort is consistently faster than Bubble Sort. Bubble Sort does unnecessary comparisons even in nearly sorted arrays.

Homework Hack #2:

def linear_search(arr, target):
    count = 0
    for i in range(len(arr)):
        count += 1
        if arr[i] == target:
            return count
    return -1

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    count = 0
    while left <= right:
        count += 1
        mid = (left + right) // 2
        if arr[mid] == target:
            return count
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

arr = list(range(1, 100001))
target = random.choice(arr)

print("Linear Search Comparisons:", linear_search(arr, target))
print("Binary Search Comparisons:", binary_search(arr, target))

Linear Search Comparisons: 26137
Binary Search Comparisons: 15

Conclusion:

Binary Search is much faster due to halving the search space. Binary Search fails on unsorted lists — sorting is a prerequisite.