This section explores the essential mathematical foundations behind algorithm analysis, including types of functions, how they grow, and the use of factorials in evaluating computational problems.
Functions as mappings from one set to another
A function is a mathematical concept that describes how each input from one set is mapped to an output in another set. In computer science, we typically deal with functions that operate on the set of natural numbers (ℕ) or integers.
For example, if we write:
f: ℕ → ℕ
This means the function f takes a natural number as input and produces another natural number as output. The rule or logic of the function determines how the input is processed.
Practice Questions
FAQ
Base 2 is used for logarithmic functions in computing because digital computers operate using binary, which is a base-2 number system. Every operation within a computer—whether it's data storage, processing, or transmission—is ultimately performed using binary digits (bits), which can have only two states: 0 or 1. This means that when an algorithm divides a problem in half at each step (such as binary search), the number of steps required to reduce the input to 1 directly corresponds to log base 2 of the input size. While mathematically all logarithmic bases differ only by a constant multiplier (log base 2 and log base 10 grow at the same rate up to a constant factor), base 2 most accurately reflects the behaviour of algorithms in binary systems. It gives a more intuitive understanding of how many binary decisions or operations are required to reach a solution. Using base 2 also aligns better with analysis of tree structures and memory hierarchies in computer systems.
You can identify the type of a function by examining how the variable appears in the expression. For polynomial functions, the variable is raised to a fixed power, such as x, x², or x³. The exponents are constants, and the variable appears in the base position. For exponential functions, the variable appears in the exponent, such as 2^x or 3^n. This means that as the variable increases, the value of the function grows much faster than any polynomial. In factorial functions, you will see the factorial symbol (!), such as n! or (n - 1)!. This denotes the product of all integers from n down to 1, and the growth is faster than both polynomial and exponential functions. Additionally, exponential and factorial functions often appear in problems involving exhaustive search, permutations, or recursive branching, while polynomial functions are more common in simpler loop-based algorithms. Recognising these patterns helps classify the time or space complexity of algorithms.
A function with superpolynomial growth is one that grows faster than any polynomial function but may not necessarily be as fast as exponential or factorial functions. It sits between polynomial and exponential growth on the complexity scale. An example of a superpolynomial function is 2^(√n) or n^(log n), which grows more quickly than n² or n³ but not as rapidly as 2^n or n!. The term is often used in computational complexity theory to describe algorithms or problems that are not solvable in polynomial time but for which the precise classification is unclear or falls into a grey area. Unlike polynomial functions, superpolynomial functions cannot be computed efficiently for large inputs, yet they might still be more practical than true exponential or factorial algorithms. Understanding superpolynomial growth is important when assessing borderline cases where performance begins to degrade noticeably even if the function isn’t classified as exponential.
Logarithmic functions are often the result of algorithms that divide the problem size by a constant factor at each step, particularly in divide-and-conquer strategies. For example, in binary search, each recursive call halves the size of the input, reducing the search space logarithmically. This leads to a time complexity of O(log n). In recursive algorithms like merge sort, which splits the data in half at each level and then merges results, the division contributes a log n factor, and the merging contributes a linear factor, resulting in O(n log n) complexity. Logarithmic behaviour is highly efficient because the number of recursive levels grows slowly with input size. For example, even for an input of 1 million elements, only around 20 recursive divisions are needed to reach the base case. Thus, when recursion is combined with halving, it typically leads to very efficient algorithms with minimal increases in resource consumption as input sizes grow.
Understanding factorial growth is critical in combinatorial problems because these often involve examining all possible arrangements or sequences of a set of items. The number of permutations of n items is n!, and this becomes unmanageable even for small values of n. For example, with just 10 items, there are 3,628,800 possible arrangements. If an algorithm must explore each one, it will become too slow to be useful. Recognising this kind of growth helps software developers and computer scientists to avoid brute-force solutions and instead look for heuristic, approximation, or optimisation techniques. In real-world scenarios such as route optimisation, game AI, or scheduling problems, using an algorithm with factorial complexity is often impractical. Understanding factorial growth ensures that one can choose or design alternative approaches, such as greedy algorithms or genetic algorithms, that offer acceptable performance. Being able to spot factorial complexity also allows one to identify problems that may be inherently intractable or computationally expensive.
