Recent from talks
Nothing was collected or created yet.
Analysis of algorithms
View on WikipediaThis article includes a list of general references, but it lacks sufficient corresponding inline citations. (March 2010) |


In computer science, the analysis of algorithms is the process of finding the computational complexity of algorithms—the amount of time, storage, or other resources needed to execute them. Usually, this involves determining a function that relates the size of an algorithm's input to the number of steps it takes (its time complexity) or the number of storage locations it uses (its space complexity). An algorithm is said to be efficient when this function's values are small, or grow slowly compared to a growth in the size of the input. Different inputs of the same size may cause the algorithm to have different behavior, so best, worst and average case descriptions might all be of practical interest. When not otherwise specified, the function describing the performance of an algorithm is usually an upper bound, determined from the worst case inputs to the algorithm.
The term "analysis of algorithms" was coined by Donald Knuth.[1] Algorithm analysis is an important part of a broader computational complexity theory, which provides theoretical estimates for the resources needed by any algorithm which solves a given computational problem. These estimates provide an insight into reasonable directions of search for efficient algorithms.
In theoretical analysis of algorithms it is common to estimate their complexity in the asymptotic sense, i.e., to estimate the complexity function for arbitrarily large input. Big O notation, Big-omega notation and Big-theta notation are used to this end.[2] For instance, binary search is said to run in a number of steps proportional to the logarithm of the size n of the sorted list being searched, or in O(log n), colloquially "in logarithmic time". Usually asymptotic estimates are used because different implementations of the same algorithm may differ in efficiency. However the efficiencies of any two "reasonable" implementations of a given algorithm are related by a constant multiplicative factor called a hidden constant.
Exact (not asymptotic) measures of efficiency can sometimes be computed but they usually require certain assumptions concerning the particular implementation of the algorithm, called a model of computation. A model of computation may be defined in terms of an abstract computer, e.g. Turing machine, and/or by postulating that certain operations are executed in unit time. For example, if the sorted list to which we apply binary search has n elements, and we can guarantee that each lookup of an element in the list can be done in unit time, then at most log2(n) + 1 time units are needed to return an answer.
Cost models
[edit]Time efficiency estimates depend on what we define to be a step. For the analysis to correspond usefully to the actual run-time, the time required to perform a step must be guaranteed to be bounded above by a constant. One must be careful here; for instance, some analyses count an addition of two numbers as one step. This assumption may not be warranted in certain contexts. For example, if the numbers involved in a computation may be arbitrarily large, the time required by a single addition can no longer be assumed to be constant.
Two cost models are generally used:[3][4][5][6][7]
- the uniform cost model, also called unit-cost model (and similar variations), assigns a constant cost to every machine operation, regardless of the size of the numbers involved
- the logarithmic cost model, also called logarithmic-cost measurement (and similar variations), assigns a cost to every machine operation proportional to the number of bits involved
The latter is more cumbersome to use, so it is only employed when necessary, for example in the analysis of arbitrary-precision arithmetic algorithms, like those used in cryptography.
A key point which is often overlooked is that published lower bounds for problems are often given for a model of computation that is more restricted than the set of operations that you could use in practice and therefore there are algorithms that are faster than what would naively be thought possible.[8]
Run-time analysis
[edit]Run-time analysis is a theoretical classification that estimates and anticipates the increase in running time (or run-time or execution time) of an algorithm as its input size (usually denoted as n) increases. Run-time efficiency is a topic of great interest in computer science: A program can take seconds, hours, or even years to finish executing, depending on which algorithm it implements. While software profiling techniques can be used to measure an algorithm's run-time in practice, they cannot provide timing data for all infinitely many possible inputs; the latter can only be achieved by the theoretical methods of run-time analysis.
Shortcomings of empirical metrics
[edit]Since algorithms are platform-independent (i.e. a given algorithm can be implemented in an arbitrary programming language on an arbitrary computer running an arbitrary operating system), there are additional significant drawbacks to using an empirical approach to gauge the comparative performance of a given set of algorithms.
Take as an example a program that looks up a specific entry in a sorted list of size n. Suppose this program were implemented on Computer A, a state-of-the-art machine, using a linear search algorithm, and on Computer B, a much slower machine, using a binary search algorithm. Benchmark testing on the two computers running their respective programs might look something like the following:
| n (list size) | Computer A run-time (in nanoseconds) |
Computer B run-time (in nanoseconds) |
|---|---|---|
| 16 | 8 | 100,000 |
| 63 | 32 | 150,000 |
| 250 | 125 | 200,000 |
| 1,000 | 500 | 250,000 |
Based on these metrics, it would be easy to jump to the conclusion that Computer A is running an algorithm that is far superior in efficiency to that of Computer B. However, if the size of the input-list is increased to a sufficient number, that conclusion is dramatically demonstrated to be in error:
| n (list size) | Computer A run-time (in nanoseconds) |
Computer B run-time (in nanoseconds) |
|---|---|---|
| 16 | 8 | 100,000 |
| 63 | 32 | 150,000 |
| 250 | 125 | 200,000 |
| 1,000 | 500 | 250,000 |
| ... | ... | ... |
| 1,000,000 | 500,000 | 500,000 |
| 4,000,000 | 2,000,000 | 550,000 |
| 16,000,000 | 8,000,000 | 600,000 |
| ... | ... | ... |
| 63,072 × 1012 | 31,536 × 1012 ns, or 1 year |
1,375,000 ns, or 1.375 milliseconds |
Computer A, running the linear search program, exhibits a linear growth rate. The program's run-time is directly proportional to its input size. Doubling the input size doubles the run-time, quadrupling the input size quadruples the run-time, and so forth. On the other hand, Computer B, running the binary search program, exhibits a logarithmic growth rate. Quadrupling the input size only increases the run-time by a constant amount (in this example, 50,000 ns). Even though Computer A is ostensibly a faster machine, Computer B will inevitably surpass Computer A in run-time because it is running an algorithm with a much slower growth rate.
Orders of growth
[edit]Informally, an algorithm can be said to exhibit a growth rate on the order of a mathematical function if beyond a certain input size n, the function f(n) times a positive constant provides an upper bound or limit for the run-time of that algorithm. In other words, for a given input size n greater than some n0 and a constant c, the run-time of that algorithm will never be larger than c × f(n). This concept is frequently expressed using Big O notation. For example, since the run-time of insertion sort grows quadratically as its input size increases, insertion sort can be said to be of order O(n2).
Big O notation is a convenient way to express the worst-case scenario for a given algorithm, although it can also be used to express the average-case — for example, the worst-case scenario for quicksort is O(n2), but the average-case run-time is O(n log n).
Empirical orders of growth
[edit]Assuming the run-time follows the power rule, t ≈ kna, the parameter a can be found [9] by taking empirical measurements of run-time t1 and t2 at some problem-size points n1 and n2, and solving the equation t2/t1 = (n2/n1)a w.r.t. a, that is, a = log(t2/t1)/log(n2/n1). In other words, this measures the slope of the empirical line on the log–log plot of run-time vs. input size, at some size point. If the order of growth indeed follows the power rule (and so the line on the log–log plot is indeed a straight line), the empirical value of a will stay constant at different ranges, and if not, it will change (and the line is a curved line)—but still can serve for comparison of any two given algorithms as to their empirical local orders of growth behaviour. Applied to the above table:
| n (list size) | Computer A run-time (in nanoseconds) |
Local order of growth (n^_) |
Computer B run-time (in nanoseconds) |
Local order of growth (n^_) |
|---|---|---|---|---|
| 15 | 7 | 100,000 | ||
| 65 | 32 | 1.04 | 150,000 | 0.28 |
| 250 | 125 | 1.01 | 200,000 | 0.21 |
| 1,000 | 500 | 1.00 | 250,000 | 0.16 |
| ... | ... | ... | ||
| 1,000,000 | 500,000 | 1.00 | 500,000 | 0.10 |
| 4,000,000 | 2,000,000 | 1.00 | 550,000 | 0.07 |
| 16,000,000 | 8,000,000 | 1.00 | 600,000 | 0.06 |
| ... | ... | ... |
It is clearly seen that the first algorithm exhibits a linear order of growth indeed following the power rule. The empirical values for the second one are diminishing rapidly, suggesting it follows another rule of growth and in any case has much lower local orders of growth (and improving further still), empirically, than the first one.
Evaluating run-time complexity
[edit]The run-time complexity for the worst-case scenario of a given algorithm can sometimes be evaluated by examining the structure of the algorithm and making some simplifying assumptions. Consider the following pseudocode:
1 get a positive integer n from input 2 if n > 10 3 print "This might take a while..." 4 for i = 1 to n 5 for j = 1 to i 6 print i * j 7 print "Done!"
A given computer will take a discrete amount of time to execute each of the instructions involved with carrying out this algorithm. Say that the actions carried out in step 1 are considered to consume time at most T1, step 2 uses time at most T2, and so forth.
In the algorithm above, steps 1, 2 and 7 will only be run once. For a worst-case evaluation, it should be assumed that step 3 will be run as well. Thus the total amount of time to run steps 1–3 and step 7 is:
The loops in steps 4, 5 and 6 are trickier to evaluate. The outer loop test in step 4 will execute ( n + 1 ) times,[10] which will consume T4( n + 1 ) time. The inner loop, on the other hand, is governed by the value of j, which iterates from 1 to i. On the first pass through the outer loop, j iterates from 1 to 1: The inner loop makes one pass, so running the inner loop body (step 6) consumes T6 time, and the inner loop test (step 5) consumes 2T5 time. During the next pass through the outer loop, j iterates from 1 to 2: the inner loop makes two passes, so running the inner loop body (step 6) consumes 2T6 time, and the inner loop test (step 5) consumes 3T5 time.
Altogether, the total time required to run the inner loop body can be expressed as an arithmetic progression:
The total time required to run the inner loop test can be evaluated similarly:
which can be factored as
Therefore, the total run-time for this algorithm is:
which reduces to
As a rule-of-thumb, one can assume that the highest-order term in any given function dominates its rate of growth and thus defines its run-time order. In this example, n2 is the highest-order term, so one can conclude that f(n) = O(n2). Formally this can be proven as follows:
Prove that
Let k be a constant greater than or equal to [T1..T7]
Therefore
A more elegant approach to analyzing this algorithm would be to declare that [T1..T7] are all equal to one unit of time, in a system of units chosen so that one unit is greater than or equal to the actual times for these steps. This would mean that the algorithm's run-time breaks down as follows:[12]
Growth rate analysis of other resources
[edit]The methodology of run-time analysis can also be utilized for predicting other growth rates, such as consumption of memory space. As an example, consider the following pseudocode which manages and reallocates memory usage by a program based on the size of a file which that program manages:
while file is still open:
let n = size of file
for every 100,000 kilobytes of increase in file size
double the amount of memory reserved
In this instance, as the file size n increases, memory will be consumed at an exponential growth rate, which is order O(2n). This is an extremely rapid and most likely unmanageable growth rate for consumption of memory resources.
Relevance
[edit]Algorithm analysis is important in practice because the accidental or unintentional use of an inefficient algorithm can significantly impact system performance. In time-sensitive applications, an algorithm taking too long to run can render its results outdated or useless. An inefficient algorithm can also end up requiring an uneconomical amount of computing power or storage in order to run, again rendering it practically useless.
Constant factors
[edit]Analysis of algorithms typically focuses on the asymptotic performance, particularly at the elementary level, but in practical applications constant factors are important, and real-world data is in practice always limited in size. The limit is typically the size of addressable memory, so on 32-bit machines 232 = 4 GiB (greater if segmented memory is used) and on 64-bit machines 264 = 16 EiB. Thus given a limited size, an order of growth (time or space) can be replaced by a constant factor, and in this sense all practical algorithms are O(1) for a large enough constant, or for small enough data.
This interpretation is primarily useful for functions that grow extremely slowly: (binary) iterated logarithm (log*) is less than 5 for all practical data (265536 bits); (binary) log-log (log log n) is less than 6 for virtually all practical data (264 bits); and binary log (log n) is less than 64 for virtually all practical data (264 bits). An algorithm with non-constant complexity may nonetheless be more efficient than an algorithm with constant complexity on practical data if the overhead of the constant time algorithm results in a larger constant factor, e.g., one may have so long as and .
For large data linear or quadratic factors cannot be ignored, but for small data an asymptotically inefficient algorithm may be more efficient. This is particularly used in hybrid algorithms, like Timsort, which use an asymptotically efficient algorithm (here merge sort, with time complexity ), but switch to an asymptotically inefficient algorithm (here insertion sort, with time complexity ) for small data, as the simpler algorithm is faster on small data.
See also
[edit]- Amortized analysis
- Analysis of parallel algorithms
- Asymptotic computational complexity
- Information-based complexity
- Master theorem (analysis of algorithms)
- NP-complete
- Numerical analysis
- Polynomial time
- Program optimization
- Scalability
- Smoothed analysis
- Termination analysis — the subproblem of checking whether a program will terminate at all
Notes
[edit]- ^ "Knuth: Recent News". 28 August 2016. Archived from the original on 28 August 2016.
- ^ Cormen, Thomas H., ed. (2009). Introduction to algorithms (3rd ed.). Cambridge, Mass: MIT Press. pp. 44–52. ISBN 978-0-262-03384-8. OCLC 311310321.
- ^ Alfred V. Aho; John E. Hopcroft; Jeffrey D. Ullman (1974). The design and analysis of computer algorithms. Addison-Wesley Pub. Co. ISBN 9780201000290., section 1.3
- ^ Juraj Hromkovič (2004). Theoretical computer science: introduction to Automata, computability, complexity, algorithmics, randomization, communication, and cryptography. Springer. pp. 177–178. ISBN 978-3-540-14015-3.
- ^ Giorgio Ausiello (1999). Complexity and approximation: combinatorial optimization problems and their approximability properties. Springer. pp. 3–8. ISBN 978-3-540-65431-5.
- ^ Wegener, Ingo (2005), Complexity theory: exploring the limits of efficient algorithms, Berlin, New York: Springer-Verlag, p. 20, ISBN 978-3-540-21045-0
- ^ Robert Endre Tarjan (1983). Data structures and network algorithms. SIAM. pp. 3–7. ISBN 978-0-89871-187-5.
- ^ Examples of the price of abstraction?, cstheory.stackexchange.com
- ^ How To Avoid O-Abuse and Bribes Archived 2017-03-08 at the Wayback Machine, at the blog "Gödel's Lost Letter and P=NP" by R. J. Lipton, professor of Computer Science at Georgia Tech, recounting idea by Robert Sedgewick
- ^ an extra step is required to terminate the for loop, hence n + 1 and not n executions
- ^ It can be proven by induction that
- ^ This approach, unlike the above approach, neglects the constant time consumed by the loop tests which terminate their respective loops, but it is trivial to prove that such omission does not affect the final result
References
[edit]- Sedgewick, Robert; Flajolet, Philippe (2013). An Introduction to the Analysis of Algorithms (2nd ed.). Addison-Wesley. ISBN 978-0-321-90575-8.
- Greene, Daniel A.; Knuth, Donald E. (1982). Mathematics for the Analysis of Algorithms (Second ed.). Birkhäuser. ISBN 3-7643-3102-X.
- Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L. & Stein, Clifford (2001). Introduction to Algorithms. Chapter 1: Foundations (Second ed.). Cambridge, MA: MIT Press and McGraw-Hill. pp. 3–122. ISBN 0-262-03293-7.
- Sedgewick, Robert (1998). Algorithms in C, Parts 1-4: Fundamentals, Data Structures, Sorting, Searching (3rd ed.). Reading, MA: Addison-Wesley Professional. ISBN 978-0-201-31452-6.
- Knuth, Donald. The Art of Computer Programming. Addison-Wesley.
- Goldreich, Oded (2010). Computational Complexity: A Conceptual Perspective. Cambridge University Press. ISBN 978-0-521-88473-0.
External links
[edit]
Media related to Analysis of algorithms at Wikimedia Commons
Analysis of algorithms
View on GrokipediaFundamentals
Definition and Scope
Analysis of algorithms is the systematic study of the resource usage of algorithms, primarily focusing on their efficiency in terms of time, space, and other computational resources as the input size increases.[3] This field employs mathematical techniques to evaluate how algorithms perform under varying conditions, providing bounds on resource consumption rather than exact measurements for specific implementations.[4] The scope of algorithm analysis encompasses both theoretical upper and lower bounds on performance, as well as practical implications for selecting and optimizing algorithms in real-world systems.[3] It distinctly separates from algorithm design, which involves creating step-by-step procedures to solve problems, and from correctness verification, which confirms that an algorithm produces the expected outputs for valid inputs.[3] While design emphasizes innovation and structure, and correctness ensures reliability, analysis prioritizes quantifiable efficiency to inform decisions without requiring full code execution.[4] The formal study of algorithm analysis originated in the 1960s, pioneered by Donald Knuth, who is widely regarded as the father of the field.[5] In his seminal work, The Art of Computer Programming, Volume 1: Fundamental Algorithms (1968), Knuth introduced a rigorous mathematical approach to evaluating algorithms, shifting the emphasis from ad-hoc testing and empirical observation to precise analytical methods.[6] This foundation established analysis as a core discipline in theoretical computer science, influencing subsequent developments in computational complexity.[5] Key objectives of algorithm analysis include predicting performance characteristics prior to implementation, enabling comparisons between competing algorithms, and guiding optimizations to improve scalability for large-scale problems.[4] By abstracting away hardware-specific details, it allows researchers and practitioners to assess suitability for diverse applications, such as ensuring an algorithm remains feasible as data volumes grow exponentially.[3]Importance and Applications
Analysis of algorithms plays a pivotal role in selecting appropriate solutions for large-scale computational problems, where efficiency directly determines feasibility and performance. For instance, in database systems, developers rely on asymptotic analysis to choose between algorithms like quicksort and mergesort for sorting operations on massive datasets, ensuring that the selected method scales without excessive resource demands. Similarly, in artificial intelligence applications such as pathfinding for autonomous vehicles or robotics, analysis guides the preference for algorithms like A* over exhaustive search methods to handle complex graphs efficiently. This selection process is formalized in meta-algorithm frameworks that evaluate multiple candidates based on predicted performance metrics, enabling optimal choices for combinatorial optimization challenges.[7] In practical applications, algorithm analysis underpins critical domains including compiler optimization, network routing, and big data processing. Compiler designers use performance evaluations to apply transformations that reduce execution time in generated code, as surveyed in foundational techniques for machine-independent optimizations. In network routing, analysis of Dijkstra's algorithm ensures reliable shortest-path computations in dynamic topologies, with comparative studies confirming its superiority for large node counts in software-defined networks. For big data, MapReduce frameworks leverage algorithm efficiency assessments to process petabyte-scale datasets, where optimizations in map and reduce phases minimize shuffling overhead and enhance throughput.[8][9][10] The benefits of rigorous algorithm analysis extend to scalability prediction and economic advantages in modern computing environments. By forecasting runtime behavior on expanding datasets, analysis allows engineers to anticipate bottlenecks in applications like machine learning training, ensuring systems remain viable as data volumes grow. In cloud computing, where billing correlates directly with computational resources consumed, efficient algorithm choices yield substantial cost savings; for example, workflow scheduling optimizations can reduce expenses by selecting low-latency paths without violating deadlines.[11][12]Cost Models
Time Cost Models
In the analysis of algorithms, time cost models provide a framework for estimating the computational time required by an algorithm, typically by abstracting hardware details and focusing on the number of basic operations performed relative to input size. The most commonly used model for practical algorithm design is the unit-cost random-access machine (RAM) model, which assumes a single processor with unbounded memory accessible in constant time via direct addressing. In this model, each primitive operation—such as arithmetic additions, multiplications, comparisons, assignments, or memory reads/writes—takes a constant unit of time, regardless of operand size, provided the operands fit within a word of fixed size.[13][14] To quantify time under this model, analysts count the number of primitive operations executed as a function of the input size , often breaking down the algorithm into steps like loops, conditionals, and recursive calls. For instance, in a linear scan of an array of elements to find the maximum value, the algorithm performs approximately comparisons (one per element) and assignments (to update the current maximum), yielding a total time cost of roughly primitive operations.[15] This counting approach extends to more complex structures; for a simple loop that iterates times and executes a constant number of primitives per iteration (e.g., an addition and a comparison), the total time is given by where represents the unit cost per iteration, assuming no overhead from loop control.[14] While the unit-cost RAM model simplifies analysis by treating all primitives uniformly, it overlooks nuances in real hardware where operations on larger data may incur logarithmic overheads. The word RAM model refines this by assuming words of size bits, where basic arithmetic and bitwise operations still take constant time. Array accesses in this model also remain on average, but indirect addressing or operations on large integers (e.g., beyond word size) can add logarithmic costs, as seen in sorting algorithms where comparisons involve -bit keys.[16] For theoretical lower bounds on time complexity, particularly in proving impossibility results, the single-tape Turing machine model is often employed, where the machine has a single read-write tape and a finite-state control that moves the head in unit steps. Each transition takes unit time. The multi-tape Turing machine extends this with multiple tapes (one for input, others for work) and can simulate single-tape machines with at most a quadratic slowdown, providing better upper bounds but the single-tape model establishes tighter lower bounds for problems like palindrome recognition with time. These tape-based models are less practical for everyday algorithm design due to their sequential access.[17] Space models complement time analysis by tracking memory usage alongside these temporal costs, ensuring a holistic view of resource demands.[14]Space and Other Resource Models
Space complexity quantifies the amount of auxiliary memory required by an algorithm as a function of the input size , excluding the space needed to store the input and output themselves.[18] This measure focuses on the additional working memory, such as temporary variables or data structures created during execution, and is formally defined as representing the maximum memory usage over all possible execution paths.[19] Algorithms with low space complexity are particularly valuable in resource-constrained environments, where memory limitations can bottleneck performance as severely as time constraints. In-place algorithms exemplify optimal space usage by modifying the input data directly, achieving auxiliary space without requiring significant extra allocation.[18] Traditional space models distinguish between array-based and pointer-based representations, which affect memory overhead and access patterns. Array-based models allocate contiguous blocks of memory, enabling efficient indexing but potentially wasting space if the fixed size exceeds needs or leads to fragmentation.[20] In contrast, pointer-based models, such as those using linked lists, allow dynamic resizing but introduce extra space for storing pointers to subsequent elements, increasing the overall footprint by a constant factor per node.[20] For modern hardware with hierarchical memory systems, cache-aware models extend these by incorporating cache behavior; the ideal-cache model assumes a two-level hierarchy with fast cache of size and slower main memory, analyzing performance through the number of cache misses rather than raw memory accesses.[21] Cache-oblivious algorithms, designed without knowledge of specific cache parameters, achieve optimal miss ratios across multiple hierarchy levels by recursively dividing problems in a balanced manner.[21] Beyond memory, other resource models address auxiliary costs in specialized settings. In parallel computing, bandwidth models like LogP parameterize communication overhead with factors for latency () and gap (), where limits the rate of message transmission between processors, capturing data movement as a key bottleneck separate from computation.[22] For energy-constrained mobile devices, energy complexity evaluates power draw from operations, including leakage in idle memory and dynamic costs during computation, often modeled to guide algorithm selection for battery-limited applications.[23] These models highlight trade-offs, such as using more space to reduce energy-intensive data fetches in low-power systems. Space analysis is frequently considered alongside time models to balance overall efficiency.[23] Time-space trade-offs illustrate how increased memory can accelerate computation. In dynamic programming for problems like the longest common subsequence, a tabular approach employs space to store intermediate results, enabling time by avoiding recomputation, whereas a naive recursive formulation uses only stack space but incurs exponential time due to redundant subproblem solving.[24] This balance allows practitioners to select implementations based on available resources, prioritizing space savings in memory-tight scenarios at the cost of slower execution. Emerging quantum models treat space as the number of qubits allocated, measuring complexity through unitary transformations on a fixed qubit register; characterizations show that quantum space classes like QSPACE() capture computations reversible within qubits, though practical realizations remain theoretical due to decoherence challenges.[25]Asymptotic Analysis
Notation and Definitions
In the analysis of algorithms, asymptotic notations provide a formal framework for describing the bounding behavior of functions that model resource usage, such as time or space complexity, as the input size approaches infinity. These notations abstract away constant factors and lower-order terms to focus on the dominant growth rates, enabling comparisons of algorithmic efficiency independent of specific hardware or implementation details.[26] The most commonly used notation is Big O, denoted , which provides an upper bound on the growth of a function . Formally, as if there exist constants and such that for all , . This definition ensures that grows no faster than a constant multiple of for sufficiently large .[26] Complementing Big O, Big Omega notation, denoted , establishes a lower bound. Formally, as if there exist constants and such that for all , . Thus, grows at least as fast as a constant multiple of for large . For tight bounds that are both upper and lower, Big Theta notation, denoted , combines the previous two: if and . Equivalently, there exist constants and such that for all , . This indicates that and share the same order of growth. Stricter variants without constant multiples are the little-o and little-omega notations. Little-o, denoted , means as if for every constant , there exists such that for all , ; for example, . Similarly, little-omega, denoted , is the strict lower bound counterpart: if for every , there exists such that for all , . These emphasize that grows strictly slower or faster than .[27] Asymptotic notations satisfy several useful properties that facilitate their application. Transitivity holds: if and , then . For sums, , assuming without loss of generality that ; more generally, the sum is bounded by the larger of the two. These properties, along with compatibility with scalar multiplication ( for ), allow compositional analysis of complex functions.[28]Growth Rate Classifications
Growth rate classifications in the analysis of algorithms categorize the asymptotic upper bounds on an algorithm's time or space complexity as the input size approaches infinity, providing a framework to compare efficiency based on how resources scale. These classifications rely on big-O notation to describe dominant terms, ignoring lower-order factors and constants, which allows for a high-level understanding of performance hierarchies.[29] Common classes include constant, logarithmic, linear, polynomial, and exponential growth, each with distinct implications for practicality. Constant growth, denoted , indicates that the resource usage remains bounded by a fixed amount independent of . For example, accessing an element in an array by its index requires a constant number of operations regardless of array length.[29] This class represents the most efficient scaling, suitable for operations that do not depend on input scale. Logarithmic growth, , occurs when the resource requirements increase by a factor proportional to the logarithm of , typically arising in algorithms that repeatedly divide the problem size. Binary search on a sorted array exemplifies this, as each step halves the search space, requiring at most comparisons.[29] Such algorithms remain efficient even for very large inputs, making them ideal for hierarchical data structures. Linear growth, , scales directly with the input size, where resources grow proportionally to . A simple example is sequentially traversing a list to find an element, which examines each item in the worst case.[29] This class is acceptable for moderate inputs but can become prohibitive as grows significantly. Polynomial growth, for constant , involves resources scaling as a power of ; for instance, the bubble sort algorithm exhibits time due to nested loops comparing adjacent elements.[29] While feasible for small to moderate , higher-degree polynomials like quickly become inefficient, though they are still considered tractable in complexity theory compared to superpolynomial rates. Exponential growth, , results in resources doubling (or worse) with each unit increase in , rendering algorithms impractical beyond tiny inputs. The naive recursive solution to the subset sum problem, which explores all possible subsets, demonstrates this by evaluating combinations.[29] These classes form a strict hierarchy of growth rates: for sufficiently large , grows slower than , which is slower than , slower than any for , and all polynomials grow slower than ; further, factorials like outpace exponentials.[30] Tools like the Master Theorem classify growth for divide-and-conquer recurrences (with , ) into three cases: if for , then ; if for , then ; otherwise, if and regularity holds, then .[31]| Growth Rate | Notation | Example | Relative Growth for Large |
|---|---|---|---|
| Constant | Array access | Bounded, no increase | |
| Logarithmic | Binary search | Slow, halves per step | |
| Linear | List traversal | Proportional to | |
| Polynomial | Bubble sort | Quadratic, manageable for moderate | |
| Exponential | Naive subset sum | Rapid, infeasible for |