离散数学里,"逻辑"这一章是最容易被低估的部分。不少人图论能刷、组合能推、关系闭包算得飞快,一到命题逻辑的真值表就开始凭感觉填,一到谓词逻辑的翻译题就开始自由发挥。我自己当年也是这样过来的,后来带过几轮课、也看过不少同学的作业,慢慢意识到一个事实:逻辑这一章考的从来不是计算能力,而是把自然语言精确翻译成形式符号、再按规则严格推演的能力。它不像其他章节那样有"套路公式一套就出结果"的爽感,反而处处是语文陷阱和约定俗成的坑。这篇内容就是把这个过程中的核心概念、实操方法、验证手段和我见过的典型翻车场景一次讲透,无论你是正在学这门课的学生,还是想补一补布尔代数基础、把它用到程序条件判断和电路化简里的工程师,都能直接拿去用。
1. 逻辑这一章到底在训练什么能力
1.1 从"会算"到"会证"的转折点
离散数学前面几章,很多时候是"给定输入、按定义输出"的机械过程:两个集合求交并差、关系矩阵做布尔乘、图的邻接矩阵求可达性。这些内容有一个共同点——答案唯一,过程可验证。你算完了,回头一检查就知道对不对。
逻辑这一章不一样。它第一次要求你面对一个需要你构造过程的问题:给一组前提,证明某个结论成立。这个过程没有唯一写法,同一道题可以用假言推理走三步,也可以用反证法走五步,还能用附加前提法绕一个弯。于是很多人的第一反应是"我推出来了,但不知道算不算对"。这就是从"会算"到"会证"的门槛。
我的观察是,跨过这道门槛的关键不在于背更多公式,而在于建立两个习惯:第一,每写一步都问自己"这一步用了哪条规则、作用在哪两个式子上";第二,在动手之前先用真值表或者代入特殊值验证一遍结论是否真的成立。前者保证过程可审计,后者保证方向没跑偏。这两件小事做扎实,逻辑部分的失分能少一大半。
另外要提醒的是,逻辑的形式化程度比想象中高。日常语言里"或者"经常是排他的("你要么去要么不去"),"如果"经常带因果("如果下雨地就湿"暗含"地湿是因为下雨"),但形式逻辑里这些都只保留真值关系,因果关系一律不管。**接受这种"降维",是学好这一章的心理前提。**很多人学不下去,本质上是拒绝接受形式化带来的语义损失。
1.2 逻辑和日常写代码的关系比你以为的紧
如果只是应付考试,上面那段话就够了。但我更想说:命题逻辑是所有条件判断的老祖宗。
写if (user != null && user.age > 18)的时候,你其实在写一个合取式。短路求值(short-circuit evaluation)能成立,底层依赖的是¬P → (P ∧ Q ≡ False)这类恒等式——左边假了,右边拆开判断就是"假与任何值都为假",所以没必要算。反过来,如果语言不做短路,这段代码就会在user为 null 时崩掉。看起来是语法糖,本质是逻辑等价式的工程化应用。
再比如 SQL 里WHERE age > 18 AND age < 30,当age为NULL时,表达式结果不是真也不是假,而是UNKNOWN。这是一个三值逻辑系统,UNKNOWN AND FALSE = FALSE,但UNKNOWN AND TRUE = UNKNOWN。搞不清楚这一点的人,写出来的过滤条件会在边界数据上悄悄丢行或者多行。
还有契约式编程里的前置条件、后置条件,单元测试里的断言,状态机里的跃迁条件,甚至正则表达式里的字符类并集,都是命题逻辑的直接应用。谓词逻辑则在数据库的EXISTS、FORALL(部分引擎)以及类型系统里的全称量化中反复出现。把这一章学扎实,不是学一门"考完就忘"的课,是给后面所有涉及条件、约束、断言的场景打地基。
2. 命题联结词:五个符号背后的约定
2.1 蕴含符号为什么"假命题蕴含一切"
这是逻辑这一章第一个劝退点,也是我见过最多人卡住的地方。真值表里,P → Q只在P 真、Q 假时为假,其余全真。很多人第一反应是:"凭什么P是假的时候,整个式子就是真的?"
我的解释方式是这样的:把P → Q理解成一个承诺。我说"如果明天下雨,我就带伞"。现在请问,什么情况下算我说话不算话?只有一种:明天下雨了,而我确实没带伞。如果明天压根没下雨,我不管带不带伞,你都没法说我食言。如果明天下雨了我也带伞了,那我是守信用的。这就是P → Q的真值表。
P为假时式子为真,术语叫空真(vacuously true)。这个约定看起来反直觉,但它是唯一能让推理规则自洽的选择。如果我们反过来规定"P假则P → Q假",那么假言推理就废了——因为很多定理的前件在大部分取值下都是假的,这些定理就全都变成假命题了。更麻烦的是,P → Q和¬P ∨ Q的逻辑等价关系会崩掉,整个等价代换体系就得推倒重来。
实际做题时有个特别好用的小技巧:把蕴含统一改写成"非前件或后件"。P → Q ≡ ¬P ∨ Q,这一步改写之后,式子就只剩¬、∨、∧三个联结词,后面套德摩根律、套分配律都变得很顺手。我几乎所有的等价推导都是这么开头的。
2.2 "充分"与"必要"这两个词怎么不搞混
自然语言里的四个说法,对应四个不同的逻辑式,考试里专门用来设陷阱:
| 日常说法 | 逻辑形式 | 记忆锚点 |
|---|---|---|
| P 是 Q 的充分条件 | P → Q | 有 P 就够推 Q |
| P 是 Q 的必要条件 | Q → P | 没 P 就没 Q |
| P 当且仅当 Q | P ↔ Q | 双向都成立 |
| 只有 P 才 Q | Q → P | "只有"后面是必要的 |
"只有...才..."这个句式最容易翻。比如"只有通过体检才能上岗",形式化是"上岗 → 通过体检",而不是"通过体检 → 上岗"。判断方法是:把句子读成"如果 Q,那么必须 P",看哪个方向不成立。"如果上岗了,那么必定通过体检"——成立;"如果通过体检,那么必定上岗"——不成立。所以箭头从 Q 指向 P。
我建议在做这类翻译题时,先在草稿上写出"如果…那么…"的自然语言版本,再转换符号,不要一步跳到符号。这一步多花十秒钟,能避免后面全盘重来。
异或⊕、与非↑(Sheffer 竖)、或非↓这几个"多余"的联结词之所以常考,是因为它们涉及功能完备集的概念:{¬, ∧}是完备的,{↑}单独也是完备的,{↓}单独也是完备的。这个知识点在数字电路里会直接用到——只用一种门电路搭出所有逻辑功能,是芯片设计里的基本操作。异或则有天然的可逆性和奇偶校验特性,网络传输里的校验位、密码学里的流加密都在用它。
3. 真值表:最笨的方法往往最稳
3.1 一张表怎么排才不会乱
真值表看起来毫无技术含量,但真正动手排的时候,错得最多的恰恰是排表本身。我总结了一套固定流程:
- 数变元个数 n,确定行数
2^n。三个变元就是 8 行,四个变元 16 行。先把行数写死,不要边写边数。 - 确定列的左右顺序:变元列 → 中间子公式列 → 最终结果列。所有子公式都单独成列,不要试图心算。
- 变元的取值按照"二进制计数"排列:最右边一列每行交替真假,往左一列每两行交替,再往左每四行交替。这样排出来最不容易漏行、重行。
- 子公式按依赖顺序依次填,先填只含变元的,再填复合的。
举个具体例子,判断(P → Q) ∧ (Q → R) → (P → R)是否永真。变元三个,8 行。列分别是:P、Q、R、P→Q、Q→R、(P→Q)∧(Q→R)、P→R、整体。填完你会发现最后一列全为真,说明这是永真式——也就是假言三段论。
提示:真值表验证的是"是否永真",不是"是否可满足"。如果一个推理的前提合取式是矛盾式,那么"前提 → 结论"恒真,但推理本身没有任何意义。所以验证推理有效性时,只检查"前提全真而结论假的赋值是否存在",比直接算蕴含式更贴近本意。
3.2 用脚本批量验证:把 2 的 n 次方交给机器
手排三四个变元还行,五个以上就很容易出错,而且检查成本极高。我通常会用一段十几行的 Python 脚本兜底,先用机器确认结论,再回来手写推导过程。脚本不需要装任何第三方库:
from itertools import product def check(vars_, premises, conclusion): """检查 前提 ∧ 前提 ∧ ... → 结论 是否有效""" n = len(vars_) print(" | ".join(vars_) + " || 结果") valid = True for values in product([False, True], repeat=n): env = dict(zip(vars_, values)) prem_true = all(p(env) for p in premises) concl = conclusion(env) status = "" if prem_true and not concl: status = " <-- 反例" valid = False row = " | ".join("T" if v else "F" for v in values) print(f"{row} || {prem_true and concl}{status}") return valid # 例:验证 (P→Q) ∧ (Q→R) → (P→R) P = lambda e: e['P'] Q = lambda e: e['Q'] R = lambda e: e['R'] valid = check( ['P', 'Q', 'R'], [lambda e: (not P(e)) or Q(e), # P → Q lambda e: (not Q(e)) or R(e)], # Q → R lambda e: (not P(e)) or R(e) # P → R ) print("推理有效" if valid else "推理无效")这段代码的价值不在于替你写作业,而在于给你一个独立的裁判。手推完一个五步证明,你心里没底,跑一遍脚本,立刻知道结论对不对。如果错了,脚本还会把反例那一行打出来,你能反查到具体是哪一步推错了。
如果你的环境里装了符号计算库,sympy的satisfiable更省事,直接把逻辑式子交给它判断可满足性就行。不过要注意,它判断的是"存在满足赋值",验证推理有效性时你需要判断的是前提 ∧ ¬结论是否不可满足,别搞反方向。
另外有个实用的做法:给每个变元起有意义的名字,比如用rain、umbrella而不是P、Q。脚本里读起来一目了然,也便于你回头对照题目检查翻译对不对。
4. 逻辑等价与范式:化简是手艺,不是背公式
4.1 常用等价式该背到什么程度
等价式一共就那么十几条,但硬背的效果很差。我建议按**"作用"分组记**,而不是按字母顺序记。
| 分组 | 等价式 | 用途 |
|---|---|---|
| 消去蕴含与双蕴含 | P→Q ≡ ¬P∨Q;P↔Q ≡ (P→Q)∧(Q→P) | 一切推导的第一步 |
| 否定内推 | ¬(P∧Q) ≡ ¬P∨¬Q;¬(P∨Q) ≡ ¬P∧¬Q;¬¬P ≡ P | 把否定压到变元上 |
| 分配与合并 | P∧(Q∨R) ≡ (P∧Q)∨(P∧R);P∨(Q∧R) ≡ (P∨Q)∧(P∨R) | 转成范式 |
| 吸收与化简 | P∨(P∧Q) ≡ P;P∧(P∨Q) ≡ P | 减少变元 |
| 恒定式 | P∨¬P ≡ T;P∧¬P ≡ F;P∨F ≡ P;P∧T ≡ P | 判定永真与矛盾 |
我的实践是:前两组必须背到条件反射,后三组会用就行。因为任何等价推导的前两步都是"消蕴含、推否定",这两步卡住就完全动不了。而后三组遇到具体题目现推完全来得及,甚至可以靠真值表反查。
要特别提醒的是分配律在布尔代数里的对称性。代数学里a(b+c)=ab+ac,但a+bc≠(a+b)(a+c)。布尔代数里这两条都成立,因为∨和∧互为对偶。这个特性在化简电路时非常有用,很多人脑子里还带着算术代数的直觉,看到"把或分配到与上"就觉得别扭,其实是没转过弯来。
4.2 主析取范式与主合取范式:从真值表直接读出来
范式是考试的重头戏,也是最容易"算出一半"的地方。我总结的步骤很简单:
主析取范式(Σ 形式):跳着看真值表,把结果为T的每一行写成一个极小项。极小项的规则是:该行中变元取T就写原变元,取F就写其否定,然后用∧连起来,最后用∨把所有极小项串起来。
主合取范式(Π 形式):反过来,把结果为F的每一行写成一个极大项。极大项的规则是:取T写否定,取F写原变元,用∨连起来,再用∧串联。
拿P → Q做例子。真值表四行,只有P=T, Q=F那行为假。
- 主析取范式:读三个真行,分别是
¬P∧¬Q、¬P∧Q、P∧Q,用二进制编号是m0 ∨ m1 ∨ m3,记作Σm(0,1,3)。 - 主合取范式:读一个假行,得到
¬P ∨ Q,对应极大项M2,记作ΠM(2)。
两者是同一个公式的两种面孔,可以通过Σm(i)与ΠM(所有未出现的编号)互相转换。这个转换关系非常值得记:已知一种范式,另一种范式就是把没出现的编号全取上。
这里有个实操细节:编号的二进制顺序必须固定为变元按字典序排列,原变元记 1,否定记 0。比如变元是P, Q, R,赋值P=T, Q=F, R=T对应二进制101,即十进制 5。如果顺序搞反,编号全乱,后面的范式互相转换就全错了。我在批改时见过的错误里,编号错位能排进前三。
注意:主析取范式和主合取范式都不是唯一的写法——极小项之间的顺序可以调换,同一极小项内部变元的顺序也可以调换。但编号集合是唯一的。所以答题时写编号集合最安全,写成展开式时一定要反复核对每个极小项里哪些变元带否定。
5. 推理规则:形式证明的招式与心法
5.1 八条基本规则怎么记住
推理规则本质上是"从已有的式子,合法地生成新式子"的许可证。常用的八条可以按"输入形式"归类记:
| 规则名 | 前提 | 结论 | 一句话理解 |
|---|---|---|---|
| 假言推理(MP) | P→Q,P | Q | 前件成立,后件紧跟 |
| 拒取式(MT) | P→Q,¬Q | ¬P | 后件不成立,前件也不成立 |
| 假言三段论 | P→Q,Q→R | P→R | 蕴含可以连起来 |
| 析取三段论 | P∨Q,¬P | Q | 排除一个,剩下那个 |
| 附加 | P | P∨Q | 真的可以"或"上任何东西 |
| 化简 | P∧Q | P | 合取可以拆 |
| 合取 | P,Q | P∧Q | 两个真可以拼 |
| 消解 | P∨Q,¬P∨R | Q∨R | 消掉互补的那个变元 |
这八条里,假言推理和拒取式是心脏,绝大部分证明都是由这两条驱动的。附加和化简是"搬运工",负责把式子改造成能套用前两条的形状。消解规则在自动定理证明里地位极高——Prolog 的求解内核本质就是反复做消解,把目标取反后推到空子句。
记忆上有两个抓手:蕴含类规则看箭头方向,箭头必须同向才能串(假言三段论),箭头首尾相接才能推(MP);析取类规则看互补项能否消掉(析取三段论、消解)。
5.2 证明推不下去时的三条自救路径
推证明卡住是常态,我通常用三条路径破局,按成本从低到高排:
第一条,反证法。把结论取反加进前提,推出矛盾。这条路对"结论是否定式"或"结论含→"的题目特别有效。因为一旦取反¬(P→Q)就化简成P∧¬Q,直接多出两个原子前提,弹药立刻充足。我在实战里发现,当结论是蕴含式时,反证法有八成概率比直接证更短。
第二条,附加前提法(CP 规则)。要证P→Q,就把P拉进前提集合,去证Q,证完再收回来。这个方法在逻辑上等价于"演绎定理",工程上等价于"先把目标拆成假设和子目标"。写证明时它的好处是前提变多、变元变少,路径更清晰。
第三条,分情况讨论。前提里出现P∨Q又没法消掉的时候,就分两支:一支假设P,一支假设Q,两支都推出同一个结论R,于是得R。这条路写起来长,但几乎不会走死,适合作为兜底。
再补充一个非常实用的技巧:把中间结论写成子公式并编号。比如第 4 步推出¬R,第 5 步要用它,那就明确写"由 2、4 用拒取式得 5.¬P"。每一步都标注"用了哪几个式子、用了哪条规则",这样即使最后结论错了,阅卷时也能看出你的思路,拿到过程分。而更实际的是,标注过的证明自己检查起来快得多——回头一眼扫过去,哪一步没有依据立刻能发现。
6. 谓词逻辑:量词把逻辑从句子推进到结构
6.1 翻译题的三步法
谓词逻辑的翻译题是失分重灾区。我固定用三步走,基本不会跑偏:
第一步,划论域。题目给不给全集很重要。如果论域是"所有人",那"存在一个学生"就要写成∃x(Student(x) ∧ ...);如果论域已经被限定为"所有学生",那Student就不需要了,直接∃x(...)。论域一变,写法的形态就变,很多错误都是从这一步埋下的。
第二步,定谓词。把句子里的名词、形容词、动词拆成谓词。P(x):x 是人;S(x):x 是学生;L(x, y):x 喜欢 y。谓词的元数(几元)要数清楚,"x 把 y 介绍给 z"是三元谓词,漏掉一个变元后面全错。
第三步,定量词和连接词。这里有一条铁律:全称配蕴含,存在配合取。
- "所有 A 都是 B":
∀x(A(x) → B(x)) - "有的 A 是 B":
∃x(A(x) ∧ B(x))
为什么是这样?用反例法想一下就清楚了。如果写成∀x(A(x) ∧ B(x)),它的含义是"论域里每一个东西,既是 A 又是 B",这比原句强得多——原来只说"是 A 的那些东西是 B",没说"所有东西都是 A"。而∃x(A(x) → B(x))更糟:只要存在一个不是 A 的东西,蕴含式就为真,这句话几乎恒真,毫无约束力。
"全称配蕴含、存在配合取"这句话我建议直接抄在草稿纸顶上,每做一道翻译题就抬头看一眼。它背后是上面这个反例分析,理解了原因之后,你甚至可以自己现推出来。
6.2 量词否定与辖域:翻车密度最高的地方
否定一个带量词的式子,规则本身很简单:
¬∀xP(x) ≡ ∃x¬P(x)¬∃xP(x) ≡ ∀x¬P(x)
白话就是"并非所有"等于"存在不是","不存在"等于"全都是不是"。但这条规则一旦嵌套,就容易开始乱套。处理嵌套的标准做法是从外往里逐层剥:最外层是∀就换成∃,否定号往里推一格,继续处理下一层,直到推到谓词上。这个过程和消去蕴含、把否定压到变元上的思路完全一致。
比否定更容易错的是量词顺序。∀x∃y L(x,y)和∃y∀x L(x,y)差得非常远:
∀x∃y L(x,y):每个人都有自己喜欢的人(每个人可以喜欢不同的人)。∃y∀x L(x,y):存在一个人,被所有人喜欢。
前者几乎总是成立,后者是很强的断言。**全称在前、存在在后,是"依赖关系";存在在前、全称在后,是"统一对象"。**判断的时候问一句:"这个 y 是不是跟着 x 变的?"如果答案是"是",那就是∀x∃y。
再有就是辖域和变元绑定。∀x(P(x) ∧ ∃x Q(x))这种写法里,内部那个x被内层量词重新绑定了,外层的∀x对它无效。这种"变元遮蔽"在教材里出现得不多,但一旦遇到,先做约束变元换名(把内层的x换成全新名字z),式子立刻变得清晰:∀x(P(x) ∧ ∃z Q(z))。换名是等价变形,不影响语义,是我处理复杂式子时的习惯动作。
最后提醒量词的分配律只部分成立:
| 等价式 | 是否成立 |
|---|---|
∀x(P(x)∧Q(x)) ≡ ∀xP(x)∧∀xQ(x) | 成立 |
∃x(P(x)∨Q(x)) ≡ ∃xP(x)∨∃xQ(x) | 成立 |
∀x(P(x)∨Q(x)) ≡ ∀xP(x)∨∀xQ(x) | 不成立 |
∃x(P(x)∧Q(x)) ≡ ∃xP(x)∧∃xQ(x) | 不成立 |
前两个成立的直觉是"全称管交集、存在管并集"。第三个不成立可以取反例:"每个整数要么是偶数要么是奇数"为真,但"每个整数都是偶数,或者每个整数都是奇数"为假。第四个的反例是"存在一个人既是老师又是学生"未必成立,但"存在老师且存在学生"显然为真。只要记住"A 配 ∧ 可以提、E 配 ∨ 可以提,交叉的都不行",剩下的靠反例一想就通。
7. 高频翻车现场:几种典型错误的复盘
7.1 逆命题、否命题、逆否命题的三个方向
这是最经典的混淆点,而且错的人往往还不觉得自己错。四个命题的关系是:
| 名称 | 形式 | 与原命题真假关系 |
|---|---|---|
| 原命题 | P → Q | —— |
| 逆命题 | Q → P | 不必同真假 |
| 否命题 | ¬P → ¬Q | 不必同真假 |
| 逆否命题 | ¬Q → ¬P | 恒同真假 |
真正恒等的只有"原命题 ↔ 逆否命题"这一对。"逆命题"和"否命题"虽然互为逆否关系,但它们和原命题没有必然联系。
我见过最典型的错误,是把"所有 A 都是 B"的逆否写成"所有非 A 都不是 B"。正确的逆否是"所有非 B 都不是 A"。用具体例子检查一下:原命题"所有矩形都是平行四边形",逆否命题应该是"所有不是平行四边形的都不是矩形"——成立。如果写成"所有不是矩形的都不是平行四边形",那就错了。
**检查方法很简单:找一个满足前件、不满足后件的反例,如果找得到,就说明写反了。**做翻译和改写题时,我习惯随手构造一个小反例来验证,比死记表格可靠。
7.2 逻辑等价和逻辑蕴含的符号混用
P ≡ Q和P → Q是两个层面的东西,但很多人写着写着就混了。核心区别:
P → Q是一个公式,它本身有真假值,可以出现在别的公式里。P ≡ Q是一个元语言断言,说的是"P 和 Q 的真值对所有赋值都相同",它不是一个可以被否定的公式。
这个区别在写证明时很现实:如果你写"由德摩根律得¬(P∧Q) ≡ ¬P∨¬Q",这是合法的等价变形;但如果你写"由德摩根律得¬(P∧Q) → ¬P∨¬Q",虽然这个蕴含式也是永真的,但下一步你就没法把它当作替换规则用了,因为替换需要等价,不是蕴含。
**判断依据:如果你要拿它来替换式子中的子公式,必须用等价;如果只是要推出某个结论,用蕴含。**这个区分看起来吹毛求疵,但它直接影响你的证明能不能继续往下写。
7.3 真值表列数不够导致的连锁错误
还有一种错误非常隐蔽:证明过程完全正确,但最终答案错了,原因是翻译那一步就错了。这种错误真值表查不出来,因为你的真值表是在验证一个错的命题。
防御办法是回译:把形式化后的式子用自然语言重新读一遍,看是不是和原句意思一致。比如题目是"没有不努力的学生能通过考试",形式化为¬∃x(Student(x) ∧ ¬Work(x) ∧ Pass(x))。回译一遍:"不存在这样一个学生,他同时满足是学生、不努力、通过考试"——意思对上了。如果回译出来是"没有学生是不努力的"或者"所有不努力的人都不能通过考试",那就要检查量词的作用范围是不是画宽了。
回译这个动作通常只要十几秒,但能拦下大量"看起来对但其实翻错"的错误。我个人的经验是,翻译题的错误率有一半来自这一步没做。
8. 把逻辑落到手上:几件值得动手做的事
8.1 一份可以贴在桌上的自查清单
学完这一章,我给自己的检查项是固定的几条,做任何一道题之前扫一眼:
- 蕴含是否已经改写成
¬P ∨ Q? - 否定是否已经推到原子命题上?
- 量词的作用域在哪里,边界括号有没有标清楚?
- 全称配的是不是蕴含,存在配的是不是合取?
- 结论方向有没有搞反(充分条件 vs 必要条件)?
- 变元有没有被内层量词意外捕获?
- 用了替换的地方,依据的是等价还是蕴含?
这七条覆盖了我踩过的绝大多数坑。写在便签上贴屏幕边,做题时视线扫一遍,成本极低。
另外还有一个习惯:每做完一道证明题,用真值表或者脚本把结论验一遍,哪怕你确信自己是对的。验证一次一分钟,比事后重做半小时划算。我当年就是因为偷懒不验证,在一道看似简单的题上把"充分条件"和"必要条件"写反,整道题白丢。
8.2 手推和机验的比例控制
有同学会问:既然脚本能算,还要手推干什么?答案是两者解决的是完全不同的问题。
脚本擅长的是判定:给定一个公式,判断它是否永真、是否可满足、给出反例赋值。这一类问题有固定的算法流程,机器做得又快又准。
手推擅长的是构造:从一个前提出发,找到一条通向结论的推理链。这个过程没有通用算法能在合理时间内给出"最短"的证明,需要人根据式子的形状去猜、去试、去回溯。而恰恰是这种"猜和试"的能力,才是这一章真正想训练的东西——它迁移到任何需要构造论证的场合:写技术方案、做架构决策、排查线上问题时的因果链分析,用的都是同一套肌肉。
我的建议比例是七成手推、三成机验。手推时把每一步的依据写清楚,卡住了跑脚本看反例;等推完了,再跑一遍脚本确认结论。两边都练,两边都不荒废。
最后再分享一个我自己用得很顺手的小方法:把每一类题型整理成一个"急救卡"——翻译题写三条(论域、谓词、量词搭配),等价推导题写三条(消蕴含、推否定、转范式),证明题写三条(反证、附加前提、分情况)。一共九条,还没一张 A4 纸大。考场上或者项目里真遇到卡壳的时候,翻出来按顺序试,绝大多数情况下试到第二条就能动了。