用多线程证明 Gödel Incompleteness 是否搞错了什么

WARNING

本文不是严格的学术论文可能存在一些错漏之处见谅。

前言

Kurt Gödel 的几个著名定理构成了现代逻辑学的基石。其中Gödel 第一和第二不完备性定理更是为人所称道。

一直以来包括笔者在内的很多人都觉得 Gödel 不完备性定理的证明十分繁琐有非常复杂的技术细节所以学习起来相当吃力。在 2023 年的 ∞-type Café 暑期学校ocau 老师给出了一个非常优雅和简洁的综合 Gödel 不完备性定理证明讲座。这个讲座让我意识到Gödel 不完备性定理其实与可计算性或者说递归论有着极为密切的联系。

在这篇文章中我将结合自己对 Gödel 不完备性定理的理解介绍一个最简单也许不严格的Gödel 不完备性定理证明力图使得普通程序员在学习了简单的一阶逻辑后也能理解。

一阶逻辑

一阶逻辑First order logic, FOL是本文所讨论的逻辑系统。一阶逻辑有语法层面和语义层面如果读者没有在学校里学过一阶逻辑那么可以这么理解

  1. 一阶逻辑是一种语言和我们常见的编程语言类似它有一套形式化的语法Syntax
    • 一阶逻辑的语句可以被看作一个语法树Abstract Syntax Tree),或者是一个代数数据类型ADT
    • 这个语法树有两层一层是命题Statement),一层是表达式Expression
    • 表达式层包括变量、函数调用等等xx, f(x)f(x), g(x,y)g(x, y) 都是表达式
    • 命题层由表达式构成表达式用等号或关系拼起来构成命题比如 f(x1)=f(x2)f(x_1) = f(x_2), P(x1,f(x2))P(x_1, f(x_2))
    • 命题之间可以用逻辑连接词连接起来表达特定的含义比如 f(x1)=f(x2)P(x1,f(x2))f(x_1) = f(x_2) \land P(x_1, f(x_2)) 也是一个命题。这样的逻辑连接词还有 \rightarrow, \lor, ¬\neg
    • 命题内的自由变量free variable用量词绑定可以得到新的命题。比如 x1x2.f(x1)=f(x2)\forall x_1 \exists x_2. f(x_1) = f(x_2)
  2. 一阶逻辑这种语言需要规定其语义这是通过模型Model实现的。相关的内容比较复杂暂时不做讨论。
  3. 一阶逻辑有很多证明系统比如 Hilbert System这些证明系统和语义是独立的。简单来说一个证明系统可以通过计算给出 Γψ\Gamma \vdash \psi 这样的命题 1其中 Γ\Gamma 代表一个命题的集合也就是一堆命题ψ\psi 代表需要证明的命题。
  4. 当我们使用一阶逻辑的时候我们使用的是一阶理论Theory. 一个一阶理论由符号表 SS 和公理集合 TT 构成。符号表规定了所有在命题中可能出现的符号包括常量、函数符号、关系符号三者公理集合规定了在这个理论中不证自明的命题它们是证明系统证明的起点2。例如著名的 Peano 算术数就可以作为一个一阶理论它的符号表有
    • 常量符号 00
    • 函数符号 ++
    • 函数符号 ×\times
    • 函数符号 SS表示后继
    • 关系符号 \le

对一阶逻辑的介绍我们就进行到这里。一阶逻辑有非常丰富的数学性质这是数理逻辑课程的主要讨论对象。如果读者在阅读本文的过程中感到了不适那么不妨找一本靠谱的数理逻辑书籍比如 [1, 2] 等等学习一下相关知识。

Gödel 不完备性定理

完备性是一个一阶理论的性质。一个一阶理论 TT 是完备的就是说

对任意由该理论符号集合构造的命题 ψ\psi要么 TψT \vdash \psi要么 T¬ψT \vdash \neg \psi

换句话说在这个理论里的任何命题要么能被证明要么能被证否。

注意到这是一个纯语法概念它似乎需要指定一个特定的证明系统。不过各种一阶逻辑的证明系统如果没有特别构造那么在证明能力上都是等价的。又因为 Gödel 完备性定理的存在TψT \vDash \psi 当且仅当 TψT \vdash \psi所以这实际上也是语义概念。这样一来我们一般不需要指定一个证明系统。在 Gödel 不完备性定理的形式化 [3] 这种指定才有可能是必要的。

对于一个一阶理论我们当然期望它是完备的。例如在 Peano 算术中以下符合直觉的命题可以被证明

  • n.nS(n)\forall n. n \le S(n)
  • n1,n2.n1+n2=n2+n1\forall n_1, n_2 . n_1 + n_2 = n_2 + n_1

而相应地直觉不成立的命题可以被证否。

如果一阶理论足够简单不完备性是很容易达成的。例如考虑一个符号表为 {c,e}\{ c, e \}公理为 \emptyset 的一阶理论 AA显然有

  • A⊬c=eA \not\vdash c = e
  • A⊬ceA \not\vdash c \neq e

直观上看这是因为理论的公理集太少了。在 AA 里加入一条公理这往往被叫做扩张extensionc=ec = e就能让 AA 变为一个完备的理论。

对于任何理论 TT如果 ψ\psi 在这个理论中既不能证明又不能证否称之为 ψ\psi 独立于 TT),那么把 ψ\psi ¬ψ\neg \psi 加入 TT 的公理就可以消除这种不完备性。

这似乎揭示了一个美好的图景我们可以把整个数学建立在某种完备的一阶理论上在这个终极理论中任何命题要么能证明要么能证否。这样一来模糊、混乱的数学语言就完全可以被清晰、整洁的一阶逻辑所替代……

令人失望的是1931 Gödel 在其发表的论文 Über formal unentscheidbare Sätze der Principia Mathematica und verwandter Systeme I [13] 中证明了只要一个形式系统 TT 包含初等算术3那么在其中就一定有既不能证明也不能证否的命题。注意加公理是没有任何用处的因为加入公理后不完备性定理的前件仍然满足仍然可以推导出后件。4

这等于是给我们上面想象的美好图景判了死刑。当然我们是程序员不是数学家这和我们有什么关系呢

可判定性

Gödel 1931 年发表论文之后Church Turing 1936-1937 年分别提出了关于一阶逻辑的另一重大问题可判定性的否定证明。

前文已经说过了一阶逻辑的命题可以被看作一个语法树或字符串),而一阶理论又是符号表和公理集合在某些条件下比如下文要讲的可递归公理化),这都可以看成程序里的数据。

显然我们会问有没有一个程序 p\texttt{p}判断一个命题 ψ\psi 是否可以在 TT 内被证明

这个问题的回答是否定的没有这样的程序。证明的方法我们留到下文再谈。这个事实是稍有些反直觉的因为一个公理系统中的证明理论上来讲是可以枚举的。

怎么枚举呢考虑 Peano 算术和 Hilbert 系统。Peano 算术的公理中归纳公理是无穷多的但它有固定模式

yk.((φ(0,yk) (φ(x,yk)φ(S(x),yk)))x.φ(x,yk)) \begin{aligned} \forall \vec{y_k}. (& (\varphi(0, \vec{y_k})\ \land \\ & (\varphi(x, \vec{y_k}) \rightarrow \varphi(S(x), \vec{y_k}))) \\ & \rightarrow \forall x. \varphi(x, \vec{y_k})) \end{aligned}

这里需要枚举的就是 kk φ\varphi. kk 自不必说φ\varphi 是所有的谓词它的语法也是确定的可以用固定的规则产生。不过直接写一个 gen 函数还是相当复杂的。我们这里可以借鉴 Gödel 的做法。

首先Peano 算术的符号集是无限的但我们可以分为两个区域

  • 十个逻辑符号经过简化后
  • 无限个变量符号记作 v0v_0, v1v_1, v2v_2,

把这两个区域的符号按照先后顺序进行编码可以给出编码和解码函数。编码函数将符号映射为自然数解码函数把自然数映射为符号。

1# put '∀' to 1 , instead of 0
2LOGIC_SYM = [ 'dummy', '∀', '0', 'S', '+', '×', '(', ')', '¬', '→', '=' ]
3
4def decode(n):
5    if n <= len(LOGIC_SYM):
6        return LOGIC_SYM[n]
7    else:
8        # append '$' to variable head
9        return f"$v_{n - len(LOGIC_SYM)}"
10
11def encode(sym):
12    if sym[0] == '$':
13        # is variable, start with '$'
14        index = sym[3:]
15        return int(index) + len(LOGIC_SYM)
16    elif sym in LOGIC_SYM and sym != 'dummy':
17        return LOGIN_SYM.index(sym)
18    else:
19        # error
20        return -1

类似地一个命题也就是字符串字符的列表也可以进行编码和解码。Gödel 用的技术是这样的

[a1,a2,a3,...,an]=p1a1×p2a2××pnan \sharp [ a_1, a_2, a_3, ..., a_n ] = p_1^{\sharp a_1} \times p_2^{\sharp a_2} \times \cdots \times p_n^{\sharp a_n}

其中这里的 ai\sharp a_i 表示我们刚才定义的 encode(aᵢ). pip_i 表示第 ii 个质数

1from sympy import nextprime, Pow, factorint
2
3def encode_str(stmt):
4    pᵢ = 2
5    res = 1
6    for aᵢ in stmt:
7        res *= Pow(pᵢ, encode(aᵢ))
8        pᵢ = nextprime(pᵢ)
9    return res
10
11def decode_str(n):
12    """
13    n must be of form `Πᵢ pᵢ^{aᵢ}, aᵢ ≠ 0`, or this function is undefined
14    """
15    res = []
16    factors = factorint(n)
17    # factors is like { pᵢ : aᵢ }
18    for pᵢ, aᵢ in sorted(factors.items()):
19        res.append(decode(aᵢ))
20    return res

decode_str 的值域包括了所有的字符串。这样一来对字符串的遍历就可以用对自然数的遍历实现。

一个字符串可能不是一个合法的命题。所以还需要一个判断字符串是否是合法命题的函数。只要有了这个函数就可以枚举所有的命题

1def is_valid_stmt(stmt):
2    # this function is hard to write
3    pass
4
5def gen_all_stmts():
6    n = 0
7    while True:
8        stmt = decode_str(n)
9        if is_valid_stmt(stmt):
10            # do something here
11        n += 1

函数 is_valid_stmt 类似于编程语言的 parser虽然写起来比较困难但肯定是可以写出来的。

类似地我们可以枚举所有的证明也就是一个命题列表),进而构造一个函数判断一个命题是否可以被证明

1def is_valid_proof(stmt_l):
2    pass
3
4def encode_proof(proof):
5    # like encode_str()
6    pass
7
8def decode_proof(n):
9    pass
10
11def can_prove(ψ):
12    n = 0
13    while True:
14        stmt_l = decode_proof(n)
15        if is_valid_proof(stmt_l):
16            stmt_last = stmt_l[-1]
17            if stmt_last == ψ:
18                return True
19        n += 1

对程序 can_prove 有以下观察

  • 如果列表的最后一个就是要证明的 ψ\psi那么显然TψT \vdash \psi 是成立的。
  • 我们的枚举遍历了所有的证明所以如果 TψT \vdash \psi 也就是存在一个符合要求的证明那么那个证明一定会被找到。
  • 如果 T⊬ψT \not\vdash \psi这个算法根本不会停机它会一直循环下去。

这与停机问题的情况非常类似在可计算性理论中被称为半可判定性semi-decidable. 半可判定性是我们下面的证明中所用到的核心概念值得仔细讨论一下。

一个集合5 SS 是半可判定的当且仅当存在一个程序 p\texttt{p}使得对于所有的 eSe \in S, p(e)=True\texttt{p}(e) = \texttt{True}.

半可判定的关键是它没有讨论当 e∉Se \not\in S 时程序是否停机。虽然如果 p(e)=True\texttt{p}(e) = \texttt{True}也就是程序输出 True\texttt{True} 的时候根据当且仅当eSe \in S. 但如果输入 e1∉Se_1 \not\in Sp(e)\texttt{p}(e) 是否停机在定义中是没有提及的。所以可判定的集合一定是半可判定的集合而半可判定的集合却不一定是可判定的集合。

停机问题是半可判定的不是可判定的

证明很简单半可判定性存在的那个程序构造如下

1def p(prog, x):
2    exec(prog, x)
3    return True

如果 prog(x) 停机那么解释器 exec(prog, x) 也停机。

半可判定性有几个等价定义

  1. 递归可枚举recursively enumerable, r.e.。一个集合 SS 是递归可枚举的当且仅当 S=S = \emptyset 或存在一个递归全函数 ff使得 S={y  x.f(x)=y}S = \{ y\ |\ \exists x. f(x) = y \}也就是说SS ff 的值域.
  2. SS 的部分特征函数是部分递归的.
  3. SS 是某个部分递归函数的值域.

什么是部分函数partial function简单来说就是在某些输入上没定义的函数。一个在某些输入不停机的程序也就是 {(x,y)  xA,p(x)=y}\{ (x, y)\ |\ x \in A, \texttt{p}(x) = y \} 就是一个部分函数。

这些等价性需要证明可以参考 [1, 5].

is_valid_stmt is_valid_proof 的构造要求存在一个算法判断一个字符串在不在公理集中。这对公理集提出了要求。事实上Gödel 不完备性定理的条件中就有公理集合需要是递归集的要求递归集与可判定是等价的它在半可判定的要求上加入了对任何输入都要停机的限制。

Gödel 不完备定理的证明

我们已经知道了至少对于 Peano 算术 PA\mathsf{PA} 来说PAψ\mathsf{PA} \vdash \psi 的问题是半可判定的。根据这个结果和一点小小的编程技巧我们立刻可以得到 Gödel 不完备性定理。

首先假设 PA\mathsf{PA} 是完备的也就是说对任意的 ψ\psiPAψ\mathsf{PA} \vdash \psi PA¬ψ\mathsf{PA} \vdash \neg \psi. PA\mathsf{PA} 的半可判器为 p\texttt{p}可以构造一个 PA\mathsf{PA} 的判定器 p_total\texttt{p\_total}. 构造如下

1from multiprocessing.pool import ThreadPool
2
3def can_prove_input(ψ):
4    can_prove(ψ)
5    return ψ
6
7def p_total(ψ):
8    pool = ThreadPool(processes=2)
9    result = pool.apply_async(can_prove_input, (ψ, ¬ψ))
10    which = result.get()
11    if which == ψ:
12        return True
13    else:
14        return False

没想到吧这个程序的关键居然在于并发并发的另一个名字叫做非确定性计算其实我这里只是想用这种方法模拟非确定性计算而已。也就是同时计算 can_prove_input(ψ) can_prove_input(¬ψ)

仔细分析一下 p_total\texttt{p\_total}考虑完备性分情况讨论

  • 如果 PAψ\mathsf{PA} \vdash \psi那么 can_prove_input(ψ) 就会停机result.get() 返回 ψ所以整个函数返回 True. 注意这时 can_prove_input(¬ψ) 不会停机所以 result.get() 一定会返回 ψ.
  • 如果 PA¬ψ\mathsf{PA} \vdash \neg\psi那么 can_prove_input(¬ψ) 就会停机result.get() 返回 ¬ψ整个函数返回 False. 根据 PA\mathsf{PA} 的一致性这时 PA⊬ψ\mathsf{PA} \not\vdash \psi.

所以p_total\texttt{p\_total} 是一个 PAψ\mathsf{PA} \vdash \psi 的判定器。这样一来 PA\mathsf{PA} 就是可判定的这与不可判定的结果是矛盾的。所以假设不成立PA\mathsf{PA} 不是完备的。

读者可能不太服气这不是耍赖吗 Gödel 哪里知道什么多线程事实上非确定性图灵机和确定性图灵机是等价的多线程也可以用单线程模拟。不过我们仍然可以给出一个比较正常 p_total\texttt{p\_total}.

1def can_prove_n(ψ, n):
2    n0 = 0
3    while n0 <= n:
4        stmt_l = decode_proof(n0)
5        if is_valid_proof(stmt_l):
6            stmt_last = stmt_l[-1]
7            if stmt_last == ψ:
8                return True
9        n += 1
10    return False
11
12def p_total(ψ):
13    n = 0
14    while True:
15        if can_prove_n(ψ, n):
16            return True
17        elif can_prove_n(¬ψ, n):
18            return False
19        
20        n += 1

这里我们把原来的 can_prove(ψ) 改成了 can_prove_n(ψ, n)也就是在 nn 步之内是否可以证明 ψ\psi. 这样一来can_prove_n(ψ, n) 就一定停机所以可以在 p_total 里直接调用 can_prove_n 做顺序计算.

不可判定性证明

从上面的讨论中我们可以看到

  • 虽然半可判定性要写一个比较繁杂的程序但总体是直觉的
  • 有了半可判定性和不可判定性可以直接得到 Gödel 不完备性定理

问题的关键就成了如何得到 PA\mathsf{PA} 的不可判定性。本文并不会严格地给出证明而是简单介绍一下证明的思路。

利用 Church 的结果

要证明不可判定性我们必须引入一个比较复杂的概念可表示性representabilityGödel 本人似乎没有直接使用这个概念 [4]而是用了它的一个等价形式。

Church 1936 年的论文 [11] 里首次证明了存在一个不可判定的问题虽然有些学者不太满意他的证明),这个问题是

给定 a,bΛa, b \in \Lambda^{\circ}aβba \approx_{\beta} b 是否成立

其中Λ\Lambda^{\circ} 指的是封闭的 lambda β\approx_{\beta} 指的是 β-等价关系大概就是说aa bb 之间有一条 β\beta 规约和 β\beta 抽象构成的路径。他的文章进一步指出任何 ω-一致性的系统都无法构造判定器。[11, 12]

如果要沿用 Church 的证明那么我们首先需要把 β\approx_{\beta} 算术化。这也就是说要找到一个整数上的关系 PP使得 P(M,N)P(\sharp M, \sharp N) 当且仅当 MβNM \approx_{\beta} N其中 \sharp 表示编码。我们显然需要 PP 有一些良好的表示因为我们下面要找到一个 PA\mathsf{PA} 中的句子 P(x,y)\mathsf{P(x, y)} 使得对任意的 n1,n2n_1, n_2

P(n1,n2)PAP(n1,n2)¬P(n1,n2)PA¬P(n1,n2) \begin{aligned} P(n_1, n_2) &\rightarrow \mathsf{PA \vdash P(n_1, n_2)} \\ \neg P(n_1, n_2)& \rightarrow \mathsf{PA \vdash \neg P(n_1, n_2)} \end{aligned}

P(n1,n2)\mathsf{P(n_1, n_2)} 里的 n1\mathsf{n_1}指的是元语言里的自然数 n1n_1 PA\mathsf{PA} 里对应的符号串也就是

SSSSn1 0 \underbrace{S S S \cdots S}_{n_1 个}\ 0

P(x,y)\mathsf{P(x, y)} 是一个确定的、含有两个自由变量的 PA\mathsf{PA} 句子。这样一来

  • 假设 PA\mathsf{PA} 是可判定的对于任何的 a,bΛa, b \in \Lambda^{\circ}
  • 在判定器 p\texttt{p} 中输入 φ=P(a,b)\varphi = \mathsf{P(\sharp a, \sharp b)}$\psi = \mathsf{\neg P(\sharp a, \sharp b)} $
  • 返回 p(φ)\texttt{p}(\varphi)

显然这个程序判定了 Church 证明为不可判定的问题。实际上如果 PP TT 中可表示那么 PP 的判定问题就可以规约到 TT 的可判定性。

然而想找到 P\mathsf{P}我个人觉得是很麻烦的工作。

利用 Kleene 的结果

Gödel 1931 年的论文中证明了原始递归函数都是可表示的。关于函数的可表示性教材 [1] 和材料 [9] 的说法不太一样材料 [9] 把函数的可表示性和关系的可表示性等同了。我们暂时后者的说法6一个函数 ff 是可表示的当且仅当存在一个 P\mathsf{P}使得

f(m)=nTP(m,n)f(m)nT¬P(m,n) \begin{aligned} f(\vec{m}) = n \rightarrow T &\vdash \mathsf{P(\vec{m}, n)} \\ f(\vec{m}) \neq n \rightarrow T &\vdash \mathsf{\neg P(\vec{m}, n)} \end{aligned}

Kleene 证明了范式定理

存在一个三元原始递归函数 C(x,y,z)C(x, y, z) 和一个一元原始递归函数 U(x)U(x)使得任意的部分递归函数 ff ee 值给定时可以表示为
f(n)=U(μz[C(e,n,z)]) f(n) = U(\mu z[C(e, n, z)])

这个定理看起来挺费解的其实它描述的是一个类似于解释器的系统

1def U(x):
2    # some calculation 
3    pass
4
5def C(e, y, z):
6    # some calculation
7    pass
8
9def μ(f):
10    n = 0
11    while f(n) != 0:
12        n += 1
13    return n
14
15def f(n):
16    return n * 2
17
18def f_another(n):
19    # note: e may not be 10, we just use a fixed value here
20    return U(μ(lambda z: C(10, n, n)))
21
22# ∀ n, f(n) == f_anthor(n)

这里的 ee 大概可以理解为程序递归函数被编码之后的结果通过这个定理Kleene 用对角线方法证明了下面的定理

不是每个部分递归函数都是潜在递归potentially recursive

所谓的潜在递归就是说对于部分递归函数 gg存在一个全函数 ff使得在 gg 有定义的地方 f(n)=g(n)f(n) = g(n). 直觉上来看这个定理类似于不是所有的半可判定集都是可判定的

现在我们假设存在一个 TT 的判定器 p_t\texttt{p\_t}根据 Gödel 的证明存在一个公式 C(e,n,z,t)\mathsf{C(e, n, z, t)} 表示了 C(e,n,z)C(e, n, z). 那么可以构造一个新的超级解释器它能给所有的部分递归函数 gg 都找到对应的全函数 ff. 考虑任意的 gg ee 值为 e1e_1构造 ff 如下

1def f(n):
2    if p_t(∀ z. ¬C(e₁, n, z, 0)):
3        # g(n) can never halt
4        # return a random value, doesn't matter
5        return 0
6    else:
7        # g(n) will halt
8        # return g(n)
9        return U(μ(lambda z: C(e₁, n, z)))

Kleene 的定理保证了gg 唯一不停机的方式就是 z.C(e1,n,z)0\forall z. C(e_1, n, z) \neq 0. 所以因为 CC 本身是可以在 TT 里表示的原始递归函数一旦 TT 可判定对于任意 nng(n)g(n) 是否停机就是可判定的。

Gödel 原始证明的比较

Gödel 的原始证明 [1, 13] 相当复杂大概可以概括为

  1. 证明所有的原始递归函数是可表示的。
  2. 将一阶逻辑和初等算术的语法用原始递归函数算术化我们这里通过写 can_prove_n(ψ, n) 解决Gödel 则是定义了初等递归函数并且定义了对应的初等递归关系。他写了 45 个递归关系最终定义了可证明性著名的 Bew
  3. 由于 Bew 是原始递归关系所以是可表示的。这样一来能找到一个公式 P(ψ)\mathsf{P(\sharp\psi)} 表示它。
  4. Gödel 证明了不动点定理对任意的一元公式 ψ(x)\psi(x)都能找到一个语句 σ\sigma使得 Tσψ(σ)T \vdash \sigma \leftrightarrow \psi(\mathsf{\sharp\sigma}).
  5. 所以存在 σ\sigma 使得 Tσ¬P(σ)T \vdash \sigma \leftrightarrow \neg \mathsf{P(\sharp \sigma)}注意这个句子构造了类似于说谎者悖论的命题。
  6. 利用 ω-完备性无论假设 TσT \vdash \sigma 还是 T¬σT \vdash \neg \sigma都会得到矛盾。这样一来σ\sigma TT 就是独立的。

可见我们的证明没有避免 1. 2.而这是最复杂的步骤。不过半可判定性直觉理解起来比较简单如果把它当前提那么证明会非常简短。1. 2. 其实都是为了证明半可判定性引入的。

延伸阅读

  • 如果你想学习 Gödel 的原始证明那么请直接阅读 [1] Gödel 的原始论文 [13]. 杨睿之老师的书写得非常好。
  • 这种证明的思路最早似乎来源于 Kleene可以参考他的著作 [10] 和这份笔记 [9].
  • 这篇文章 [4] 也用这种思路介绍不完备性定理
  • ocau 老师的 Agda 形式化值得参考不过他讲课的视频没有公开原因不明。他的思路可能来源于这份 Coq 形式化的工作 [6]可以直接阅读这篇论文作为替代。
  • 一个 Gödel 不完备性定理的完全 Coq 形式化 [3]上面的论文是综合式的和这里讨论的有比较大的区别
  • 有两本关于 Gödel 不完备性定理的书 [2, 8] 写得都还不错可以选部分章节阅读。
  • 关于递归函数相关的知识可以参考 [1, 10] 和宋老师的 [7]. 类似地可计算性相关的概念可以参考 [5]

参考文献