时间复杂度入门 - 什么是 P 问题

中文 | English

计算复杂度基础概念简介

在进入 NP、NP 完全性、多项式规约这些更深的主题之前,首先需要建立一套共同语言: 什么是“算法的复杂度”?什么叫“多项式时间”?为什么有些算法明明很快,却不算多项式?

先介绍三个最重要的复杂度概念:

  • 多项式时间(Polynomial Time)

  • 伪多项式时间(Pseudo-Polynomial Time)

  • 弱多项式时间(Weakly Polynomial Time)

这三者会在后面内容重复出现!

[!note] 后面“多项式时间”可能都会缩写成 poly-time,伪多项式时间可能会缩写成 pseudo-poly-time, 弱多项式时间可能会写成 weakly-poly-time. 这个写法不一定专业,只是为了后面简单而已。

为什么要强调 poly-time?

在理论计算机科学中,我们普遍把 poly-time 作为“有效算法”的判断标准。

因为:

  1. 多项式时间算法的增长速度(如 $n$、$n\log n$、$n^2$、$n^3$)都可控;

  2. 换电脑、换语言、换常数,不会影响其“级别”;

  3. 多项式时间问题 = 工程上可扩展的问题;

例如:

时间复杂度
是否可行
评价

$O(n)$

linear time, 非常快

$O(n\log n)$

sort alg.级别的, 快

$O(n^2)$

这些都是没有问题的

$O(n^7)$

仍然是poly-time, 这些都是可控的

$O(2^n)$

指数爆炸

$O(n!)$

爆炸

因此,P 类问题(Polynomial-Time Problems) 代表“可在合理时间内解决的问题”。

P 类问题

一个算法是多项式时间的,如果运行时间满足:$O(n^k)$, 其中 k 是某个常数。那么这个算法可以解决的问题,可以被称之为一个P问题。

[!important] 注意,不是算法是P,而是这个问题,因为可以被poly-time的算法解决,所以这个问题属于P类问题。

经典多项式时间算法例子

  1. Dijkstra 最短路径(非负权图)

  2. 最大流(Max-Flow)问题

    • 算法

      时间复杂度

      类型

      Ford–Fulkerson

      $O(C m)$

      pseudo-polynomial

      Scaled version (Capacity Scaling)

      $O(m² log C)$

      weakly polynomial

      Edmonds–Karp

      $O(n m²)$

      strongly polynomial

      Orlin + KRT (改进版)

      $O(n m)$

      strongly polynomial

      Modern Approximation methods

      $≈ O(m log n + m^{4/3})$

      almost linear time

    • 所以 Max Flow Problem 也是典型的 P 类问题

  3. 最小生成树(MST)

这些算法构成了“可高效求解”的基石。

伪多项式时间 (Pseudo-Polynomial Time)

一句话定义:

伪多项式算法的运行时间依赖于输入数字的“数值大小 $V$”,而不是其“二进制位数 $\log V$”。

听起来抽象,但例子一看就懂。

经典例子:0/1 背包动态规划

01 Knapsack Problem DP 时间复杂度:$O(nW)$

其中:

  • n = 物品数量

  • W = 背包容量(数值大小)

注意:

$W = 10^9$ 的 bit-length 只有约 30 bits。

但是算法需要跑 十亿步。

因此,不是真正意义上的 polytime。

上面的解释是我当初不理解的时候,问大模型,大模型的解释。

这样解释还是很奇怪,我个人的理解是:

比如不同路径arrow-up-right这个题目,和背包问题都是二维数组的DP,为什么前者是 poly-time, 后者不是? 因为在不同路径这题中,我们的输入,是二维的!而在背包问题中,W容量只是一个数字! 在不同路径这个问题中,如果你想要DP数组多一行,你就要把地图扩展一行,所以输入和输出的变化是同一量级的! 在背包问题中,想要DP数组多一行,我们只需要改一个数字W,让他加1就行了,而不需要输入多加一行这么个量级。 所以在背包问题中,一个小小数字的+1,就会导致DP数组多加一行,这种变化其实是非常大的。

不知道这样能否让大家理解。

为什么叫“伪 (pseudo)”?

因为:

  • 当 W 很小的时候,看起来像 $O(nW)$ 很快 → 像多项式

  • 当 W 很大(但 bit-length 也很小)时 → 实际是指数级时间

例如:

W
bit-length
DP 时间
评价

1000

10 bits

$O(n·1000)$

可行

10⁹

30 bits

$O(n·10^9)$

不可行

2¹⁰⁰

100 bits

$O(n·2¹⁰⁰)$

爆炸

伪多项式算法常出现于 NP-Hard 问题的 DP 解法中,如:

  • Knapsack

  • Subset Sum

关于 NP难 是什么意思,我后面会继续讲解的。

弱多项式时间 (Weakly Polynomial Time)

弱多项式时间介于 “真正多项式” 与 “伪多项式”之间。

一句话定义:

弱多项式时间算法依赖于 n 与输入数字的 bit-length ($\log V$),而不是 $V$ 本身。

因此:

  • weak-poly 属于 P(多项式)

  • pseudo-poly 不属于 P

经典例子:线性规划(Linear Programming, LP)

LP 的求解算法(例如 Ellipsoid、Interior-Point)运行时间大致为:$\operatorname{poly}(n, B)$。

其中:

  • n = 变量与约束的数量

  • B = 输入数字的 bit-length(例如数字有 10 位,就 B=10)

强多项式时间 (Strongly Polynomial Time)

强多项式时间算法的定义:

$\operatorname{poly}(n) \quad \text{且不依赖} \log V \text{ 或 V 本身}$

纯粹依赖输入结构,而不是数字大小。

经典例子

  • BFS / DFS

  • Prim / Kruskal

  • 匹配(Hopcroft–Karp 等)

  • 部分最大流算法(如 Orlin)

强多项式算法是最稳健的复杂度类型。

三种复杂度的终极对比(非常重要)

下表是理解复杂度结构最关键的一个表格:

类型

运行时间依赖

示例

属于 P?

强多项式 (Strong Poly)

$\operatorname{poly}(n)$

MST、匹配

✔ 是

弱多项式 (Weak Poly)

$\operatorname{poly}(n, log V)$

线性规划 LP

✔ 是

伪多项式 (Pseudo-Poly)

$\operatorname{poly}(n, V)$

背包 DP

✘ 不是(除非数字很小)

[!tip] 伪多项式算法看起来“快”,但实际上对 bit-length 不能保证多项式增长,因此 NP-hard 问题不被视为已解决。

Last updated