← 目录 / 算法文档 / A00 · 什么是算法

A00 什么是算法

什么是算法?怎么判断一个算法的好坏?在深入学习具体算法之前,先建立起这两个最基本的认识。

本页目录
A00.1 什么是算法

算法是解决某类问题的一套明确的步骤。它必须满足三个条件:每一步都足够具体(不能模糊),步骤有限(不能无限执行),最终有结果(不能没有输出)。

你每天都在用算法:查字典时从中间翻开缩小范围,这是二分查找;整理扑克牌时把新摸的牌插到合适位置,这是插入排序;导航时选最短路,这是最短路径算法。

在竞赛中,算法的核心问题只有两个:能不能做到,以及快不快、省不省内存

💡
算法 vs 代码
算法是解题的思路和步骤,代码是把这个思路翻译成计算机能执行的语言。同一个算法可以用 C++、Python、Java 实现,结果相同,速度略有差异。学算法的重点在于理解思路,而不是背代码。
A00.2 用一道题感受算法的差异

问题:给你一个 1 到 n 的整数,求它们的总和。

这个问题有两种做法,效率天壤之别:

C++ · 方法一:逐个累加
1int sum = 0;
2for (int i = 1; i <= n; i++)
3 sum += i; // 执行 n 次加法
C++ · 方法二:高斯公式
1int sum = n * (n + 1) / 2; // 执行 1 次乘法、1 次加法、1 次除法

当 n = 10 时,两种方法都很快,看不出差别。但当 n = 1,000,000,000(十亿)时,方法一需要执行十亿次加法——耗时约几秒;方法二仍然只需要 3 次运算,瞬间出结果。

这就是算法好坏的核心体现:随着问题规模增大,不同算法的速度差距会急剧拉开。

A00.3 时间复杂度:算法有多快

我们用时间复杂度来描述算法的速度,写作 O(...)(读作"大O")。它表示随着数据规模 n 增大,算法所需操作次数的增长趋势。

注意:时间复杂度不是精确的运行时间,而是一种量级估算——我们只关心最主要的增长因素,忽略常数和低阶项。

n(数据规模) 操作次数 0 250 500 750 1000 O(1) O(log n) O(n) O(n log n) O(n²) 可接受 越平坦的曲线代表越高效的算法——随 n 增大,操作次数增长越慢
五种常见时间复杂度的增长曲线。O(1) 最理想(常数时间),O(n²) 增长最快。
虚线以上区域表示当 n 较大时算法会变得很慢,在竞赛中通常会超时。

怎么计算时间复杂度?只需要数"代码里最深层的循环大约执行了多少次":

C++ · 从代码结构判断复杂度
1// O(1):没有循环,操作次数固定
2int sum = n * (n + 1) / 2;
3
4// O(n):一层循环,执行 n 次
5for (int i = 0; i < n; i++) { ... }
6
7// O(n²):两层嵌套循环,执行 n×n 次
8for (int i = 0; i < n; i++)
9 for (int j = 0; j < n; j++) { ... }
10
11// O(log n):每次把范围缩小一半(如二分查找)
12while (lo <= hi) {
13 int mid = (lo + hi) / 2;
14 if (...) lo = mid + 1; else hi = mid - 1;
15} // 每次范围减半,最多执行 log₂n 次
A00.4 空间复杂度:算法用多少内存

除了速度,算法还需要内存。空间复杂度描述算法需要多少额外内存,同样用 O(...) 表示。

⏱️ 时间复杂度
算法执行的操作次数随 n 的增长趋势。
操作次数越少,程序跑得越快。
竞赛中一般要求 1 秒内不超过约 10⁸ 次操作。
💾 空间复杂度
算法使用的额外内存量随 n 的增长趋势。
内存越少,越不容易 MLE(内存超限)。
竞赛中一般限制 256MB,约能存 6400 万个 int。
💡
时间换空间 vs 空间换时间
这两者经常需要权衡:前缀和用 O(n) 额外空间,换来 O(1) 的查询时间;暴力枚举不用额外空间,但时间可能很慢。竞赛里通常时间限制更严,所以往往愿意多用一点内存换速度。
A00.5 常见复杂度速查
复杂度名称典型场景n=10⁶ 时的操作次数
O(1) 常数时间 数组下标访问、数学公式 1 次
O(log n) 对数时间 二分查找、倍增 约 20 次
O(√n) 平方根时间 质数判断、分块 约 1000 次
O(n) 线性时间 一次遍历、前缀和 100 万次
O(n log n) 线性对数 sort、归并排序 约 2000 万次
O(n²) 平方时间 冒泡排序、暴力枚举对 1 万亿次 ❌
O(2ⁿ) 指数时间 枚举所有子集(n 很小时) 远超宇宙原子数 ❌
A00.6 竞赛中的估算方法

竞赛中,题目通常会给出数据范围(如 1 ≤ n ≤ 10⁵)。拿到题目后,先看数据范围,就能大致判断需要哪个量级的算法:

n ≤ 12
O(n!) 或 O(2ⁿ)
全排列、枚举子集
n ≤ 10³
O(n²) 可接受
暴力枚举对、简单 DP
n ≤ 105
需要 O(n log n)
排序、前缀和、二分
n ≤ 10⁷
需要 O(n) 或更优
线性筛、双指针
⚠️
竞赛粗估规则:1 秒大约能执行 10⁸(一亿)次基本操作(实际因机器和操作类型有差异)。拿到 n 的范围后,把你想到的算法复杂度代入计算,如果超过 10⁸ 就需要换更快的思路。
读题 找数据范围 n 估算复杂度 O(n²) 代入 n=10⁵ → 10¹⁰ 超过 10⁸? 10¹⁰ >> 10⁸,超时 换更快的算法 O(n log n) → 约 1.7×10⁶ ✅ 拿到题目先看 n,再估算你想用的算法是否在时限内
竞赛解题流程:看数据范围 → 估算操作次数 → 判断是否超时 → 调整算法
这个估算过程比写代码更重要——先想清楚再动手,避免白写一个必然超时的解法。
A00.7 从这里开始学什么

本算法文档共 18 个模块,按难度和依赖关系由浅入深排列。建议按顺序学习,每个模块都配有原理讲解、代码实现和执行过程图示。

🗺️
推荐学习路径
・先打好基础:01 数学基础 → 02 基础算法思想 → 03 排序,这是后续所有内容的地基。
・核心竞赛技巧:05 区间处理 → 07 双指针与二分 → 12 动态规划基础,CSP-J 高频考点。
・进阶方向:11 搜索 → 14 图论 → 15 树状数组/线段树,NOIP 及以上的必备内容。
・每读完一个模块,找 2~3 道对应题目练手,比单纯看文档有效得多。
🎯
学算法最重要的事:理解"为什么这样做",而不是背代码模板。每个算法背后都有一个核心观察——找到它,其余的细节自然就记住了。