DPLL 算法(求解k-SAT问题)详解(C++实现)

本文详细介绍了DPLL算法,一种用于判定命题逻辑公式可满足性的完备搜索算法,主要应用于求解k-SAT问题。文章首先阐述了SAT问题的基本概念和暴力解决方案,接着详细讲解了DPLL算法的思路、化简步骤(单位子句传播和孤立文字消去)以及算法流程。还提供了C++实现的具体细节和代码示例,帮助读者理解DPLL算法的运作过程。
摘要由CSDN通过智能技术生成

By  C h e s i u m \text{By}\ \mathsf{Chesium} By Chesium

DPLL 算法,全称为 Davis-Putnam-Logemann-Loveland(戴维斯-普特南-洛吉曼-洛夫兰德)算法,是一种完备的,基于回溯(backtracking)的搜索算法,用于判定命题逻辑公式(为合取范式形式)的可满足性,也就是求解 SAT(布尔可满足性问题)的一种(或者一类)算法。

SAT 问题简介

何为布尔可满足性问题?给定一条真值表达式,包含逻辑变量(又称 变量命题变号原子,用小写字母 a , b , … a,b,\dots a,b, 表示)、逻辑与(AND,记为 “ ∧ \wedge ” )运算符、逻辑或(OR,记为 “ ∨ \vee ” )运算符以及(NOT,否定,记为“ ¬ \neg ¬”)运算符,如:
( a ∧ ¬ b ∧ ( ¬ ( c ∨ d ∨ ¬ a ) ∨ ( b ∧ ¬ d ) ) ) ∨ ( ¬ ( ¬ ( ¬ b ∨ a ) ∧ c ) ∧ d ) (a\wedge\neg b\wedge(\neg(c\vee d\vee\neg a)\vee(b\wedge\neg d)))\vee(\neg(\neg(\neg b\vee a)\wedge c)\wedge d) (a¬b(¬(cd¬a)(b¬d)))(¬(¬(¬ba)c)d)
是否存在一组对这些变量的赋值(如把所有 a a a d d d 均赋值为 T r u e \mathrm{True} True ,将所有 b b b c c c 赋值为 F a l s e \mathrm{False} False ),使得整条式子最终的运算结果为 T r u e \mathrm{True} True ?若可以,那么这个性质被称为这条逻辑公式的可满足性(satisfiability),如何快速高效地判断任意指定逻辑公式的可满足性是理论计算机科学中的一个重要的问题,也是第一个被证明为NP-完全(NP-complete,NPC)的问题。

暴力方案

对于这个问题,我们能够很容易地想到一种“暴力”的判定方法:测试这些变量赋值的每种可能的排列方式(如全部赋为 T r u e \mathrm{True} True 、其一为 T r u e \mathrm{True} True 其他全为 F a l s e \mathrm{False} False ……),若存在一种赋值排列使得公式的结果为 T r u e \mathrm{True} True ,那么就可以说明这条公式是可满足的。但很显然,最坏情况下这种方法需要我们测试 2 n 2^n 2n 种( n n n 为变量数)赋值排列,而用于检查每种赋值排列最终的运算结果也是不可忽略的。因此,随着公式规模的扩大,这种暴力算法所需的运算量会呈指数级飞快增长,这是我们不可接受的。

算法概述

但是根据现有计算复杂度理论,SAT问题是无法在多项式时间复杂度内解决的,DPLL算法也不例外。

DPLL算法是一种搜索算法,思想与DFS(Depth-first search,深度优先搜索)十分相似,或者说DPLL算法本身就属于DFS的范畴,其类似于上述我们设想的“暴力”算法:搜索所有可能的赋值排列。

具体地说,算法会在公式中选择一个变量(命题变号),将其赋值为 T r u e \mathrm{True} True ,化简赋值后的公式,如果简化的公式是可满足的(递归地判断),那么原公式也是可满足的。否则就反过来将该变量赋值为 F a l s e \mathrm{False} False ,再执行一遍递归的判定,若也不能满足,那么原公式便是不可满足的。

这被称为 分离规则 (splitting rule),因为其将原问题分离为了两个更加简单的问题。

概念说明

DPLL算法求解的是合取范式(Conjunctive normal form,CNF),这是指形如下式的逻辑公式:
( a ∨ b ∨ ¬ c ) ∧ ( ¬ d ∨ x 1 ∨ ¬ x 2 ∨ ⋯ ∨ x 7 ) ∧ ( ¬ r ∨ v ∨ g ) ∧ ⋯ ∧ ( a ∨ d ∨ ¬ d ) (a\vee b\vee\neg c)\wedge (\neg d\vee x_1\vee\neg x_2\vee\dots\vee x_7)\wedge (\neg r\vee v\vee g)\wedge\dots\wedge (a\vee d\vee\neg d) (ab¬c)(¬dx1¬x2x7)(¬rvg)(ad¬d)
其由多个括号括住部分的逻辑与组成,每一个括号内又是许多变量或变量的否定(逻辑非)的逻辑或组成。可以证明,所有只包含逻辑与、逻辑或、逻辑非、逻辑蕴含和括号的逻辑公式均可化为等价的合取范式。下面,我们称整个范式为“公式”,称每个括号里的部分为该公式的子句(clause),每个子句中的每个变量或其否定为文字(literal)。

可以看出,要使整条公式结果为 T r u e \mathrm{True} True ,其所有子句都必须为 T r u e \mathrm{True} True ,也就是说,每个子句中都至少有一个文字为 T r u e \mathrm{True} True ,这个结论下面会用到。

DPLL 算法中的化简步骤实际上就是移除所有在赋值后值为 T r u e \mathrm{True} True 的子句,以及所有在赋值后值为 F a l s e \mathrm{False} False 的文字。

化简步骤

这两个化简步骤是 DPLL 算法与我们“暴力”算法的主要区别,它们大大减少了搜索量,亦即加快了算法的运行速度。

第一个化简步骤:单位子句传播(Unit propagation)

我们称只含有一个(未赋值)变量的子句为单位子句(unit clause),根据上面的结论,要想让公式为 T r u e \mathrm{True} True ,这个子句必须为 T r u e \mathrm{True} True ,即这个变量对应的文字必须被赋值为 T r u e \mathrm{True} True

比如下面的这条公式:
( a ∨ b ∨ c ∨ ¬ d ) ∧ ( ¬ a ∨ c ) ∧ ( ¬ c ∨ d ) ∧ ( a ) (a\vee b\vee c\vee\neg d)\wedge(\neg a\vee c)\wedge(\neg c\vee d)\wedge(a) (abc¬d)(¬ac)(¬cd)(a)
其中最后一个子句就为单位子句,亦即我们要使文字 ( a ) (a) (a) T r u e \mathrm{True} True

然后,我们要依次处理这个变量在其他子句中的出现,如果另一个子句中的一个文字与单位子句中的文字相同,如上面例子中的 (

  • 38
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chesium

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值