P52 - 合取范式
Conjunctive normal form
官方模块:
Problems.P52核心函数:toConjunctiveNormalForm
题目描述
将命题逻辑公式转换为合取范式(CNF)。CNF 是子句的合取,每个子句是文字的析取。
数据类型
1import Data.List (nub, sort)
2
3data Formula
4 = Value Bool | Variable String | Complement Formula
5 | Disjoin [Formula] | Conjoin [Formula]
6 deriving (Eq, Ord, Show)
函数签名
1toConjunctiveNormalForm :: Formula -> Formula
实现
方法一:真值表法(枚举所有 falsifying 行)
1toConjunctiveNormalForm :: Formula -> Formula
2toConjunctiveNormalForm formula = Conjoin
3 [ Disjoin [literal name value | (name,value) <- env]
4 | env <- environments (variables formula)
5 , not (evaluate env formula)
6 ]
7 where
8 literal name True = Complement (Variable name)
9 literal name False = Variable name
10
11variables :: Formula -> [String]
12variables (Value _) = []
13variables (Variable x) = [x]
14variables (Complement f) = variables f
15variables (Disjoin fs) = sort . nub $ concatMap variables fs
16variables (Conjoin fs) = sort . nub $ concatMap variables fs
17
18environments :: [String] -> [[(String, Bool)]]
19environments [] = [[]]
20environments (x:xs) =
21 [(x,value) : env | value <- [False,True], env <- environments xs]
22
23evaluate :: [(String, Bool)] -> Formula -> Bool
24evaluate _ (Value b) = b
25evaluate env (Variable x) = maybe False id (lookup x env)
26evaluate env (Complement f) = not (evaluate env f)
27evaluate env (Disjoin fs) = any (evaluate env) fs
28evaluate env (Conjoin fs) = all (evaluate env) fs
对于每个使公式为假的环境,生成一个对应子句排除它。
方法二:代数变换法
1toConjunctiveNormalForm :: Formula -> Formula
2toConjunctiveNormalForm = fromClauses . cnfClauses . toNNF
3 where
4 fromClauses clauses = Conjoin (map Disjoin clauses)
5
6 toNNF (Complement (Conjoin fs)) = Disjoin (map (toNNF . Complement) fs)
7 toNNF (Complement (Disjoin fs)) = Conjoin (map (toNNF . Complement) fs)
8 toNNF (Complement (Complement f)) = toNNF f
9 toNNF (Complement (Value b)) = Value (not b)
10 toNNF (Conjoin fs) = Conjoin (map toNNF fs)
11 toNNF (Disjoin fs) = Disjoin (map toNNF fs)
12 toNNF f = f
13
14 cnfClauses (Value True) = []
15 cnfClauses (Value False) = [[]]
16 cnfClauses (Conjoin fs) = concatMap cnfClauses fs
17 cnfClauses (Disjoin fs) = foldl distribute [[]] (map cnfClauses fs)
18 cnfClauses literal = [[literal]]
19
20 distribute left right =
21 [nub (leftClause ++ rightClause)
22 | leftClause <- left, rightClause <- right]
toNNF 先用德摩根律把否定推到变量上。cnfClauses 用 [[Formula]] 表示“子句的合取”,遇到析取时计算两个子句集合的笛卡尔积,从而完整应用分配律。这个方法不枚举真值环境,通常比方法一生成更紧凑的 CNF。
方法对比
| 方法 | 特点 | 代价 |
|---|---|---|
| 真值表 | 定义直接,结果容易验证 | 变量数为 n 时枚举 2^n 个环境 |
| 代数变换 | 不枚举赋值,保留公式结构 | 分配律仍可能导致指数膨胀 |
测试
1>>> toConjunctiveNormalForm (Disjoin [Variable "x", Variable "y"])
2Conjoin [Disjoin [Variable "x",Variable "y"]]
3>>> toConjunctiveNormalForm
4... (Disjoin [Variable "x", Conjoin [Variable "y", Variable "z"]])
5Conjoin [Disjoin [Variable "x",Variable "y"],Disjoin [Variable "x",Variable "z"]]