数理逻辑
数理逻辑是用数学方法研究推理规律的学科。它为数学证明提供了精确的语言和规则,也为计算机科学中的程序验证、人工智能推理、数据库查询等领域奠定了理论基础。本章将从最基础的命题逻辑开始,逐步深入到谓词逻辑和证明方法。
命题逻辑
命题逻辑(Propositional Logic)研究的是命题之间的逻辑关系。它是逻辑学中最简单也最基础的部分。
什么是命题?
命题(Proposition)是一个能够判断真假的陈述句。命题的关键特征是它必须有确定的真值——要么为真(True,记作 T 或 1),要么为假(False,记作 F 或 0)。
命题的例子:
- "2 + 2 = 4" —— 这是一个命题,真值为真
- "北京是中国的首都" —— 这是一个命题,真值为真
- "1 + 1 = 3" —— 这是一个命题,真值为假
不是命题的例子:
- "x + 1 = 2" —— 不是命题,因为 x 的值不确定,无法判断真假
- "今天天气真好啊!" —— 不是命题,这是感叹句,不是陈述句
- "你在说什么?" —— 不是命题,这是疑问句
命题通常用小写字母 , , 等表示。例如,设 表示命题"今天下雨",那么我们可以讨论 的真值。
命题联结词
单个命题的作用有限。通过联结词,我们可以将简单命题组合成复合命题,表达更复杂的逻辑关系。
否定(Negation)
否定是一元运算,将命题的真值取反。命题 的否定记作 (读作"非 p")。
| T | F |
| F | T |
例如,设 表示"今天是周末",则 表示"今天不是周末"。
合取(Conjunction)
合取就是逻辑"与",当且仅当两个命题都为真时,合取结果为真。 和 的合取记作 (读作"p 且 q")。
| T | T | T |
| T | F | F |
| F | T | F |
| F | F | F |
生活中的例子:"我有电脑 并且 我会编程"——只有两个条件同时满足,整个命题才为真。
析取(Disjunction)
析取是逻辑"或",只要有一个命题为真,析取结果就为真。 和 的析取记作 (读作"p 或 q")。
| T | T | T |
| T | F | T |
| F | T | T |
| F | F | F |
注意区分"或"的两种含义:这里的"或"是可兼的(inclusive or),即两者都可以为真。日常生活中也有"要么...要么..."的表达,那是异或(exclusive or),表示两者恰有一个为真。
异或(Exclusive OR)
异或表示两个命题恰好有一个为真。记作 或 。
| T | T | F |
| T | F | T |
| F | T | T |
| F | F | F |
异或在计算机中应用广泛,例如校验位的计算。在编程中,异或运算符通常写作 ^:
# 异或运算
print(True ^ True) # False
print(True ^ False) # True
蕴含(Implication)
蕴含表示"如果...那么..."的关系。 读作"如果 p,则 q"或"p 蕴含 q"。其中 称为前件(antecedent), 称为后件(consequent)。
| T | T | T |
| T | F | F |
| F | T | T |
| F | F | T |
蕴含的真值表可能令人困惑:为什么前件为假时,整个蕴含式为真?这称为"空真"(vacuously true)。可以这样理解:
"如果我今天中奖,我就请你吃饭。"
- 如果我今天中奖了( 为真)并且我请你吃饭了( 为真),我没有违背承诺,命题为真。
- 如果我今天中奖了( 为真)但我没有请你吃饭( 为假),我违背了承诺,命题为假。
- 如果我今天没中奖( 为假),无论我是否请你吃饭,我都算不上违背承诺。因为没有触发条件,承诺自然成立。
等价(Biconditional)
等价表示"当且仅当"的关系,记作 。当 和 真值相同时,等价式为真。
| T | T | T |
| T | F | F |
| F | T | F |
| F | F | T |
例如:"一个三角形是等边三角形,当且仅当它的三个角相等。"
运算优先级
当表达式中有多个运算符时,需要遵循优先级规则:
- (否定)—— 最高优先级
- (合取)
- (析取)
- (蕴含)
- (等价)—— 最低优先级
例如, 应理解为 。为了清晰起见,建议在复杂表达式中使用括号。
真值表
真值表是分析复合命题的工具。通过列出所有可能的真值组合,我们可以判断一个命题是永真式、矛盾式还是可满足式。
永真式(Tautology):无论命题变量的真值如何,整个命题恒为真。例如 (排中律)。
矛盾式(Contradiction):无论命题变量的真值如何,整个命题恒为假。例如 (矛盾律)。
可满足式(Contingency):至少有一种真值组合使命题为真。
让我们构造 和 的真值表,看看它们是否等价:
| T | T | T | F | T |
| T | F | F | F | F |
| F | T | T | T | T |
| F | F | T | T | T |
可以看到 和 的真值完全相同,因此它们是逻辑等价的。这是一个重要的等价关系,常用于证明中。
逻辑等价
两个命题 和 逻辑等价,记作 ,当且仅当它们的真值表完全相同。以下是常用的逻辑等价式:
德摩根律(De Morgan's Laws)
德摩根律描述了否定如何分配到合取和析取中。在编程中,这常用于简化条件表达式:
# 这两个条件是等价的
not (a > 0 and b > 0) # 等价于
a <= 0 or b <= 0
分配律
吸收律
蕴含等价式
这个等价式非常重要,它将蕴含转化为更容易处理的形式。
逆否命题
逆否命题与原命题等价,这是证明中常用的技巧。要证明"如果 则 ",可以改为证明"如果非 则非 "。
对偶原理
将一个命题中的 与 互换,T 与 F 互换,得到的新命题称为原命题的对偶。如果原命题是永真式,则其对偶也是永真式。
例如, 的对偶是 。
谓词逻辑
命题逻辑的表达能力有限。例如,"所有人都会死"这样的陈述,无法用命题逻辑精确表达。谓词逻辑(Predicate Logic,也称一阶逻辑)引入了谓词、量词等概念,大大增强了表达能力。
谓词
谓词是描述对象性质或关系的函数。设 表示" 是偶数",则 为真, 为假。 就是谓词, 是变量。
一元谓词描述单个对象的性质,如" 是素数"。
多元谓词描述对象之间的关系,如" 大于 ",记作 。
谓词可以与命题联结词组合使用。例如,设 表示" 是学生", 表示" 喜欢学习",则 表示" 是学生且喜欢学习"。
量词
量词用来描述谓词中变量的范围。
全称量词
全称量词 (读作"对于所有")表示某个谓词对所有对象都成立。
表示"对于所有 , 都成立"。
例如,设论域为全体实数, 表示"所有实数的平方都非负"。
存在量词
存在量词 (读作"存在")表示至少存在一个对象使谓词成立。
表示"存在一个 ,使得 成立"。
例如, 表示"存在一个数,其平方等于 2"。
量词的否定
量词的否定遵循以下规则:
理解这两条规则:否定"所有人都喜欢学习",等于说"存在一个人不喜欢学习";否定"存在一个完美的人",等于说"所有人都不完美"。
嵌套量词
量词可以嵌套使用。嵌套量词的顺序很重要:
- :对每个 ,存在一个 (可以依赖 ),使 成立
- :存在一个 ,对所有的 , 都成立
这两者含义不同。以"每个人都有自己的父亲"为例:
- :对每个人 ,存在一个 是 的父亲。这是正确的。
- :存在一个人 ,他是所有人的父亲。这显然是错误的。
论域
量词的作用范围称为论域(Domain of Discourse)。明确论域对理解命题至关重要。
例如, 的真值取决于论域:
- 如果论域是正整数集合,则命题为真
- 如果论域是全体整数,则命题为假
在实际应用中,可以用条件限制来指定论域:
表示"对所有正数 , 成立"
表示"存在一个正数 ,使 成立"
注意全称量词用蕴含,存在量词用合取——这是书写习惯,也是逻辑需要。
证明方法
证明是建立数学命题真实性的过程。掌握证明方法,是学习离散数学的核心目标之一。不同的命题结构需要不同的证明策略,选择合适的策略往往能大大简化证明过程。
直接证明
直接证明从前提出发,通过逻辑推理直接得出结论。证明 时,假设 为真,然后推导 为真。
例题:证明"如果 是奇数,则 也是奇数"。
证明:
假设 是奇数。根据奇数的定义,存在整数 ,使得 。
那么 。
令 ,则 ,这正是奇数的形式。
因此, 是奇数。证毕。
反证法
反证法(Proof by Contradiction)假设结论为假,然后推导出矛盾,从而证明结论必须为真。
适用场景:
- 直接证明困难或复杂
- 结论的否定更容易处理
- 想要证明"不存在"或"不可能"
例题:证明 是无理数。
证明:
假设 是有理数。那么存在互质的正整数 和 ,使得 。
两边平方得 ,即 。
这意味着 是偶数,因此 是偶数(因为奇数的平方是奇数)。
设 ,代入得 ,即 ,化简得 。
同理, 是偶数,因此 是偶数。
现在 和 都是偶数,与它们互质的假设矛盾。
因此, 是无理数。证毕。
逆否证明
逆否证明基于等价关系 。当直接证明困难时,证明逆否命题往往更简单。
例题:证明"如果 是偶数,则 是偶数"。
直接证明需要涉及复杂的分解。使用逆否证明:
逆否命题是"如果 是奇数,则 是奇数"——这正是我们前面直接证明过的结论。
因此原命题成立。
何时使用逆否证明:当前提比结论更难直接处理时,逆否命题可能更易证明。
数学归纳法
数学归纳法(Mathematical Induction)是证明与自然数相关命题的重要方法。它包含两个步骤:
- 基础步骤(Base Case):证明当 (起始值)时命题成立
- 归纳步骤(Inductive Step):假设当 时命题成立(归纳假设),证明当 时命题也成立
例题:证明对所有正整数 ,有 。
证明:
基础步骤:当 时,左边 ,右边 。命题成立。
归纳步骤:假设当 时命题成立,即 。
当 时:
这正是 时的公式。因此,由数学归纳法,命题对所有正整数成立。证毕。
强归纳法
强归纳法(Strong Induction)在归纳步骤中,假设对所有小于等于 的值命题都成立,而不仅仅是 。
适用场景:
- 证明中需要引用多个前面的情况
- 递归定义的结构
例题:证明每个大于 1 的整数都可以表示为素数的乘积。
证明:
基础步骤: 是素数,可以表示为自身的乘积。
归纳步骤:假设对所有 的整数, 都可以表示为素数的乘积。
考虑 :
- 如果 是素数,它本身就是素数的乘积
- 如果 是合数,则 ,其中
由归纳假设, 和 都可以表示为素数的乘积,因此 也可以。证毕。
构造性证明
构造性证明通过具体构造一个对象来证明存在性命题。要证明 ,我们显式地给出一个满足 的 。
例题:证明存在两个无理数 和 ,使得 是有理数。
证明:
考虑 。
情况 1:如果 是有理数,则 , 即为所求。
情况 2:如果 是无理数,令 ,。则:
这是有理数。
无论哪种情况,都存在满足条件的无理数。证毕。
存在性证明
存在性证明有两种主要方法:
构造性证明:直接给出满足条件的对象。
非构造性证明:证明满足条件的对象必须存在,但不具体构造它。常用方法包括:
- 反证法
- 鸽巢原理
- 计数论证
例题:证明存在无理数 和 使 为有理数(非构造性版本)。
上面给出的证明虽然明确,但实际上没有告诉我们到底是哪种情况( 是有理数还是无理数)。这就是非构造性证明的特点:我们确定解存在,但不一定知道具体的解是什么。
唯一性证明
要证明"存在唯一的 使得 ",需要两步:
- 存在性:证明存在 使得 成立
- 唯一性:证明如果 且 ,则
例题:证明每个整数有唯一的加法逆元。
证明:
存在性:对于整数 ,取 。则 ,所以逆元存在。
唯一性:假设 和 都是 的加法逆元,即 且 。
从 得 。 从 得 。
因此 。证毕。
分情况证明
当命题涉及多种情况时,可以分别证明每种情况,然后综合得出结论。
例题:证明对于任意整数 , 是偶数。
证明:
情况 1: 是偶数。 设 ,则 ,是偶数。
情况 2: 是奇数。 设 ,则 ,是偶数。
由排中律, 要么是偶数要么是奇数,因此 总是偶数。证毕。
空真证明
要证明 ,如果 为假,则整个蕴含式为真(空真)。这在证明全称命题时特别有用。
例题:证明"所有会飞的猪都是粉红色的"。
证明:论域中不存在会飞的猪,因此对于所有 ,前提" 是会飞的猪"为假。所以蕴含式"如果 是会飞的猪,则 是粉红色的"对任意 都为真。证毕。
证明策略选择指南
| 命题形式 | 推荐策略 |
|---|---|
| 直接证明、逆否证明、反证法 | |
| 任取 ,证明 | |
| 构造性证明或非构造性证明 | |
| 存在性 + 唯一性 | |
| 反证法 | |
| 证明 和 | |
| 与自然数相关 | 数学归纳法 |
常见证明错误
循环论证:在证明中使用了要证明的结论。这是最常见的逻辑错误。
例子:证明 。 设 ,则 ,,,,,。 错误:当 时,,除以 是除以零,无效。
不当推广:从特殊情况错误地推广到一般情况。
忽略边界:归纳证明中忽略基础步骤,或递归定义中忽略基本情况。
推理规则
推理规则是从已知命题推导新命题的规则。正确的推理保证如果前提为真,则结论必然为真。
命题逻辑推理规则
假言推理(Modus Ponens)
如果"如果下雨则地湿"为真,且"下雨了"为真,则"地湿"为真。
拒取式(Modus Tollens)
如果"如果下雨则地湿"为真,且"地没湿"为真,则"没下雨"为真。
假言三段论
这是蕴含的传递性。
析取三段论
"或者今天是周末,或者是工作日"且"今天不是周末",则"今天是工作日"。
谓词逻辑推理规则
谓词逻辑引入量词后,需要新的推理规则来处理量化的陈述。核心思想是:通过实例化将量化的陈述转化为具体陈述,在布尔逻辑中推理,然后再推广回量化陈述。
全称实例化(Universal Instantiation)
从全称命题推出具体实例:
含义:如果 对所有 都成立,那么对任何特定的 , 也成立。
例子:
- 前提:所有人都会死。
- 推论:苏格拉底是人 苏格拉底会死。
注意: 可以是论域中的任意元素,包括具体个体或任意选择的元素。
# 全称实例化的直观理解
def universal_instantiation(all_statement, element):
"""
如果 P(x) 对所有 x 成立,则 P(c) 对特定 c 成立
"""
# all_statement 是一个函数,接受任意元素返回 True
return all_statement(element)
# 例子:所有正整数都有后继
def has_successor(n):
return n + 1 > n # 显然成立
# 实例化:5 有后继
print(has_successor(5)) # True
全称推广(Universal Generalization)
从关于任意元素的陈述推出全称命题:
限制条件:
- 必须是任意元素(arbitrary element)
- 不能在任何前提或未释放的假设中出现
含义:如果我们对任意选择的 (没有任何特殊性质)证明了 ,那么 对所有 成立。
例子:证明所有偶数的平方是偶数。
- 设 是任意偶数(这里 是任意元素)
- 对于某个整数
- 所以 是偶数
- 由全称推广:所有偶数的平方都是偶数
关键:在步骤 1 中, 是任意选择的,没有特殊性质,因此可以推广。
存在实例化(Existential Instantiation)
从存在命题推出具体实例:
限制条件:
- 必须是新引入的符号,之前未使用过
- 代表"某个满足 的元素",但我们不知道具体是哪个
含义:如果存在某个 使 成立,那么我们可以给这个元素一个名字 ,并断言 。
例子:
- 前提:存在一个最大的素数。
- 实例化:设 是最大的素数。
注意:使用星号标记存在实例化引入的元素,以区别于任意元素。
存在推广(Existential Generalization)
从具体实例推出存在命题:
含义:如果 对某个具体元素 成立,那么存在 使 成立。
例子:
- 前提:7 是素数。
- 推广:存在一个素数。
实例化与推广的关系
这四条规则形成两对互逆的操作:
关键区别:
- 全称实例化可以实例化为任意元素 (包括已知的具体元素或新引入的任意元素)
- 存在实例化必须使用新的符号 ,因为"那个存在的元素"是未知的
推理规则的正确使用顺序
在证明中,正确的实例化顺序至关重要:
规则:先进行存在实例化,再进行全称实例化。
原因:全称实例化可以选择任意元素,而存在实例化必须选择新元素。如果先做全称实例化,可能已经"占用"了某个符号,导致存在实例化无法使用该符号。
例子:证明"如果存在猫,且所有猫都会爬树,则存在会爬树的动物。"
前提:
正确证明:
[1] ∃x Cat(x) 前提
[2] ∀x (Cat(x) → ClimbTree(x)) 前提
[3] Cat(c*) 存在实例化 [1]
[4] Cat(c*) → ClimbTree(c*) 全称实例化 [2],使用同一个 c*
[5] ClimbTree(c*) 假言推理 [3], [4]
[6] ∃x ClimbTree(x) 存在推广 [5]
错误示范:如果先做全称实例化 [2],得到 ,再做存在实例化 [1],必须用 ,得到 。现在 和 无法关联,推理失败。
量词交换规则
除了实例化和推广规则,还有两条重要的等价规则:
量词否定(Quantifier Negation):
直观理解:
- "并非所有人都喜欢学习" = "存在一个人不喜欢学习"
- "不存在完美的人" = "所有人都不完美"
这些规则是德摩根律在量词上的推广。 类似于无穷大的合取, 类似于无穷大的析取。
推理规则的完备性
命题逻辑:假言推理和假言三段论对于 Horn 子句(最多一个正文字的子句)是完备的。对于一般命题公式,消解法是完备的。
谓词逻辑:
- 对于只含 Horn 子句的谓词逻辑,推广的假言推理是完备的
- 对于一般谓词逻辑,消解法配合合一算法是完备的
重要定理:谓词逻辑是半可判定的。如果公式是有效的,存在算法能在有限步内证明它;但如果公式不是有效的,可能永远无法确定。
证明的有效性
一个证明是有效的,当且仅当如果所有前提为真,则结论必然为真。检验证明有效性可以使用真值表或推理规则。
谓词逻辑证明实例
理解谓词逻辑推理规则的最好方式是通过具体例子。以下是几个经典的证明实例,展示了如何正确使用量词推理规则。
实例 1:苏格拉底论证
前提:
- 所有人都会死。
- 苏格拉底是人。
结论:苏格拉底会死。
证明:
[1] ∀x (Human(x) → Mortal(x)) 前提
[2] Human(Socrates) 前提
[3] Human(Socrates) → Mortal(Socrates) 全称实例化 [1]
[4] Mortal(Socrates) 假言推理 [2], [3]
分析:这是一个直接的应用。全称实例化允许我们将全称量词具体化为特定的个体苏格拉底。
实例 2:存在性结论
前提:
- 所有猫都会爬树。
- 存在猫。
结论:存在会爬树的动物。
证明:
[1] ∀x (Cat(x) → ClimbTree(x)) 前提
[2] ∃x Cat(x) 前提
[3] Cat(c*) 存在实例化 [2],引入新常量 c*
[4] Cat(c*) → ClimbTree(c*) 全称实例化 [1]
[5] ClimbTree(c*) 假言推理 [3], [4]
[6] ∃x ClimbTree(x) 存在推广 [5]
关键点:
- 必须先进行存在实例化,再进行全称实例化
- 是新引入的常量,代表"某个存在的猫"
- 存在推广从具体实例推出存在性结论
实例 3:涉及多个量词的证明
前提:
- 每个人都有父亲。
- 父亲比自己年长。
结论:每个人都有比自己年长的人。
证明:
[1] ∀x ∃y Father(y, x) 前提
[2] ∀x ∀y (Father(x, y) → Older(x, y)) 前提
[3] 设 a 为任意元素 开始全称推广
[4] ∃y Father(y, a) 全称实例化 [1]
[5] Father(f(a), a) 存在实例化 [4],引入 f(a) 作为 a 的父亲
注意:f(a) 是依赖于 a 的元素
[6] ∀y (Father(y, a) → Older(y, a)) 全称实例化 [2]
[7] Father(f(a), a) → Older(f(a), a) 全称实例化 [6]
[8] Older(f(a), a) 假言推理 [5], [7]
[9] ∃y Older(y, a) 存在推广 [8]
[10] ∀x ∃y Older(y, x) 全称推广 [3]-[9]
注意:这个证明使用了函数符号 表示"a 的父亲"。在标准的一阶逻辑中,存在实例化引入的常量可以依赖于之前选择的元素。
实例 4:矛盾证明
前提:
- 所有学生都完成了作业。
- 有人没完成作业。
结论:存在非学生。
证明:
[1] ∀x (Student(x) → Finished(x)) 前提
[2] ∃x ¬Finished(x) 前提
[3] ¬Finished(c*) 存在实例化 [2]
[4] 假设 Student(c*) 假设(用于反证)
[5] Student(c*) → Finished(c*) 全称实例化 [1]
[6] Finished(c*) 假言推理 [4], [5]
[7] Finished(c*) ∧ ¬Finished(c*) 合取引入 [3], [6],矛盾!
[8] ¬Student(c*) 否定引入 [4]-[7]
[9] ∃x ¬Student(x) 存在推广 [8]
分析:这个证明展示了如何结合存在实例化和矛盾法。我们假设"那个没完成作业的人是学生",然后导出矛盾。
常见错误警示
错误 1:全称推广应用于特定元素
错误示例:
[1] 7 是素数 前提
[2] ∀x Prime(x) 错误的全称推广!
是一个特定的元素,不能对其进行全称推广。全称推广只适用于"任意"元素。
错误 2:存在实例化使用已存在的常量
错误示例:
[1] ∀x (P(x) → Q(x)) 前提
[2] ∃x P(x) 前提
[3] P(a) 假设:先做了全称实例化得到 P(a)→Q(a)
[4] P(a) 错误!存在实例化不能用已存在的 a
正确做法是先进行存在实例化,引入新常量,再进行全称实例化。
错误 3:混淆存在实例化和存在推广
错误示例:
[1] ∃x P(x) 前提
[2] P(c) 错误!不能用普通常量 c
存在实例化引入的元素必须是新常量,通常用 标记,表示"某个存在的、但不具体知道的元素"。
范式
范式是将复合命题转化为标准形式的方法,便于判断命题的性质和进行自动推理。在计算机科学中,范式在逻辑编程、SAT 求解器、定理证明等领域有广泛应用。
基本概念
文字(Literal):命题变量或其否定。例如, 和 都是文字。 称为正文字, 称为负文字。
子句(Clause):若干文字的析取。例如, 是一个子句。在子句中,文字之间是"或"的关系。
合取范式(Conjunctive Normal Form,CNF):若干子句的合取。整个公式是子句的"与"运算。
其中每个 是一个子句(文字的析取)。
例子: 是 CNF。
析取范式(Disjunctive Normal Form,DNF):若干合取项的析取。整个公式是合取项的"或"运算。
其中每个 是若干文字的合取。
例子: 是 DNF。
CNF 与 DNF 的比较
| 特性 | CNF | DNF |
|---|---|---|
| 结构 | 子句的合取 | 合取项的析取 |
| 子句/项内部 | 文字的析取 | 文字的合取 |
| 可满足性检测 | 需要每个子句都为真 | 只需一个合取项为真 |
| 应用 | SAT 求解器、消解法 | 直接判断可满足性 |
DNF 判断可满足性很容易:只要有一个合取项不包含矛盾(同时包含 和 ),公式就可满足。但将一般公式转换为 DNF 可能导致指数级膨胀。
CNF 虽然判断可满足性较难,但转换过程可以高效进行,并且是消解法推理的基础。
转换为 CNF 的算法
任何命题公式都可以转换为等价的 CNF。以下是标准的六步转换算法:
步骤 1:消除蕴含和等价
使用等价关系:
步骤 2:将否定内移
使用德摩根律将否定推到命题变量之前:
步骤 3:消除双重否定
经过这三步,公式变为"否定范式"(Negation Normal Form,NNF),即否定只作用于命题变量。
步骤 4:分配析取到合取
使用分配律将 分配到 中:
重复应用直到无法再分配。
转换示例
问题:将 转换为 CNF。
解答:
步骤 1:消除蕴含
步骤 2:将否定内移(德摩根律) 所以:
步骤 3:消除双重否定 已经完成()
步骤 4:分配析取到合取
最终 CNF:
从真值表构造 DNF
给定真值表,可以直接构造 DNF:
- 找出所有使公式为真的行
- 对每一行,构造一个合取项:变量为真则用正文字,为假则用负文字
- 将所有合取项用析取连接
例子:构造公式 的 DNF
| T | T | T |
| T | F | F |
| F | T | T |
| F | F | T |
使公式为真的行是 (T,T)、(F,T)、(F,F):
- (T,T):
- (F,T):
- (F,F):
DNF:
可以化简:
这正是 的另一种形式,验证了转换的正确性。
范式的应用
SAT 问题:判断一个命题公式是否可满足。将公式转换为 CNF 后,可以使用高效的 SAT 求解器(如 DPLL 算法、CDCL 算法)来求解。
消解法:一种自动推理方法,只适用于 CNF 形式的公式。基本规则是:
从 推出
其中 和 是子句的剩余部分。如果消解出空子句,则原公式不可满足。
def to_cnf(formula):
"""
将命题公式转换为 CNF(简化版)
实际实现需要处理语法树
"""
# 步骤1: 消除蕴含
# 步骤2: 德摩根律
# 步骤3: 消除双重否定
# 步骤4: 分配律
pass
# 示例:验证 CNF 可满足性(简单情况)
def is_satisfiable_cnf(clauses):
"""
检查 CNF 是否可满足(简化版)
clauses: [[('p', True), ('q', False)], ...]
每个子句是 (变量, 是否为正文字) 的列表
"""
# 使用回溯法或 DPLL 算法
pass
前束范式
前束范式(Prenex Normal Form,PNF)是谓词逻辑中的一种标准形式,它将所有量词移到公式的最前面。这种形式在自动定理证明、逻辑编程和数据库查询优化中有重要应用。
前束范式的定义
一个谓词逻辑公式处于前束范式,当且仅当它具有以下形式:
其中:
- 每个 是量词( 或 )
- 是变量
- 是不含量词的公式,称为母式(Matrix)
- 量词序列 称为前束词(Prefix)
特殊情况:不含任何量词的公式本身就是前束范式(前束词为空)。
例子:
| 公式 | 是否为前束范式 |
|---|---|
| 是 | |
| 否(量词在内部) | |
| 是 | |
| 否(否定在量词前面) |
为什么需要前束范式?
前束范式的价值体现在以下几个方面:
统一处理:将量词集中在公式开头,便于分析和处理。许多自动推理算法(如消解法)要求输入公式处于前束范式。
简化推理:前束范式将量词"提纯",使我们可以先处理量词部分,再处理无量词的母式。
Skolem 化的准备:前束范式是 Skolem 化的预备步骤,后者用于消除存在量词,将公式转化为只含全称量词的形式。
转换为前束范式的算法
任何谓词逻辑公式都可以转换为等价的前束范式。转换分为四个步骤:
步骤 1:消除蕴含和等价
使用逻辑等价式:
步骤 2:将否定内移
使用量词否定规则和德摩根律,将否定推到原子公式之前:
经过这一步,公式变为否定范式(NNF),即否定只作用于原子公式。
步骤 3:标准化变量
当公式中同一变量名被多个量词约束时,需要重命名以避免冲突。规则是:将约束变量的出现替换为新的、未被使用的变量名。
例子:
两个量词都使用变量 ,需要标准化。将第二个 重命名为 :
步骤 4:将量词移到前面
使用量词移动等价式,将量词逐步移到公式最前面。
量词移动规则
以下是移动量词的关键等价式,其中 是不含自由变量 的公式, 是可能含自由变量 的公式:
量词与合取:
量词与析取:
注意最后一条: 与 不等价。前者要求"所有 都满足 或所有 都满足 ",后者只要求"每个 满足 或 "。前者更强。
量词顺序:
同类量词可以交换顺序:
但不同类量词一般不能交换:
转换示例
例 1:将 转换为前束范式。
解答:
步骤 1:消除蕴含
步骤 2:否定已在原子公式前,无需移动。
步骤 3:标准化变量。将第二个 重命名为 :
步骤 4:移动量词。先移动 :
再移动 :
这就是前束范式。
例 2:将 转换为前束范式。
解答:
步骤 1:消除蕴含
步骤 2:将否定内移(德摩根律)
步骤 3:变量已标准化。
步骤 4:移动量词
例 3:将 转换为前束范式。
解答:
步骤 1:消除蕴含
步骤 2:将否定内移
步骤 3:变量已标准化。
步骤 4:这里遇到了一个关键问题。 不能简单地合并为 ,因为这两者不等价。
正确的方法是保持原样,或者使用更复杂的技术(如 Skolem 化)。作为前束范式转换,我们得到:
这不是标准的前束范式。要解决这个问题,需要注意到:如果我们将 看作一个整体,可以写成:
虽然这两者不完全等价,但在某些情况下(如检查有效性而非可满足性)这种转换是可接受的。更严格的做法是保持公式原样。
前束范式的局限性
前束范式虽然有用,但也有局限:
-
可读性下降:将所有量词移到前面可能使公式的直观含义变得不清晰。
-
非唯一性:同一公式可能有多个不同的前束范式表示。
-
量词顺序敏感:移动量词可能改变原公式的含义,特别是当混合使用全称和存在量词时。
-
非等价转换:某些情况下,前束范式转换不能保持严格等价性。
与 CNF 的关系
将谓词逻辑公式转换为 CNF 通常需要先转换为前束范式:
- 转换为前束范式
- 进行 Skolem 化(消除存在量词)
- 将母式转换为 CNF
这个组合形式(Skolem CNF)是消解法推理的输入格式。
def to_prenex_normal_form(formula):
"""
将谓词逻辑公式转换为前束范式的概念框架
实际实现需要语法树操作
"""
# 步骤1: 消除蕴含和等价
# 步骤2: 将否定内移
# 步骤3: 标准化变量
# 步骤4: 移动量词到前面
pass
# 示例:验证量词移动规则
def demonstrate_quantifier_rules():
"""演示量词移动规则"""
# ∀x A(x) ∧ ∀y B(y) ≡ ∀x∀y (A(x) ∧ B(y))
# 示例:"所有学生都学习" ∧ "所有老师都教书"
# 等价于:"对所有x和y,如果x是学生则学习,如果y是老师则教书"
# ∃x A(x) ∧ ∀y B(y) ≡ ∃x∀y (A(x) ∧ B(y))
# 注意:通常存在量词应放在全称量词前面,以保持语义
pass
谓词逻辑的等价式与蕴含式
谓词逻辑有丰富的等价式和蕴含式,它们是进行逻辑推理和公式化简的重要工具。
量词与否定
这是最基本的一组等价式,已在前面讨论:
量词与合取、析取
分配性规则:
全称量词对合取可分配:
存在量词对析取可分配:
不可分配的情况:
全称量词对析取不可分配:
反例:设论域为整数, 表示" 是偶数", 表示" 是奇数"。左边说"每个整数是偶数或奇数",为真;右边说"所有整数都是偶数,或所有整数都是奇数",为假。
存在量词对合取不可分配:
反例:同一论域, 表示" 是偶数", 表示" 是奇数"。左边说"存在一个既是偶数又是奇数的整数",为假;右边说"存在偶数,且存在奇数",为真。
量词与蕴含
蕴含式涉及量词时的等价关系需要特别小心:
但以下蕴含成立:
例子:设 表示" 能被 4 整除", 表示" 能被 2 整除"。
- :"能被 4 整除则能被 2 整除",为真。
- :"如果所有数都能被 4 整除,则所有数都能被 2 整除",也为真(因为前件为假)。
但反向不成立。设论域为 :
- 为假(2 不能被 4 整除)
- 为真
- 所以 为真
- 但 为假(因为 为假而 为真,但 为真而... 实际上这个例子中它也为真)
更清晰的例子:设论域为 , 为真, 为假, 为假, 为真。
- :检查 ,所以整体为假。
- :前件 ,所以蕴含式为真。
双量词等价式
当公式含有两个量词时,有许多有用的等价式:
相同量词可交换:
不同量词不可交换:
例子:设 表示" 爱 "。
- :"每个人都爱某个人",这是可能的。
- :"存在一个人被所有人爱",这更难满足。
前者蕴含后者不成立,但后者蕴含前者成立:
谓词逻辑蕴含式总结
以下是一些重要的谓词逻辑蕴含式( 表示 ):
| 前提 | 结论 | 说明 |
|---|---|---|
| 全称蕴含存在(论域非空时) | ||
| 全称量词可交换 | ||
| 存在量词可交换 | ||
| 强存在蕴含弱存在 | ||
| 等价关系 | ||
| 等价关系 |
def verify_quantifier_equivalence():
"""
验证量词等价式的示例代码
使用有限论域进行验证
"""
# 定义一个有限论域
domain = [1, 2, 3, 4, 5]
# 定义谓词
def is_even(x):
return x % 2 == 0
def is_positive(x):
return x > 0
# 验证: ∀x(P(x)∧Q(x)) ≡ ∀x P(x) ∧ ∀x Q(x)
left = all(is_even(x) and is_positive(x) for x in domain)
right = all(is_even(x) for x in domain) and all(is_positive(x) for x in domain)
print(f"∀x(P(x)∧Q(x)): {left}")
print(f"∀x P(x) ∧ ∀x Q(x): {right}")
print(f"等价: {left == right}")
# 验证: ∃x(P(x)∧Q(x)) ≢ ∃x P(x) ∧ ∃x Q(x)
left = any(is_even(x) and x > 3 for x in domain) # P: 偶数, Q: > 3
right = any(is_even(x) for x in domain) and any(x > 3 for x in domain)
print(f"\n∃x(P(x)∧Q(x)): {left}") # 4 是偶数且 > 3,为 True
print(f"∃x P(x) ∧ ∃x Q(x): {right}") # 存在偶数且存在 > 3 的数,为 True
# 这个例子中两者相等,需要找一个不等的例子
# 更好的例子
# P(x): x 是偶数, Q(x): x 是奇数
left = any(x % 2 == 0 and x % 2 == 1 for x in domain) # 永远为 False
right = any(x % 2 == 0 for x in domain) and any(x % 2 == 1 for x in domain) # True
print(f"\n存在偶数且奇数的数: {left}") # False
print(f"存在偶数且存在奇数: {right}") # True
print(f"不等价: {left != right}")
Skolem化
Skolem化(Skolemization)是将前束范式中的存在量词消除的过程,得到只含全称量词的公式。这在自动定理证明、逻辑编程和数据库查询优化中有重要应用。理解Skolem化对于掌握现代逻辑系统的实现至关重要。
Skolem化的定义
Skolem化的核心思想是:如果存在某个元素满足条件,我们可以给它一个名字(常量或函数),而不必显式地用量词来声明它的存在。
Skolem常量:当存在量词不在任何全称量词的作用域内时,用一个新的常量符号替换存在量词约束的变量。
Skolem函数:当存在量词在某些全称量词的作用域内时,用一个新的函数符号替换存在量词约束的变量,该函数依赖于这些全称量词约束的变量。
Skolem化算法
步骤 1:将公式转换为前束范式。
步骤 2:从左到右扫描量词序列,对每个存在量词 :
- 如果 前面没有全称量词,引入一个新常量 (Skolem常量),用 替换公式中所有的 ,删除 。
- 如果 前面有全称量词 ,引入一个新的 元函数符号 (Skolem函数),用 替换公式中所有的 ,删除 。
步骤 3:删除所有剩余的全称量词(因为现在所有变量都由全称量词约束,可以省略)。
Skolem化示例
例 1:Skolem化
没有前置的全称量词,所以引入Skolem常量 :
这表示"存在某个特定元素 使得 为真"。
例 2:Skolem化
存在量词 在全称量词 的作用域内,所以 依赖于 。引入Skolem函数 :
这可以理解为"对于每个 ,存在一个依赖于 的元素 ,使得 成立"。
例 3:Skolem化
存在量词 在 和 的作用域内,所以 依赖于 和 :
例 4:Skolem化
- 没有前置的全称量词,用常量 替换:
- 在 的作用域内,用一元函数 替换:
Skolem化的正确性
重要性质:Skolem化不保持逻辑等价,但保持可满足性。
具体来说:
- 如果原公式可满足,则Skolem化后的公式也可满足
- 如果Skolem化后的公式可满足,则原公式也可满足
但原公式与Skolem化后的公式不等价。例如:
与 不等价。前者说"存在某个元素满足 ",后者说"特定元素 满足 "。后者蕴含前者,但前者不蕴含后者。
Skolem范式
经过Skolem化后得到的只含全称量词的公式,称为Skolem范式(Skolem Normal Form)。在自动定理证明中,通常进一步将母式转换为CNF,得到Skolem CNF。
完整转换流程:
- 消除蕴含和等价
- 将否定内移(得到否定范式NNF)
- 转换为前束范式
- Skolem化消除存在量词
- 将母式转换为CNF
- 省略全称量词
示例:将 转换为Skolem CNF。
步骤 1:消除蕴含
步骤 2:否定已内移
步骤 3:前束范式
步骤 4:Skolem化。 在 作用域内,用 替换 :
步骤 5:母式已是CNF形式(一个子句)
步骤 6:省略全称量词
Skolem化的应用
自动定理证明:Skolem CNF是消解法(Resolution)的输入格式。消解法是一种强有力的自动推理方法,广泛应用于定理证明器中。
逻辑编程:Prolog等逻辑编程语言的基础是Horn子句,而Horn子句可以看作Skolem CNF的特殊形式。
数据库查询:SQL查询优化中,存在量词的处理与Skolem化有密切关系。
def skolemize(formula):
"""
Skolem化的概念框架
实际实现需要语法树操作和符号表管理
"""
# 1. 转换为前束范式
# 2. 扫描量词序列
# 3. 对每个存在量词引入Skolem常量或函数
# 4. 删除存在量词
pass
# 示例理解
def skolem_example():
"""Skolem化示例的直观理解"""
# ∀x∃y Parent(y, x): 每个人都有父母
# Skolem化后: ∀x Parent(f(x), x)
# f(x) 可以理解为 "x的父母函数"
# 这在语义上是合理的:
# 不同的人可能有不同的父母
# f(张三) 返回张三的父母
# f(李四) 返回李四的父母
# ∃x∀y Loves(x, y): 存在一个人爱所有人
# Skolem化后: ∀y Loves(c, y)
# c 是一个常量,代表 "那个爱所有人的人"
pass
Skolem化的注意事项
函数符号必须是新的:Skolem函数不能与公式中已有的函数符号重名,否则会导致语义混淆。
依赖关系的正确处理:存在量词变量依赖于它之前所有全称量词约束的变量。例如:
| 原公式 | Skolem化结果 | 说明 |
|---|---|---|
| 不依赖于任何变量,用常量 | ||
| 依赖于 ,用一元函数 | ||
| 依赖于 和 ,用二元函数 |
与Herbrand域的关系:Skolem化后的公式可以在Herbrand域上进行解释,这是自动定理证明的理论基础之一。
消解原理
消解原理(Resolution Principle)是命题逻辑和一阶逻辑中一种强有力的推理规则,由J.A. Robinson于1965年提出。它是现代自动定理证明器的核心算法,也是逻辑编程语言Prolog的理论基础。
消解规则
消解规则的基本形式是:
其中 是一个文字, 和 是子句。这个规则说:如果两个子句包含互补的文字(一个正文字,一个负文字),则可以将它们合并,删除互补的文字对。
示例:
- 子句1:
- 子句2:
- 消解结果:(消去 和 )
命题逻辑中的消解
消解反驳:要证明命题公式 是永真式,等价于证明 是不可满足的。将 转换为CNF,然后用消解规则推导。如果能推导出空子句(记作 ),则 不可满足,从而 是永真式。
示例:证明 是永真式。
步骤 1:否定结论
步骤 2:转换为CNF
得到三个子句:
步骤 3:消解
| 步骤 | 子句 | 来源 |
|---|---|---|
| 1 | 前提 | |
| 2 | 前提 | |
| 3 | 前提 | |
| 4 | 消解1, 2(消去 和 ) | |
| 5 | 消解3, 4(消去 和 ) |
推导出空子句,证明完成。
一阶逻辑中的消解
一阶逻辑中的消解需要处理变量,因此需要**合一(Unification)**算法。
合一:找到最一般的替换,使两个原子公式相同。
示例:合一 和
替换 可以使两者合一为 。
消解步骤:
- 将公式转换为Skolem CNF
- 对两个子句中的互补文字进行合一
- 应用消解规则
示例:从以下前提证明结论。
前提:
- :所有猫都是动物
- :Tom是猫
结论:
- :Tom是动物
步骤 1:转换为CNF
前提1: 前提2:
否定结论:
步骤 2:消解
| 步骤 | 子句 | 来源 |
|---|---|---|
| 1 | 前提 | |
| 2 | 前提 | |
| 3 | 否定结论 | |
| 4 | 消解1, 2,替换 | |
| 5 | 消解3, 4 |
消解策略
单元消解:优先使用单元子句(只含一个文字的子句)进行消解,可以快速简化问题。
输入消解:至少有一个子句来自原始子句集。这种策略效率高,但不完备。
线性消解:每次消解至少使用上一步产生的子句。这是Prolog采用的策略。
支持集消解:限制消解必须涉及否定目标或其派生出的子句。
消解的完备性
命题逻辑:消解规则是完备的。如果一个CNF公式是不可满足的,则一定能通过消解推导出空子句。
一阶逻辑:消解规则也是完备的(Gödel完备性定理的构造性证明)。但由于一阶逻辑的半可判定性,如果公式不是永真式,消解过程可能不会终止。
def resolution(clauses):
"""
消解算法的概念框架
clauses: 子句集合,每个子句是文字的集合
"""
while True:
new_clauses = set()
# 对所有子句对尝试消解
for c1 in clauses:
for c2 in clauses:
if c1 != c2:
resolvents = resolve(c1, c2)
if empty_clause in resolvents:
return "UNSATISFIABLE"
new_clauses.update(resolvents)
# 如果没有新子句产生,则可满足
if new_clauses.issubset(clauses):
return "SATISFIABLE"
clauses.update(new_clauses)
def resolve(clause1, clause2):
"""
对两个子句进行消解
返回所有可能的消解结果
"""
resolvents = []
for literal in clause1:
if negate(literal) in clause2:
# 找到互补文字对,进行消解
new_clause = (clause1 - {literal}) | (clause2 - {negate(literal)})
resolvents.append(new_clause)
return resolvents
消解的应用
定理证明:消解是许多自动定理证明器的核心算法。
逻辑编程:Prolog使用消解的变种(SLD消解)来执行查询。
约束求解:SAT求解器使用消解的优化版本(CDCL算法)来处理大规模布尔可满足性问题。
程序验证:模型检验和符号执行中大量使用消解技术。
小结
本章介绍了数理逻辑的基础知识:
- 命题逻辑:命题、联结词、真值表、逻辑等价
- 谓词逻辑:谓词、量词、嵌套量词
- 证明方法:直接证明、反证法、逆否证明、数学归纳法
- 推理规则:假言推理、拒取式等常用规则,以及谓词逻辑中的实例化和推广规则
- 范式:CNF、DNF、前束范式,以及Skolem化的原理和方法
- 消解原理:自动定理证明的核心技术
这些知识是后续学习的基础。集合论中将用到逻辑表达式,证明方法将贯穿整个离散数学的学习,而消解原理和Skolem化则是连接理论与实际应用的重要桥梁。
练习
命题逻辑
-
构造 的真值表,验证它是永真式。这个永真式有什么实际意义?
-
用德摩根律简化 ,并解释每一步的逻辑含义。
-
将公式 转换为 CNF,详细写出每个转换步骤。
-
证明下列等价式:
-
判断以下命题是永真式、矛盾式还是可满足式:
谓词逻辑
-
将命题"没有最大的素数"用谓词逻辑表示。要求给出两种不同的表示方式,并说明它们的等价性。
-
设论域为所有人,将以下句子翻译成谓词逻辑:
- "所有人都喜欢某些人"
- "有些人被所有人喜欢"
- "没有人喜欢所有人"
- "并不是所有人都喜欢某些人"
-
判断以下等价式是否成立,并给出证明或反例:
-
设论域为整数,用谓词逻辑表示以下命题:
- "每个整数都有唯一的后继"
- "存在唯一的偶素数"
- "任意两个不同的整数之间必有另一个整数"
前束范式与Skolem化
-
将以下公式转换为前束范式,并写出每一步的转换依据:
-
对以下前束范式进行Skolem化,并解释Skolem函数的语义:
-
证明 ,但反向不成立。给出一个具体的谓词例子说明反向不成立。
-
将以下公式转换为Skolem CNF:
证明方法
-
用数学归纳法证明:
- 对所有正整数 成立
-
用反证法证明:
- 存在无穷多个素数
- 是无理数
-
使用谓词逻辑推理规则完成以下证明:
(a) 从以下前提证明"存在会爬树的动物":
- 前提 1:
- 前提 2:
(b) 从以下前提证明"存在喜欢学习的聪明人":
- 前提 1:
- 前提 2:
- 前提 3:
(c) 从以下前提证明"有些人不快乐":
- 前提 1:
- 前提 2:
-
使用强归纳法证明:每个大于1的整数都可以唯一地表示为素数的乘积(不计顺序)。
消解原理
-
用消解法证明以下命题逻辑公式是永真式:
-
用消解法证明一阶逻辑命题:
(a) 从以下前提证明 :
(b) 从以下前提证明 :
-
解释为什么消解法在一阶逻辑中可能不终止。给出一个具体的例子说明这种情况。
综合应用
-
在程序验证中,我们需要证明程序的某些性质。考虑以下简单的条件语句:
if (x > 0) {
y = x + 1;
} else {
y = 1;
}
// 断言: y >= 1用谓词逻辑表示这个程序的正确性,并证明断言成立。
-
数据库查询优化中常用到逻辑等价式。证明以下查询重写规则的正确性:
- (当条件只涉及R的属性时)
- (当条件只涉及投影的属性时)
提示:将关系代数操作转换为逻辑表达式进行证明。
-
在人工智能的知识表示中,框架公理用于描述状态变化。用谓词逻辑表示:
- "在状态 中,如果物体 在位置 ,且机器人没有移动 ,则在下一个状态 中, 仍在位置 "
- "在状态 中,如果机器人把 从 移到 ,则在下一个状态 中, 在位置 "
-
逻辑编程语言Prolog使用消解的变种。解释以下Prolog程序的执行过程:
parent(tom, mary).
parent(mary, john).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
?- grandparent(tom, john).用本章学到的消解原理分析查询的执行过程。