The time required by the same algorithm varies somewhat on different computers, and its actual running time is difficult to calculate theoretically; measuring it directly is also troublesome. Therefore, we usually consider not the actual time required by the algorithm, but the number of basic operations it needs to perform.
Time complexity and space complexity are important criteria for measuring the efficiency of an algorithm.
Number of basic operations
On an ordinary computer, addition, subtraction, multiplication, division, accessing variables (variables of basic data types, the same below), assigning values to variables, and so on can all be regarded as basic operations.
Counting or estimating the number of basic operations can serve as an indicator for evaluating an algorithm's running time.
Time complexity
Definition
When measuring how fast an algorithm is, the size of the data must be considered. Data size generally refers to the number of input numbers, the number of vertices and edges in a graph given in the input, and so on. In general, the larger the data size, the longer the algorithm takes. In algorithm competitions, when measuring an algorithm's efficiency, the most important thing is not its running time at a particular data size, but the trend in which its running time grows with the data size, namely time complexity.
Introduction
There are several main reasons for considering the trend of running time as the data size changes:
- Modern computers can perform hundreds of millions or more basic operations per second, so the data sizes we process are usually very large. If algorithm A takes time on data of size , while algorithm B takes time on data of size , then when the data size is less than , algorithm B takes less time; however, within one second algorithm A can process data of size millions, whereas algorithm B can process only data of size tens of thousands. When algorithms are allowed to run for longer, the impact of time complexity on the processable data size becomes more apparent, far exceeding the impact of running time at the same data size.
- We use the number of basic operations to represent an algorithm's running time, but different basic operations take different amounts of actual time; for example, addition and subtraction take far less time than division. Ignoring the differences between different basic operations and between one basic operation and ten basic operations when calculating time complexity eliminates the effect of differing operation times.
Of course, an algorithm's running time is not determined entirely by the input size; it is also related to the input's contents. Therefore, time complexity is further divided into several kinds, for example:
- Worst-case time complexity, namely the time complexity corresponding to the input that takes the longest time for each input size. In algorithm competitions, since the input can be chosen arbitrarily within the given data range, we generally consider worst-case time complexity to ensure that an algorithm can pass any data within a given range.
- Average (expected) time complexity, namely the complexity of the average running time over all possible inputs for each input size (the complexity of the expected running time under random input).
The so-called "trend of running time as the data size grows" is a vague concept. We need to use the asymptotic notation introduced below to express time complexity formally.
Definition of asymptotic notation
Asymptotic notation is a standard description of the order of a function. Simply put, asymptotic notation ignores the slower-growing parts of a function and the coefficients of its terms (in time-complexity analysis, coefficients are generally called "constants"), while retaining the important parts that can indicate the function's growth trend.
A simple way to remember this is that notation containing equality (non-strict) uses uppercase letters, while notation without equality (strict) uses lowercase letters: equality is , less than is , and greater than is . Big and little were originally the Greek letter Omicron; because their shapes are the same, they can also be understood as uppercase and lowercase in the Latin alphabet.
In English, the roots "-micro-" and "-mega-" are commonly used to denote the negative sixth power of 10 (one millionth) and the sixth power (one million), and also "small" and "large". Small and large are also meanings commonly represented by the Greek letters Omicron and Omega.
Big Theta notation
For functions and , if and only if , such that .
That is, if , then we can find two positive numbers such that is bounded between and .
For example, ; here, can be , respectively. ; here, can be , respectively.
Big O notation
The notation gives both an upper and a lower asymptotic bound for a function. If we know only an asymptotic upper bound and not an asymptotic lower bound, we can use notation. if and only if , such that .
When studying time complexity, notation is usually used because we are generally concerned with the upper bound on a program's running time, not its lower bound.
It should be noted that the "upper bound" and "lower bound" here refer to the function's growth trend, not to the algorithm. The upper bound on an algorithm's running time corresponds to its "worst-case time complexity", rather than Big notation. Therefore, it is entirely valid to use notation to express worst-case time complexity; one could even say that is more precise than . The main reasons for using notation are, first, that sometimes we can prove only an upper bound on the time complexity and not its lower bound (which generally occurs for more complicated algorithms and complexity analyses), and second, that is more convenient to type on a computer.
Big Omega notation
Similarly, we use notation to describe a function's asymptotic lower bound. if and only if , such that .
Little o notation
If notation is equivalent to the less-than-or-equal-to sign, then notation is equivalent to the less-than sign.
Little notation is widely used in mathematical analysis. The Taylor expansion of a function at a point has a Peano remainder, and little notation expresses strict less-than, thereby enabling asymptotic analysis of equivalent infinitesimals.
if and only if, for any given positive number , , such that .
Little omega notation
If notation is equivalent to the greater-than-or-equal-to sign, then notation is equivalent to the greater-than sign.
if and only if, for any given positive number , , such that .

Common properties
- . The change-of-base formula shows that any logarithmic function has the same growth rate regardless of its base, so the base of a logarithm is generally omitted in asymptotic time complexity.
Examples of simple time-complexity calculations
for loop
C++
int n, m;
std::cin >> n >> m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < m; ++k) {
std::cout << "hello world\n";
}
}
}
Python
n = int(input())
m = int(input())
for i in range(0, n):
for j in range(0, n):
for k in range(0, m):
print("hello world")
Java
int n, m;
n = input.nextInt();
m = input.nextInt();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < m; ++k) {
System.out.println("hello world");
}
}
}
If the sizes of input values and are used as the data size, the time complexity of the code above is .
DFS
When performing DFS on a graph with vertices and edges, because each vertex and each edge is visited only a constant number of times, the complexity is .
Which quantities are constants?
When we need to perform several operations, how do we determine whether these operations affect the time complexity? For example:
C++
constexpr int N = 100000;
for (int i = 0; i < N; ++i) {
std::cout << "hello world\n";
}
Python
N = 100000
for i in range(0, N):
print("hello world")
Java
final int N = 100000;
for (int i = 0; i < N; ++i) {
System.out.println("hello world");
}
If the size of is not regarded as the input size, the time complexity of this code is .
When calculating time complexity, it is important which variables are regarded as the input size. All quantities unrelated to the input size are regarded as constants and can be treated as when calculating complexity.
It should be noted that in theoretical discussions related to time complexity, "an algorithm can solve problems of any size" is a basic assumption (of course, in practice, problems that are too large cannot be solved because time and storage space are limited). Therefore, solving problems with a limited data size in constant time (for example, precomputing the answer for every possible input in the data range) cannot make an algorithm's time complexity become .
Master Theorem
We can use the Master Theorem to quickly find the complexity of recursive algorithms. The recurrence relation of the Master Theorem is as follows:
Then
It should be noted that the second case here also needs to satisfy the regularity condition, that is, , for some constant and sufficiently large .
The proof idea is to decompose a problem of size into problems of size , and then merge them successively until reaching the top level. Each time subproblems are merged, time is required.
Proof
Based on the proof idea mentioned above, the detailed proof is as follows.
At level (the top level), merging the subproblems takes time.
At level (the subproblems produced by the first division), there are subproblems, and merging each subproblem takes time, so merging all of them takes time.
This tree has height and leaves, so , where .
For the first case: , so .
For the second case: first, . Also, because , as long as is a sufficiently small positive number and is sufficiently large, we can deduce that . By squeezing between the two sides, we obtain .
For the third case: , so . The result for follows immediately once is obtained.
The following examples illustrate how to use the Master Theorem.
-
, so . Then can take a value in , satisfying the first case, so .
-
, so . Then can take a value in , satisfying the second case, so .
-
, so . Then can take the value , satisfying the third case, so .
-
, so . Then can take the value , satisfying the third case, so .
Amortized complexity
See Amortized complexity for details.
Space complexity
Similarly, the trend in which the space used by an algorithm changes with the input size can be measured by space complexity.
Computational complexity
This article has introduced complexity mainly from the perspective of algorithm analysis. If you are interested, you can learn more at Computational complexity.