When analyzing algorithms, we frequently encounter different types of mathematical functions that help us describe their time complexity and space complexity. These functions provide insight into how the running time or memory usage of an algorithm increases as the input size grows. The type of function that describes an algorithm's behavior is crucial for understanding its scalability and efficiency.
Below, we'll review the common types of functions that appear in algorithm analysis, with a focus on their significance and typical use cases.
Definition: A function is O(1) if its value does not depend on the size of the input n. In other words, the time or space required by the algorithm remains constant, regardless of how large the input is.
Example: Accessing an element in an array by its index, or performing a simple arithmetic operation like addition or assignment.
int x = arr[5]; // Accessing an element by index takes constant time O(1)
Real-world example: A hash table lookup (in average cases) takes constant time, O(1), because it directly computes the index using a hash function.
Definition: A function is O(n) if the time or space complexity grows linearly with the size of the input n. The performance of the algorithm increases in direct proportion to the size of the input.
Example: Iterating through an array of size n and performing some operation on each element.
for (int i = 0; i < n; i++) {
// Perform an operation on arr[i]
}
Real-world example: Linear search in an unsorted array or list has a time complexity of O(n) because it checks each element until it finds the target.
Definition: A function is O(n²) if the time or space complexity grows quadratically with the size of the input. This typically occurs in algorithms that involve nested loops over the input.
Example: A simple bubble sort algorithm, where two nested loops compare and possibly swap each pair of elements.
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// Compare elements and swap if necessary
}
}
Real-world example: Bubble sort, insertion sort, and other comparison-based sorting algorithms with two nested loops all exhibit O(n²) time complexity in the worst case.
Definition: A function is O(n³) if the time or space complexity grows cubically with the size of the input. This occurs when there are three nested loops.
Example: An algorithm that compares every triplet of elements in an array would likely exhibit cubic time complexity.
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
// Perform some operation
}
}
}
Real-world example: Matrix multiplication (using the standard algorithm) has O(n³) time complexity because you need to iterate over three dimensions (rows, columns, and depth of the matrix).
Definition: A function is O(log n) if the time or space complexity grows logarithmically with the size of the input. Logarithmic time complexities are typically seen in algorithms that divide the problem in half (or some constant fraction) at each step, such as binary search.
Example: Binary search on a sorted array of size n. Each iteration halves the search space.
int binarySearch(int arr[], int n, int target) {
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // Target not found
}
Real-world example: Binary search on a sorted array or balanced binary search trees (such as AVL trees or Red-Black trees) have O(log n) search times.
Definition: A function is O(n log n) if the time or space complexity grows at a rate that is a product of n and log n. This complexity is typical of efficient divide-and-conquer algorithms.
Example: The Merge Sort and Quick Sort algorithms have O(n log n) average and best-case time complexity.
// Merge Sort
void mergeSort(int arr[], int low, int high) {
if (low < high) {
int mid = low + (high - low) / 2;
mergeSort(arr, low, mid); // Divide
mergeSort(arr, mid + 1, high); // Divide
merge(arr, low, mid, high); // Combine
}
}
Real-world example: Merge Sort and Quick Sort (in the average case) both have O(n log n) time complexity, which makes them much more efficient than quadratic algorithms like Bubble Sort for large inputs.
Definition: A function is O(2ⁿ) if the time or space complexity doubles with each additional element. This represents a very inefficient algorithm that grows exponentially as the input size increases.
Example: An algorithm that solves a problem by considering every possible combination of inputs, such as the brute-force solution to the Traveling Salesman Problem (TSP).
// A brute-force approach to solving TSP
void bruteForceTSP(int dist[][], int n) {
int result = INT_MAX;
vector<int> perm(n);
for (int i = 0; i < n; i++) perm[i] = i;
do {
int currDist = 0;
for (int i = 0; i < n - 1; i++) {
currDist += dist[perm[i]][perm[i + 1]];
}
currDist += dist[perm[n - 1]][perm[0]]; // return to starting point
result = min(result, currDist);
} while (next_permutation(perm.begin(), perm.end()));
}
Real-world example: Brute-force solutions to combinatorial problems (e.g., TSP, subset sum, or knapsack problem) often have O(2ⁿ) time complexity, which makes them impractical for large inputs.
Definition: A function is O(n!) if the time or space complexity grows factorially with the input size. This is extremely inefficient and usually appears in algorithms that generate all possible permutations of a set of elements.
Example: An algorithm that generates and processes all permutations of a set of n elements, such as the brute-force approach to solving the Traveling Salesman Problem.
Real-world example: The Traveling Salesman Problem (TSP) can be solved by generating all possible permutations of the cities, which requires O(n!) time. This is not feasible for large numbers of cities.
| Function | Growth Rate | Notation | Examples |
|---|---|---|---|
| Constant | Constant time/space | O(1) | Accessing an array element, simple arithmetic operations |
| Linear | Linear growth | O(n) | Iterating over an array, linear search |
| Quadratic | Quadratic growth | O(n²) | Bubble sort, insertion sort |
| Cubic | Cubic growth | O(n³) | Matrix multiplication (naive), 3-dimensional iteration |
| Logarithmic | Logarithmic growth | O(log n) | Binary search, binary tree operations |
| Linearithmic | n log n growth | O(n log n) | Merge sort, quick sort |
| Exponential | Exponential growth | O(2ⁿ) | Brute force solutions to combinatorial problems |
| Factorial | Factorial growth | O(n!) | Generating all permutations of n elements |
Understanding the types of functions and their growth rates
Open this section to load past papers