Algorithm Basics

An algorithm is a clear, finite set of steps for solving a specific problem.

Understanding the basic concepts of algorithms and efficiency analysis methods is an important prerequisite for learning data structures and algorithms.


Definition and Characteristics of Algorithms

In computer science, an algorithm is a finite sequence of instructions, each of which represents one or more operations.

A qualified algorithm must possess the following five basic characteristics:

CharacteristicMeaningCounterexample
InputThere are zero or more external inputsNo input is also valid, such as generating random numbers
OutputProduces at least one resultAn "algorithm" without any output is meaningless
DefinitenessThe meaning of each step must be clear and unambiguousVague descriptions like "increase appropriately" are unacceptable
FinitenessMust terminate within a finite number of steps; it cannot loop indefinitelyAn operating system itself is not an algorithm because it theoretically runs forever
FeasibilityEach step can be implemented using basic operations"Dividing by zero" is infeasible, and "sorting in one step" is also infeasible

The "finiteness" of an algorithm does not contradict the "infinite loop" of a program. A program can fall into an infinite loop due to a bug, but an algorithm itself must finish within a finite number of steps. The difference is: an algorithm is a concept at the "design intent" level, while a program is a concept at the "actual execution" level.


Algorithm Representation Methods

When designing and communicating algorithms, two representation methods are commonly used.

Pseudocode

Pseudocode uses text that is close to natural language but has a certain structured format to describe algorithm logic.

It does not depend on the syntax of any specific programming language, making it convenient for quickly expressing ideas.

For example, pseudocode for finding the maximum value in a set of numbers:

算法:FindMax
输入:数组 A,长度 n
输出:A 中的最大值

1. max = A[0]
2. for i = 1 to n-1:
3.     if A[i] > max:
4.         max = A[i]
5. return max

Flowchart

A flowchart visually displays the execution process of an algorithm through graphics.

The figure above shows the complete execution process of the FindMax algorithm: starting from the input array, initialize the current maximum, then compare each element in turn, update the maximum if a larger value is found, and output the result after the traversal is complete.

Symbol conventions in flowcharts:

Symbol shapeMeaning
Rounded rectangle (or ellipse)Start / End
RectangleProcessing step
DiamondDecision / Branch
ParallelogramInput / Output
ArrowFlow direction

Algorithm Efficiency Analysis

After designing an algorithm, its efficiency also needs to be evaluated. This is mainly measured along two dimensions.

Time Complexity

Time complexity describes the growth trend of the number of basic operations required by the algorithm as the input size n increases.

Note that we care about the "growth trend" rather than the "exact number of executions." Because for large-scale inputs, differences of constant multiples are far less important than differences in growth trends.

Space Complexity

Space complexity describes the growth trend of the additional memory space required during algorithm execution as the input size n increases.

The emphasis on "additional" space here means how much auxiliary space the algorithm needs other than the space used to store the input data itself.

Time and space are often a pair of trade-offs — trading space for time, or trading time for space, is a common trade-off strategy in algorithm design. For example: a hash table uses extra storage space in exchange for O(1) lookup time; while in-place sorting algorithms use slightly more computation steps to avoid extra space consumption.


Big O Notation

Big O NotationIt is the most commonly used mathematical tool for measuring time complexity and space complexity.

It describes the upper bound of the growth trend of running time or space usage as the input size n increases, in the worst-case scenario for an algorithm.

Definition: If there exist positive constants c and n0 such that for all n ≥ n0, T(n) ≤ c × f(n), then it is denoted asT(n) = O(f(n))。

Intuitive understanding: Big O notation ignores constant factors and lower-order terms, focusing only on the fastest-growing term.

For example: If an algorithm requires 3n² + 100n + 500 operations, when n is large enough, the n² term dominates the growth, so we say it is O(n²).

Common Complexity Levels

大 O 复杂度增长趋势对比图

The figure above intuitively shows how each complexity level changes as the input size n grows.

NotationNamen=10n=1000Typical algorithms
O(1)Constant order11Array access by index, hash table lookup
O(log n)Logarithmic order~3~10Binary search, balanced BST operations
O(n)Linear order101000Linear search, traversing an array
O(n log n)Linearithmic order~30~10000Merge sort, quick sort (average)
O(n²)Quadratic order1001000000Bubble sort, selection sort
O(2ⁿ)Exponential order1024Astronomical numberBrute-force solving subset problems

From the table, we can see that when n=1000, O(1) still requires only 1 operation, O(n) requires 1000 operations, and O(n²) requires 1 million operations.

In large-scale data scenarios, choosing an algorithm with appropriate complexity can lead to performance differences of orders of magnitude.

In actual programming, try to ensure that the complexity of core algorithms does not exceed O(n log n). When complexity reaches O(n²) or higher, special attention must be paid to whether the input size is controllable and whether there are better alternatives.


Time Complexity Calculation Examples

Below is a simple C code snippet and its time complexity analysis:

Example

#include <stdio.h>

/* Function: calculate the sum of array elements
Time complexity: O(n)
n is the array length, the loop runs n times, and each iteration performs a constant number of operations */

int sumArray(int arr[], int n) {
    int total = 0;           /* O(1) — assignment operation, executed once */
    for (int i = 0; i < n; i++) {  /* Loop body */
        total += arr[i];    /* O(1) — addition and assignment, executed n times */
    }
    return total;            /* O(1) — return operation, executed once */
}
/* Overall time complexity: O(1 + n + 1) = O(n)
Lower-order constant terms are ignored */


int main() {
    int nums[] = {5, 10, 15, 20, 25};
    int size = sizeof(nums) / sizeof(nums[0]);
    int result = sumArray(nums, size);
    printf("Sum of array elements: %d\n", result);  /* Output: Sum of array elements: 75 */
    return 0;
}

Now look at another example with nested loops:

Example

#include <stdio.h>

/* Function: print an n×n multiplication table
Time complexity: O(n²)
The outer loop runs n times, the inner loop runs n times, for a total of n×n times */

void printMulTable(int n) {
    for (int i = 1; i <= n; i++) {      /* Outer loop O(n) */
        for (int j = 1; j <= n; j++) {  /* Inner loop O(n), total n² iterations after nesting */
            printf("%d\t", i * j);      /* O(1) operation */
        }
        printf("\n");
    }
}
/* Overall time complexity: O(n × n) = O(n²) */

int main() {
    printMulTable(5);  /* Output a 5×5 multiplication table */
    return 0;
}

Space Complexity Calculation Examples

Space complexity focuses on the additional memory space allocated during the execution of an algorithm.

Example

#include <stdio.h>
#include <stdlib.h> /* Header file for malloc/free */

/* Function: create a new array storing the squares of the original array
Space complexity: O(n) — allocates a new array of length n */

int* squareArray(int arr[], int n) {
    int* result = (int*)malloc(n * sizeof(int));  /* Allocate additional space for n ints */
    if (result == NULL) {
        return NULL;  /* Return NULL if malloc fails */
    }
    for (int i = 0; i < n; i++) {
        result[i] = arr[i] * arr[i];
    }
    return result;
}
/* Space complexity O(n):
- The input array arr is not counted (it is part of the input itself)
- The result array additionally allocates n ints, which is extra space
- Variable i uses constant space O(1), dominated by O(n) */


int main() {
    int nums[] = {1, 2, 3, 4, 5};
    int n = 5;
    int* squared = squareArray(nums, n);
    if (squared != NULL) {
        for (int i = 0; i < n; i++) {
            printf("%d ", squared[i]);  /* Output: 1 4 9 16 25 */
        }
        printf("\n");
        free(squared);  /* Manually free the dynamically allocated memory */
    }
    return 0;
}

Common beginner misconceptions: Time complexity and space complexity analyze "growth trends" rather than exact values. Constant coefficients, lower-order terms, and differences between programming languages are all ignored by Big O notation. Therefore O(2n) and O(n) are equivalent—they both represent linear growth.

Other extensions