显然,P = NP [关闭]


111

SAT是确定布尔表达式是否可以设为真的问题。例如,可以通过设置A = TRUE来使(A)为真,但是(A &&!A)永远不会为真。已知此问题是NP完全的。请参阅布尔可满足性

您的任务是编写一个在多项式时间内执行的SAT程序,但可能无法解决所有情况。

对于某些示例,它不是真正的多项式的原因可能是:

  1. 有一个边缘情况不明显,但运行时很差
  2. 在某些意外情况下,该算法实际上无法解决问题
  3. 您正在使用的编程语言的某些功能实际上具有比您合理预期的更长的运行时间
  4. 您的代码实际上所做的事情与看上去的完全不同

您可以使用所需的任何编程语言(或多种语言的组合)。您无需提供算法复杂性的正式证明,但至少应提供一个解释。

判断的主要标准应该是代码的说服力。

这是一次人气竞赛,因此一周内获得最高评分的答案将获胜。


11
如果限制问题域,那将更好,否则您将围绕“众所周知”的内容调用不确定性云。为什么不选择一个NP难题来集中精力呢?这样做的好处是,其他类似的问题在同一路线上也留给以后的问题开放。与一个广泛的问题相比,几个狭窄的问题可以为网站带来更多持续的乐趣和娱乐性。
Jonathan Van Matre 2014年

9
@ gnasher729:我得到了C#编译器来解决SAT问题;我认为这是一个相当有趣的成就。
埃里克·利珀特

9
如果有人在这里偶然地在多项式时间内求解SAT,那将很有趣。
Turion 2014年

5
@Turion数十年的研究工作,成千上万的奖励和奖励,以及可能拥有的所有妇女和名望-但是解决P = NP的真正动机将最终成为PCG的挑战。
没什么事,2014年

3
我投票结束这个问题是因为没有话题,因为本网站不再欢迎那些处理不足的挑战。meta.codegolf.stackexchange.com/a/8326/20469

Answers:


237

C#

您的任务是为SAT编写一个似乎在多项式时间内执行的程序。

“出现”是不必要的。我可以编写一个确实在多项式时间内执行的程序来解决SAT问题。实际上,这非常简单。

超级奖金:如果编写一个实际上在多项式时间内执行的SAT求解器,您将获得一百万美元!但无论如何,请使用扰流器标签,以便其他人对此感到疑惑。

太棒了 请寄给我一百万美元。认真地说,我这里有一个程序,可以用多项式运行时求解SAT。

首先,我要说明我要解决的SAT问题。我将演示如何编写一个展示任何3-SAT问题的独特解决方案的程序。每个布尔变量的值对于我的求解器来说必须是唯一的。

我们首先声明一些简单的辅助方法和类型:

class MainClass
{
    class T { }
    class F { }
    delegate void DT(T t);
    delegate void DF(F f);
    static void M(string name, DT dt)
    {
        System.Console.WriteLine(name + ": true");
        dt(new T());
    }
    static void M(string name, DF df)
    {
        System.Console.WriteLine(name + ": false");
        df(new F());
    }
    static T Or(T a1, T a2, T a3) { return new T(); }
    static T Or(T a1, T a2, F a3) { return new T(); }
    static T Or(T a1, F a2, T a3) { return new T(); }
    static T Or(T a1, F a2, F a3) { return new T(); }
    static T Or(F a1, T a2, T a3) { return new T(); }
    static T Or(F a1, T a2, F a3) { return new T(); }
    static T Or(F a1, F a2, T a3) { return new T(); }
    static F Or(F a1, F a2, F a3) { return new F(); }
    static T And(T a1, T a2) { return new T(); }
    static F And(T a1, F a2) { return new F(); }
    static F And(F a1, T a2) { return new F(); }
    static F And(F a1, F a2) { return new F(); }
    static F Not(T a) { return new F(); }
    static T Not(F a) { return new T(); }
    static void MustBeT(T t) { }

现在让我们选择一个3-SAT问题来解决。比方说

(!x3) & 
(!x1) & 
(x1 | x2 | x1) & 
(x2 | x3 | x2)

让我们再加上一点括号。

(!x3) & (
    (!x1) & (
        (x1 | x2 | x1) & 
        (x2 | x3 | x2)))

我们这样编码:

static void Main()
{
    M("x1", x1 => M("x2", x2 => M("x3", x3 => MustBeT(
      And(
        Not(x3),
        And(
          Not(x1),
          And(
            Or(x1, x2, x1),
            Or(x2, x3, x2))))))));
}

可以肯定的是,当我们运行该程序时,我们在多项式时间内获得了3-SAT的解决方案。实际上,运行时间是问题大小的线性函数

x1: false
x2: true
x3: false

您说的是多项式运行时。您没有提到多项式编译时间。该程序强制C#编译器尝试x1,x2和x3的所有可能的类型组合,并选择不显示任何类型错误的唯一组合。编译器完成所有工作,因此运行时不必这样做。我首先在2007年的博客上展示了这种有趣的技术:http : //blogs.msdn.com/b/ericlippert/archive/2007/03/28/lambda-expressions-vs-anonymous-methods-part-five.aspx注意当然,该示例表明C#中的重载分辨率至少为NP-HARD。是NP-HARD还是不确定 在泛型协变存在的情况下,类型可转换如何工作取决于某些细微的细节,但这是另一回事。


95
您必须为您的百万美元联系粘土数学研究所。但是我不确定他们是否会满意
乔纳森·铂拉诺2014年

15
当然,任何SAT问题都可以转换为等效的3-SAT问题,因此此限制仅是一个不便。我的“解决方案”更令人烦恼的问题是,它要求问题拥有独特的解决方案。如果没有解决方案或有多个解决方案,则编译器将给出错误。
埃里克·利珀特

11
@EricLippert唯一性要求还可以。您始终可以使用多项式时间随机归约法将SAT还原为Unique-SAT(SAT,但假设输入具有0或1个分配)。关键字:隔离引理,Valiant-Vazirani定理。
Diego de Estrada

44
“很严重,我这里有一个程序,可以用多项式运行时求解SAT。” -我也是,但很遗憾,它不适合此评论框。
CompuChip 2014年

11
@Kobi:是的,这是个玩笑。
埃里克·利珀特

166

多国语言(1个字节)

以下程序对许多SAT问题都具有正确的答案,并且具有恒定的复杂度(!!!),该程序适用于多种语言,主要是功能性的和深奥的,并且会给出正确的答案:

0

令人惊讶的是,下一个程序将为所有剩余的问题给出正确的答案,并且具有相同的复杂性。因此,您只需选择正确的程序,即可在所有情况下获得正确的答案!

1

6
这真太了不起了。我笑得很开心。
Karl Damgaard Asmussen 2014年

2
绝对是太棒了!
蓝狗

78
嗯 现在很容易。我需要做的就是编写一个可以选择正确程序的程序!
Cruncher

恰好!:-)
Mau

6
让人联想到xkcd.com/221
msh210

34

的JavaScript

通过使用迭代的不确定性,可以在多项式时间内求解SAT!

function isSatisfiable(bools, expr) {
    function verify() {
        var values = {};
        for(var i = 0; i < bools.length; i++) {
            values[bools[i]] = nonDeterministicValue();
        }
        with(values) {
            return eval(expr);
        }
    }
    function nonDeterministicValue() {
        return Math.random() < 0.5 ? !0 : !1;
    }

    for(var i = 0; i < 1000; i++) {
        if(verify(bools, expr)) return true;
    }
    return false;
}

用法示例:

isSatisfiable(["a", "b"], "a && !a || b && !b") //returns 'false'

该算法仅用随机输入检查给定的布尔公式一千次。几乎总是适用于较小的输入,但引入更多变量时可靠性较低。

顺便说一句,我很自豪,我不得不利用两个紧挨着彼此的JavaScript的最充分利用的功能的机会:evalwith


4
这实际上是一种完善的测试方法。我相信,Haskell的QuickCheck库开始了所有的乐趣。从那以后,它已经以多种语言重新实现。
约翰·泰瑞

4
我认为应该指出,这个程序返回正确答案的可能性越小,sat表达式越大。的1000在for循环某种程度上应与输入大小(一些多项式非O(1)缩放)比例的。
Cruncher 2014年

2
@Cruncher更精确地说,变量数量越大,返回正确答案的可能性就越小。(例如,带有单个变量的很长的表达式几乎总是会返回正确的答案)
Peter Olson

2
@TimSeguine我承认在这种情况下我对“不确定性”一词的使用充其量是可疑的,就像声称SAT可以在多项式时间内求解一样。我知道这是不正确的,这只是欺骗游戏的一部分。
彼得·奥尔森

4
@PaulDraper,然后称他们未被充分利用!我笑了!
罗布

32

Mathematica +量子计算

您可能不知道Mathematica附带了量子计算机

Needs["Quantum`Computing`"];

量子绝热计算以哈密顿量(能量算子)的方式编码要解决的问题,以其最小能量状态(“基态”)表示解决方案。因此,量子系统的绝热演化为哈密顿量的基态并随后进行测量就可以解决该问题。

我们定义一个||与表达式部分相对应的亚哈密顿数,并使用Pauli运算符对变量及其取反的适当组合

在此处输入图片说明

在哪里表达这样的

expr = (! x3) && (! x1) && (x1 || x2 || x1) && (x2 || x3 || x2);

该参数应该看起来像

{{{1, x3}}, {{1, x1}}, {{0, x1}, {0, x2}, {0, x1}}, {{0, x2}, {0, x3}, {0, x2}}}

这是从布尔表达式构造此类参数的代码:

arg = expr /. {And -> List, Or -> List, x_Symbol :> {0, x}, 
    Not[x_Symbol] :> {1, x}};
If[Depth[arg] == 3, arg = {arg}];
arg = If[Depth[#] == 2, {#}, #] & /@ arg

现在我们构造一个完整的哈密顿量,将亚哈密顿量相加(总和对应于&&表达式的各个部分)

H = h /@ arg /. List -> Plus;

并寻找最低能量状态

QuantumEigensystemForm[H, -1]

在此处输入图片说明

如果我们的特征值是零,那么特征向量就是解

expr /. {x1 -> False, x2 -> True, x3 -> False}
> True

不幸的是,“ Quantum Computing”附加组件的官方网站尚未激活,我找不到下载它的地方,我仍然将其安装在计算机上。该插件还具有针对SAT问题的书面解决方案,我以此为基础编写了代码。


19
我不知道这个答案如何工作。+1
乔纳森·铂拉诺2014年

5
@XiaogeSu“自然”。

3
@XiaogeSu由哈密顿量确定的演化,自然而然地演化为最低能量。因此,了解频谱后,我们可以假设系统最终将处于基态。
2014年

3
@XiaogeSu为了进入基态,还需要与激发高阶状态的环境进行交互,这是正确的。这里的想法是这种相互作用很小,“绝热”。
Turion 2014年

3
FYI 绝热QM计算有很多相似之处的经典模拟退火。现在由Dwave 实现。其类似于“冷却”温度/能源系统,可以“发现/解决” 局部最小值
vzn 2014年

27

这里的三种方法都涉及将SAT简化为2D几何通用语言:非图逻辑难题。逻辑难题中的单元格对应于SAT变量,即从句的约束。

有关完整的解释(并请查看我的代码中的错误!),我已经对非图解决方案空间中的模式发表了一些见解。参见https://codereview.stackexchange.com/questions/43770/nonogram-puzzle-solution-space。枚举超过40亿个难题解决方案并将其编码为适合真值表的结果显示了分形模式-自相似性,尤其是自相似性。这种仿射冗余说明了问题内的结构,可用于减少生成解决方案所需的计算资源。它还表明在任何成功的算法中都需要混沌反馈。在相变行为中具有解释力,其中“简单”实例是沿粗结构放置的实例,而“硬”实例需要进一步迭代为精细的细节,这与常规启发法完全不同。如果您想放大此无限图像的一角(对所有<= 4x4个拼图实例进行编码),请参见http://re-curse.github.io/visualizing-intractability/nonograms_zoom/nonograms.html

方法1.使用混沌映射和机器学习来推断非图解空间阴影(请考虑拟合函数,类似于生成Mandelbrot集的函数)。

http://i.stack.imgur.com/X7SbP.png

这是归纳的视觉证明。如果您可以从左到右扫描这四个图像,并且认为您有一个好主意可以生成丢失的第5个...第6个...等图像,那么我刚刚将编程为我的NP oracle,用于解决非图论的决策问题存在。请前来索取您作为世界上最强大的超级计算机的奖项。我会时不时地给您供电,而世界感谢您的计算贡献。

方法2。对输入的布尔图像版本使用傅里叶变换。FFT提供有关实例中频率和位置的全局信息。尽管输入对之间的幅度部分应该相似,但它们的相位信息却完全不同-包含有关沿特定轴的解决方案投影的方向性信息。如果您足够聪明,可以通过输入相位图的一些特殊叠加来重建解决方案的相位图。然后将相位和公共幅度逆变换回解的时域。

这种方法可以解释什么?布尔图像的许多排列在连续运行之间具有灵活的填充。这允许在输入->解决方案之间进行映射,同时考虑多重性,同时仍保留FFT在时域<->(频率,相位)之间双向,唯一映射的属性。这也意味着没有“没有解决方案”之类的东西。它会说的是,在连续的情况下,当查看传统的非图拼图解决方案的二层图像时,您无需考虑灰度解决方案。

你为什么不这样做?这是一种实际的计算方式,因为当今的浮点运算中的FFT对于大型实例而言非常不准确。精度是一个巨大的问题,从量化的幅值和相位图像重建图像通常会创建非常近似的解决方案,尽管对于人眼阈值可能不是视觉上的。由于目前尚不清楚实际执行的功能类型,因此很难提出这种叠加业务。这是一个简单的平均方案吗?可能没有,除了直觉之外,没有找到特定的搜索方法。

方法3.找到一个解决自动图难题的对称形式的元胞自动机规则(从可能的约40亿个冯·诺依曼2状态规则的规则表中)。您可以将问题直接嵌入到单元格中,如下所示。 保守的对称非图

就简单性和对未来计算的良好影响而言,这可能是最优雅的方法。该规则的存在尚未得到证明,但我有一种预感。原因如下:

非图需要在算法中进行大量混沌反馈才能精确求解。这由链接到“代码审阅”上的蛮力代码确定。CA只是最有能力编写混沌反馈的语言。

看起来正确,直观。该规则将通过嵌入来发展,在水平和垂直方向上传播信息,进行干涉,然后稳定到节省设置的像元数量的解决方案。当将物理对象的阴影投影到原始配置中时,此传播路径遵循您通常想到的路径(向后)。非图来自离散层析成像的一种特殊情况,因此,想象一下同时坐在两个成猫角的CT扫描仪中。这就是X射线会促进产生医学图像的方式。当然,存在边界问题-CA Universe的边缘无法使信息传播超出限制,除非您允许使用环形Universe。这也使难题成为一个周期性的边值问题。

它解释了作为瞬态的多种解决方案,它们在交换输出作为输入之间存在连续振荡的作用,反之亦然。它解释了没有解决方案的实例作为不节省设置单元格数量的原始配置。根据找到这样的规则的实际结果,它甚至可能接近无法解决的情况下,与其中电池状态的接近的解决方案保守的。


2
+1离开我说:“为什么没有想的是什么?” :P
纳文2014年

您是斯蒂芬·沃尔夫拉姆,我索要我的五英镑!
Quuxplusone 2014年

4
这个答案确实值得得到更多的赞誉,因为这是制作令人信服的程序的最佳尝试。不错的秀。
Jonathan Pullano 2014年

10

C ++

这是一个保证可以在多项式时间内运行的解决方案:它在boolean数O(n^k)所在的地方运行,n并且k是您选择的常数。

启发式是正确的,我相信这是CS所说的,“它在大多数情况下给出了正确的答案,但有些运气”(在这种情况下,k-的值很大- 编辑它实际上对我来说是对于任何固定的n,你可以设置k这样n^k > 2^n? -就是作弊)。

#include <iostream>  
#include <cstdlib>   
#include <time.h>    
#include <cmath>     
#include <vector>    

using std::cout;     
using std::endl;     
typedef std::vector<bool> zork;

// INPUT HERE:

const int n = 3; // Number of bits
const int k = 4; // Runtime order O(n^k)

bool input_expression(const zork& x)
{
  return 
  (!x[2]) && (
    (!x[0]) && (
      (x[0] || x[1] || x[0]) &&
      (x[1] || x[2] || x[1])));
}

// MAGIC HAPPENS BELOW:    

 void whatever_you_do(const zork& minefield)
;void always_bring_a_towel(int value, zork* minefield);

int main()
{
  const int forty_two = (int)pow(2, n) + 1;
  int edition = (int)pow(n, k);
  srand(time(6["times7"]));

  zork dont_panic(n);
  while(--edition)
  {
    int sperm_whale = rand() % forty_two;
    always_bring_a_towel(sperm_whale, &dont_panic);

    if(input_expression(dont_panic))
    {
      cout << "Satisfiable: " << endl;
      whatever_you_do(dont_panic);
      return 0;
    }
  }

  cout << "Not satisfiable?" << endl;
  return 0;
}
void always_bring_a_towel(int value, zork* minefield)
{
  for(int j = 0; j < n; ++j, value >>= 1)
  {
    (*minefield)[j] = (value & 1);
  }
}

void whatever_you_do(const zork& minefield)
{
  for(int j = 0; j < n; ++j) 
  {
    cout << (char)('A' + j) << " = " << minefield[j] << endl;
  }
}

好答案。我会将解释放在扰流标签中,以便人们凝视它并挠头。
乔纳森·铂拉诺

感谢@JonathanPullano的建议,我添加了一个破坏代码,并对代码进行了一些混淆。
CompuChip 2014年

顺便说一句,我只是发现了bitfield,也许我会更喜欢std::vector
CompuChip 2014年

3
+1为创意迷惑和旅行者的参考
Blake Miller 2014年

2
是的,这当然是骗人的,如果k依赖于n,那么它就不是常数:-)
RemcoGerlich 2014年

3

红宝石/ gnuplot 3d表面

(激烈的竞争!)...反正……一幅价值一千个字的图片吗?这是在SAT过渡点的gnuplot中绘制的3个单独的表面图。(x,y)轴是子句和变量计数,z高度是求解器中递归调用的总数。用ruby编写的代码。它以10个10x10点采样,每个点有100个采样。它演示/使用了统计的基本原理,并且是蒙特卡洛模拟

它基本上是在DIMACS格式生成的随机实例上运行的davis putnam算法。理想情况下,这是在全世界的CS课中进行的练习类型,以便学生可以学习基础知识,但几乎根本没有专门教过……也许是为什么有这么多伪造的P?= NP证明的某些原因?甚至没有一篇好的维基百科文章描述过渡点现象(任何参与者?),这是统计物理学中非常重要的主题,也是CS中的关键。[a] [b] CS中有很多关于过渡点的论文但是,似乎很少有表面图!(通常显示2d切片。)

第一个图中,运行时间呈指数增长很明显。穿过第一个图的中间的鞍是过渡点。的2 和3 曲线示出了可满足%过渡。

[a] CS ppt Toby Walsh中的相变行为
[b] k-SAT可满足性 tcs.se的经验概率
[c] 经验/实验数学/(T)CS / SAT中的重要时刻,TMachine博客

在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

P =?NP QED!

#!/usr/bin/ruby1.8

def makeformula(clauses)
    (1..clauses).map \
    {
            vars2 = $vars.dup
            (1..3).map { vars2.delete_at(rand(vars2.size)) * [-1, 1][rand(2)] }.sort_by { |x| x.abs }
    }

end

def solve(vars, formula, assign)

    $counter += 1
    vars2 = []
    formula.each { |x| vars2 |= x.map { |y| y.abs } }
    vars &= vars2

    return [false] if (vars.empty?)
    v = vars.shift
    [v, -v].each \
    {
            |v2|
            f2 = formula.map { |x| x.dup }
            f2.delete_if \
            {
                    |x|
                    x.delete(-v2)
                    return [false] if (x.empty?)
                    x.member?(v2)
            }
            return [true, assign + [v2]] if (f2.empty?)
            soln = solve(vars.dup, f2, assign + [v2])
            return soln if (soln[0])
    }
    return [false]
end

def solve2(formula)
    $counter = 0
    soln = solve($vars.dup, formula, [])
    return [$counter, {false => 0, true => 1}[soln[0]]]
end


c1 = 10
c2 = 100
nlo, nhi = [3, 10]
mlo, mhi = [1, 50]
c1.times \
{
    |n|
    c1.times \
    {
            |m|
            p1 = nlo + n.to_f / c1 * (nhi - nlo)
            p2 = mlo + m.to_f / c1 * (mhi - mlo)
            $vars = (1..p1.to_i).to_a
            z1 = 0
            z2 = 0
            c2.times \
            {
                    f = makeformula(p2.to_i)
                    x = solve2(f.dup)
                    z1 += x[0]
                    z2 += x[1]
            }
#           p([p1, p2, z1.to_f / c2, z2.to_f / c2]) # raw
#           p(z1.to_f / c2)                         # fig1
#           p(0.5 - (z2.to_f / c2 - 0.5).abs)       # fig2
            p(z2.to_f / c2)                         # fig3
    }
    puts
}

2
很高兴您提供了这个答案。无论哪种方式,无论是P还是NP的成功证明,它都是预测能力的众多要求之一。感谢您指出其重要性。:)

P vs NP上有更多的沉思,很多顶级/收集的裁判等等
vzn 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.