Time and Space Complexity
Algorithms
An algorithm is a method for solving a problem, made up of a sequence of steps. An algorithm has five characteristics:
- Finiteness: The algorithm must terminate after a finite number of steps.
- Definiteness: Every step of the algorithm must have a precise definition.
- Input: An algorithm has zero or more inputs.
- Output: An algorithm has one or more outputs. An algorithm with no output is meaningless.
- Effectiveness: Every step performed by the algorithm can be broken down into basic executable operations.
KNOWLEDGE QUIZ
Time Complexity
Some algorithms are efficient; others are clumsy. Time complexity and space complexity are two standards for evaluating an algorithm.
Imagine that a manager asks Xiao Hui and Da Huang to complete the same requirement. Both deliver code with the same functionality. Da Huang's code takes 100 ms and uses 5 MB of memory per run, while Xiao Hui's takes 100 seconds and uses 500 MB.
“Xiao Hui, pack your things. You don't need to come in tomorrow!”
Although Xiao Hui implemented the required functionality, his code has serious problems: it runs for too long and occupies too much space.
The time complexity of an algorithm describes the number of basic operations it performs. Basic operations include addition, subtraction, swapping, and comparison.
An algorithm's efficiency should depend on the algorithm itself rather than on a particular machine. When analyzing efficiency, we consider the relationship between running time and input size. Counting the basic operations gives us a function of the input size .
Time complexity generally uses Big-O notation to express how running time grows with input size. Common complexities include , , , , , , and .
Apply these principles when simplifying time complexity:
- If the running time is constant, write it as .
- Keep only the highest-order term in the time function.
- Remove the coefficient of the highest-order term.
KNOWLEDGE QUIZ
7n² + 30n + 8 basic operations. What is its Big-O time complexity?
Race the growth curves
Compare seven growth rates on a fixed linear axis from 0 to 50. Arrows at the top mean the curve continues beyond the chart; the exact operation count is shown below.
Big-O notation
Big-O notation represents an upper bound on time complexity, often used to discuss the worst case. The order of the algorithm does not exceed the function in the Big-O bound.
Big- notation
Big- notation represents a lower bound on time complexity, often used to discuss the best case. The order of the algorithm is not lower than the function in the Big- bound.
Little-o notation
Little-o notation represents a strict upper bound: the algorithm's order is strictly lower than the function in the bound.
Little- notation
Little- notation represents a strict lower bound: the algorithm's order is strictly higher than the function in the bound.
notation
If and , then and have the same order:
KNOWLEDGE QUIZ
f(n) is both O(g(n)) and Ω(g(n)), what can we conclude?
One hundred chickens for one hundred coins
A rooster costs 5 coins, a hen costs 3 coins, and three chicks cost 1 coin. If 100 coins must buy exactly 100 chickens, how many roosters, hens, and chicks should be purchased?
#include <stdio.h>
void buy(int n, int money) {
for (int x = 0; x <= n / 5; x++) {
for (int y = 0; y <= n / 3; y++) {
int z = n - x - y;
if (z > 0 && z % 3 == 0 && 5 * x + 3 * y + z / 3 == money) {
printf("x = %d, y = %d, z = %d\n", x, y, z);
}
}
}
}
int main() {
buy(100, 100);
return 0;
}Space Complexity
The less memory an algorithm occupies, the better. Space complexity measures the temporary storage space used while an algorithm runs, and it also uses Big-O notation.
You cannot always have the best of both worlds. Time and space complexity often require a trade-off. In most situations, time complexity is more important: it is worth allocating some additional memory to improve execution speed.
Amortized Time Complexity
Amortized time complexity analyzes a sequence of operations in which an individual operation may be expensive, but the average cost across the sequence remains low.
Dynamic arrays
void add(DynamicArray *list, int element) {
if (list->size == list->capacity) {
list->capacity *= 2;
list->arr = realloc(list->arr, list->capacity * sizeof(int));
}
list->arr[list->size++] = element;
}This code adds data to an array. When the array is not full, it places the new element directly at the end, which takes time.
When the array is full, however, it must create a larger array, copy the original data into it, and then add the new element at the end. This process takes time.
Suppose the array has capacity and expands only once for every calls to add(). We can spread that one cost across the preceding operations, making the amortized time complexity of add() equal to .
KNOWLEDGE QUIZ
Complexity audit
Compare two algorithms. Algorithm A performs n² + 4n + 20 basic operations. Algorithm B divides n by 2 each round until n = 1.
- Give the tight asymptotic time complexity of A and B.
- Estimate their operation counts when n = 1024.
- If A also creates a temporary array of length n, state its auxiliary space complexity.
- A cache can reduce repeated queries from O(n) to O(1), but requires O(n) space. Explain when you would accept this trade-off.