Time complexity is a metric used to assess an algorithm's efficiency. It describes how the algorithm's running time grows in proportion to the size of the input data. The metric is frequently presented in big O notation, which indicates the maximum number of operations that an algorithm can perform. It is used to assess the worst-case scenario and determine the algorithm's time efficiency.
For example, an algorithm with a time complexity of O(n) takes an amount of time that is directly proportional to the size of the input (n). An algorithm with a time complexity of O(1) takes a constant amount of time, regardless of the size of the input.
There are several factors that can affect the time complexity of an algorithm:
- The size of the input: As the size of the input data increases, the running time of an algorithm will typically increase as well.
- The number of operations: An algorithm that performs many operations will have a higher time complexity than one that performs fewer operations.
- The type of operations: Some operations are more computationally expensive than others. For example, a search operation on a sorted array takes less time than the same operation on an unsorted array.
- Recursive calls and nested loops: Algorithms with recursive calls and nested loops also affect the time complexity. The number of recursive calls or nested loops can increase the time complexity.
To optimize the time complexity of an algorithm, you can consider the following strategies:
- Using efficient data structures: Using efficient data structures can help to improve the time complexity of an algorithm.
- Reducing the number of operations: If an algorithm is performing too many operations, it can be optimized by removing unnecessary operations or by simplifying the algorithm.
- Using a more efficient algorithm: This is the most straightforward way to improve time complexity. For example, using a sorting algorithm with a better time complexity than the one you are currently using.
The most common time complexities are listed below, in increasing order of efficiency.
O(1) Constant time
It denotes constant time complexity, which means that the running time of an algorithm remains constant regardless of the size of the input. An algorithm with a time complexity of O(1) performs a fixed number of operations and is not affected by the size of the input. Constant time operations include accessing an array element and adding or removing an element from the end of an array.
O(log n) Logarithmic time
It indicates a logarithmic time complexity, which means that as the size of the input increases, an algorithm's execution time grows more gradually. Operations such as accessing, inserting, and deleting a value in a balanced binary search tree are considered to have a logarithmic time complexity
O(n) Linear time
When an algorithm has linear time complexity, the amount of time it takes to execute increases directly as the size of the input does. This means that the number of operations carried out by an algorithm with an O(n) time complexity is directly proportional to the size of the input. The addition of an element to the beginning of an array, the removal of an element from the beginning of an array, and searching for an element in an unsorted array are all regarded as operations of linear time complexity.
O(n log n) Linear logarithmic
When an algorithm has linear logarithmic time complexity, it means that the number of operations is proportional to the size of the input multiplied by the logarithm of the size of the input. Most sorting algorithms such as quick sort and merge sort have linear time complexity.
O(n^2) Quadratic time
An algorithm with a time complexity of O(n^2) performs a number of operations that is directly proportional to the square of the size of the input. This type of time complexity is considered relatively inefficient, as the running time increases very fast with the size of the input. Simple nested loop techniques, like checking all pairs of items in an array, or brute force algorithms, such checking all conceivable combinations of elements in a set, are examples of algorithms having O(n^2) time complexity.
O(n^3) Cubic time
Cubic time complexity algorithms carry out operations that are directly proportional to the cube of the amount of the input, much like quadric time complexity algorithms. Cubic time complexity algorithms are thought to be inefficient. Matrix multiplication and algorithms that use three nested loops are examples of algorithms having O(n^3) time complexity.
O(2^n) Exponential time
The number of operations carried out by algorithms with an O(2^n) time complexity is exactly proportional to 2 raised to the power of the input size. As the running time rapidly grows with the size of the input, this type of time complexity is thought to be exceedingly inefficient. Examples of algorithms with O(2^n) time complexity include those that tackle problems like the traveling salesman problem by examining all feasible routes and determining the shortest one.