56

每当人们询问与编程有关的停机问题时,人们都会回答“如果你只添加一个循环,你就会得到停机程序,因此你不能自动化任务

说得通。如果您的程序有一个无限循环,那么当您的程序运行时,您无法知道程序是否仍在处理输入,或者它是否只是无限循环。

但其中一些似乎违反直觉。如果我正在编写一个以源代码作为输入的停机问题解决程序会怎样。rascher@localhost$ ./haltingSolver source.c

如果我的代码(source.c)如下所示:

for (;;) {  /* infinite loop */  }

我的程序似乎很容易看到这一点。“看看循环,看看条件。如果条件只是基于文字,没有变量,那么你总是知道循环的结果。如果有变量(例如 while (x < 10)),看看是否这些变量永远不会被修改。如果没有,那么你总是知道循环的结果。

当然,这些检查不会是微不足道的(计算指针算术等),但似乎并非不可能。例如:

int x = 0
while (x < 10) {}

可以被检测到。连同 - 尽管不是微不足道的:

int x = 0
while (x < 10)
{
   x++;
   if (x == 10)
   {
      x = 0
   }
}

现在用户输入呢?这就是踢球者,这就是使程序不可预测的原因。

int x = 0;
while (x < 10) 
{
   scanf("%d", &x); /* ignoring infinite scanf loop oddities */
}

现在我的程序可以说:“如果用户输入 10 或更大,程序将停止。在所有其他输入时,它将再次循环。”

这意味着,即使有数百个输入,也应该能够列出程序将停止的条件。确实,当我编写程序时,我总是确保有人能够终止它!我并不是说生成的条件列表很容易创建,但对我来说似乎并非不可能。您可以从用户那里获取输入,使用它们来计算指针索引等 - 但这只会增加条件数量以确保程序将终止,并不会导致无法枚举它们。

那么究竟什么是停机问题呢?关于我们不能编写问题来检测无限循环的想法,我有什么不明白的?或者,为什么“循环”是一个经常被引用的例子?

更新

所以,让我稍微改变一下这个问题:什么是适用于计算机的停机问题?然后我会回应一些评论:

很多人都说过,程序必须能够处理“任意输入”。但是在计算机中,从来没有任何任意输入。如果我只输入一个字节的数据,那么我只有 2^8 个可能的输入。所以,举个例子:

int c = getchar()

switch (c) {
   case 'q':
      /* quit the program */
}

突然间,我刚刚考虑了所有的可能性。如果c位模式为 0x71,它会做一件事。对于所有其他模式,它会做其他事情。即使是接受任意字符串输入的程序也永远不会真正“任意”,因为资源是有限的,这意味着虽然“任意”理论适用……但它与实践并不完全是一对一的。

人们引用的另一个例子是:

while (n != 1)
    if (n & 1 == 1) 
        n = 3 * n + 1;
    else 
        n /= 2;

如果 n 是一个 32 位整数......那么我可以直观地告诉你这是否会停止。

我想这个编辑没有问任何问题,但我见过的最有说服力的例子是这个

假设您有您的神奇程序/方法来确定程序停止。

public bool DeterminesHalt(string filename, string[] args){
    //runs whatever program you tell it do, passing any args
    //returns true if the program halts, false if it doesn't
}

现在假设我们编写了一小段代码,例如...

public static void Main(string[] args){
    string filename = Console.ReadLine(); //read in file to run from user
    if(DeterminesHalt(filename, args))
        for(;;);
    else
        return;
}

所以对于这个例子,我们可以编写一个程序来做与我们神奇的停止方法完全相反的事情。如果我们以某种方式确定给定程序将停止,我们就会跳入无限循环;否则,如果我们确定程序处于无限循环中,则结束程序。

再说一次,如果你故意编写一个包含无限循环的程序......“解决停机问题”有点没有意义,不是吗?

4

25 回答 25

53

编辑(比原始答案晚得多):Good Math,Bad Math的 MarkCC最近用具体的例子写了一篇关于停止问题的精彩讨论

停止问题基本上是一种询问您是否可以判断任意程序最终是否会停止的正式方式。

换句话说,你能不能写一个叫做停止预言机的程序 HaltingOracle(program, input),如果 program(input) 最终会停止则返回 true,否则返回 false?

答案是:不,你不能。

跟进有关停止问题的输入是否相关或红鲱鱼的问题:是的,输入很重要。此外,似乎有些混乱,因为我看到在“任意”更正确的地方使用了“无限”。

实际示例:假设您在 QA 职位上工作,并且您要编写一个停止检查程序(又名 oracle),该程序将确认开发团队 (D) 编写的任何任意程序以及最终提供的任意输入-user (I),当给定输入 I 时,程序 D 最终将停止。

提示管理员声音:“嗬嗬,那些愚蠢的用户,让我们确保无论他们输入什么垃圾,我们的服务器任务都不会陷入无限循环。让它如此,代码猴子!”

这似乎是个好主意,对吧?你不希望你的服务器挂起,对吧?

停机问题告诉您的是,您正在接受一项无法解决的任务。相反,在这种特殊情况下,您需要计划运行超过阈值时间的任务并准备取消它们。

Mark 使用代码而不是输入来说明问题:

def Deciever(i):
  oracle = i[0]
  in = i[1]
  if oracle(Deceiver, i):
    while True:
      continue
  else:
    return i

在评论中的讨论中,我走上了恶意输入操作的路线,以强制解决无法解决的问题。马克的例子要优雅得多,使用停止的预言来打败自己:

因此,Deceiver 的输入实际上是一个包含两个元素的列表:第一个是提议的暂停预言机。第二个是另一个输入。停止杀手所做的是询问 Oracle:“你认为我会停止输入 i 吗?”。如果预言机说“是的,你会停下来”,那么程序就会进入无限循环。如果预言机说“不,你不会停止”,那么它就会停止。所以无论神谕说什么,都是错的。

换句话说,在没有作弊、重新格式化输入、可数/不可数无穷或任何其他干扰的情况下,Mark 编写了一段可以击败任何停止的预言机程序的代码。你不能写一个oracle回答是否Deceiver停止的问题。

原答案:

来自伟大的维基百科

在可计算性理论中,停机问题是一个决策问题,可以表述如下:给定程序的描述和有限的输入,在给定输入的情况下,决定程序是完成运行还是永远运行。

Alan Turing 在 1936 年证明,不可能存在解决所有可能的程序输入对的停止问题的通用算法。我们说停机问题在图灵机上是不可判定的。Copeland (2004) 将实际期限停止问题归因于 Martin Davis。

关键点之一是您无法控制程序或输入。你已经拿到了这些,由你来回答这个问题。

还要注意,图灵机是有效的可计算性模型的基础。换句话说,你用现代计算机语言所做的一切都可以映射回这些原型图灵机。结果,停止问题在任何有用的现代语言中都是不可判定的。

于 2009-07-10T18:27:15.790 回答
42

要解决停止问题,您必须开发一种算法,该算法可以确定任何任意程序是否会因任意输入而停止,而不仅仅是示例中相对简单的情况。

于 2009-07-10T18:20:44.540 回答
29

下面是对停止问题不可判定的证明的简单解释。

假设您有一个程序 H,它计算程序是否停止。H 有两个参数,第一个是程序的描述 P,第二个是输入 I。如果 P 在输入 I 处停止,则 H 返回 true,否则返回 false。

现在编写一个程序 p2,它将另一个程序 p3 的描述作为输入。p2 调用 H(p3, p3),如果 H 返回 true,则循环,否则停止。

当我们运行 p2(p2) 时会发生什么?

它必须同时循环和停止,导致宇宙爆炸。

于 2009-07-10T18:54:20.057 回答
21

这已经被打死了,实际上有一个诗意的证明,写成的风格,由 Geoffrey Pullum(他以Language Log成名)以Lewis Carroll Dr. Seuss

有趣的事。这里有一个味道:

这是我将使用的技巧——而且操作很简单。
我将定义一个过程,我将其称为 Q,
它将使用 P 的停止成功的预测
来引发一个可怕的逻辑混乱。

...

无论 P 的表现如何,Q 都会将其挖走:
Q 使用 P 的输出让 P 看起来很愚蠢。
无论 P 说什么,它都无法预测 Q:
P 错时是对的,当它是真的时是假的!

于 2009-07-10T19:18:36.997 回答
9

维基百科上有一个可以证明停止问题的证据。

为了准确地说明为什么仅对循环应用一些技术是不够的,请考虑以下程序(伪代码):

int main()
{
  //Unbounded length integer
  Number i = 3;

  while(true)
  {
    //example: GetUniquePositiveDivisiors(6) = [1, 2, 3], ...(5) = 1, ...(10) = 1, 2, 5, etc.
    Number[] divisiors = GetUniquePositiveDivisiors(i);
    Number sum = 0;
    foreach(Number divisor in divisiors) sum += divisor;

    if(sum == i) break;

    i+=2;
  }
}

你能想出一种方法,如果此代码停止,则返回 true,否则返回 false?

仔细想想

如果碰巧你正在认真争夺菲尔兹奖牌,想象一下这些问题的一些代码来代替上面的代码。

于 2009-07-10T18:41:15.900 回答
7

“如果你只添加一个循环,你就有了停止程序,因此你不能自动化任务”

听起来有人过度概括了停止问题的应用。有很多特定的循环可以证明是终止的。存在可以对大类程序执行终止检查的研究。例如,在 Coq 中,您仅限于可以证明终止的程序。Microsoft 有一个名为 Terminator 的研究项目,它使用各种近似值来证明程序将终止。

但是,请记住,停止问题不仅仅是玩具示例。这些都不能解决一般的“停止问题”,因为它们并不适用于每个程序。

问题是停止问题表明存在程序,您无法知道它们是否会在不运行它们的情况下终止,这意味着您可能永远无法确定它们是否停止。

可能会或可能不会停止的程序示例(在 Haskell 中):

collatz 1 = ()
collatz !n | odd n     = collatz (3 * n + 1)
           | otherwise = collatz (n `div` 2)

或更容易获得的东西:

while (n != 1)
    if (n & 1 == 1) 
        n = 3 * n + 1;
    else 
        n /= 2;

给定每个整数 >= 1,这个程序会停止吗?好吧,到目前为止它一直有效,但是没有定理说它会为每个整数停止。由于Lothar Collat​​z ,我们有一个可以追溯到 1937 年的猜想,它成立,但没有证据。

于 2009-07-10T18:37:51.337 回答
5

关于子点“人们回应“如果你只添加一个循环,你就有了暂停程序,因此你不能自动化任务””,我将添加这个细节:

那些说您无法通过算法计算任意程序是否会停止的帖子对于图灵机来说是绝对正确的。

问题是,并非所有程序都需要图灵机。这些程序可以用概念上“较弱”的机器来计算——例如,正则表达式可以完全由有限状态机来体现,它总是在输入时停止。这不是很好吗?

我敢打赌,当人们说“添加一个循环”时,他们试图表达这样一种想法,即当程序足够复杂时,它需要一个图灵机,因此停止问题(作为一种想法)适用。

这可能与问题略有相干,但我相信,鉴于问题中的细节,这值得指出。:)

于 2009-07-12T01:34:29.920 回答
5

图灵的一个很好的例子是自我参照——假设有一个程序可以检查另一个程序并确定它是否会停止。将halting-program-checker ITSELF 输入到halting-program-checker 中——它应该做什么?

于 2009-07-10T18:21:26.910 回答
4

这是一个停止问题永远无法解决的程序。

假设您有您的神奇程序/方法来确定程序停止。

public bool DeterminesHalt(string filename, string[] args){
    //runs whatever program you tell it do, passing any args
    //returns true if the program halts, false if it doesn't
}

现在假设我们编写了一小段代码,例如...

public static void Main(string[] args){
    string filename = Console.ReadLine(); //read in file to run from user
    if(DeterminesHalt(filename, args))
        for(;;);
    else
        return;
}

所以对于这个例子,我们可以编写一个程序来做与我们神奇的停止方法完全相反的事情。如果我们以某种方式确定给定程序将停止,我们就会跳入无限循环;否则,如果我们确定程序处于无限循环中,则结束程序。

无论您进行多少次输入检查,都没有可能的解决方案来确定每个编写的程序是否停止。

于 2009-07-10T18:33:04.483 回答
3

到目前为止,有很多有趣的具体例子/类比。如果您想更深入地了解背景知识,查尔斯·佩佐德 (Charles Petzold ) 的图灵原始论文The Annotated Turing中有一本好书。

在一个相关的,横向排序的静脉中,网上有一篇非常简洁的文章,谁能说出更大的数字?刷图灵机和阿克曼函数。

于 2009-07-10T19:18:16.997 回答
2

这是停止狗问题的一种变体,除了用程序代替狗和停止而不是吠叫。

于 2009-07-12T06:26:46.360 回答
2

已经有很多很好的答案,但我还没有看到有人解决这样一个事实,即在一种理论和实践的选择性混合中,停止问题确实是可以解决的。

所以首先,停止问题基本上是编写一个程序的任务,该程序接受任意第二个程序并确定辅助程序是否会在任意输入时停止。所以你说“是的,这个程序将在这个输入上停止”或“不,它不会”。事实上,在一般情况下(其他人似乎已经提供了证明)在图灵机上是无法解决的。真正的问题是,您可以通过运行某个东西来确定它是否会停止(只是等到它停止),但您无法真正确定运行它是否不会停止(您将只是永远等待)。

这是图灵机上的一个问题,根据定义,它具有无限量的内存,因此具有无限多的状态。然而,我们的计算机只有有限的内存。计算机上只有这么多位。因此,如果您能够以某种方式跟踪您在运行程序时看到的所有先前状态(位配置),则可以保证您的检查器永远不会进入无限循环。如果辅助程序最终停止,您会说“是的,此程序将在此输入时停止”。如果您在它停止之前两次看到相同的位配置,您就会知道“不,它不会”。可能技术上并不重要,但很高兴知道很多时候我们面临的真正“困难”的问题在理论上比在实践中更难。

于 2009-07-10T19:07:02.433 回答
2

另一个角度的证明

假设我们有一个带有 mov、add、jmp 等指令的 cpu,但没有 in 或 out。我们得到了记忆。与其他 CPU 不同,这个 CPU 有另一个寄存器,称为paraReg。这个寄存器就像一个文件,我们可以向里面移动无限的内容,获取它的大小,寻找到它的中间,从中删除一些内容,这些都是通过一些额外的指令来完成的。

在开始之前,让我们定义一些词。程序是一堆指令,它是一个字符串在我们运行程序之前,我们将所有寄存器和内存清零,除了 paraReg ,它保存参数(一个字符串),然后将程序放入内存位置为零并将 ip register 设置为零。进程是程序运行的时候。

现在停止问题可以这样表述:给定任何名为 proObj 的程序(如果它带有参数 para0,我们在它的第一行添加一条指令:mov paraReg,para0),是否有一个程序将 proObj 作为参数并可以决定当 proObj 在 paraReg 设置为零时开始运行时 proObj 是否会停止?

假设我们有这样一个程序,叫做 p1。然后我们可以创建另一个程序,称为 p2,它采用参数 para0。通过p1,我们可以判断内容为para0,参数为para0的程序是否会停止。(我们这样做。构造一个程序,其第一行为[mov paraReg,para0],其余为para0。将此程序命名为pro0。然后我们将paraReg设置为pro0并调用p1。)如果它会停止,我们让p2进入无限循环,否则我们让p2停止。

如果我们将 p2 放入 paraReg 并运行 p2,进程会停止吗?如果它停止了,根据 p2 的定义,我们知道当我们将 p2 放入 paraReg 并运行 p2 时,它不应该停止;同样,如果它没有停止,我们知道当将 p2 放入 paraReg 并运行 p2 时,它应该停止。那么我们可以说没有p2,也没有p1。

于 2018-01-07T02:17:23.770 回答
1

您列出了一些简单的案例。

现在,考虑考虑所有其他情况。

有无数种可能的场景,你必须把它们都列出来。

除非你当然可以概括它。

这就是停机问题的来源。你如何概括它?

于 2009-07-10T18:23:08.123 回答
1

您的程序如何解决Collat​​z 猜想

于 2009-07-10T18:26:58.537 回答
1

摘自Programming Pearls , by Jon Bentley

4.6 习题

5。证明当输入 x 为正整数时该程序终止。

while x != 1 do
    if even(x)
        x = x/2
    else
        x = 3*x +1
于 2009-07-10T18:46:42.007 回答
0

停机问题的意义不在于问题本身的重要性,而在于问题本身的重要性。相反,自动化测试在软件工程中几乎没有实际用途(证明程序停止并不能证明它是正确的,并且在任何情况下,假设算法只为给定的有限输入提供证明,而现实生活软件开发人员会对所有可能的有限输入的测试更感兴趣)。

相反,停止问题是最先被证明不可判定的问题之一,这意味着不存在适用于一般情况的算法。换句话说,图灵在 70 多年前就证明了有些问题是计算机无法解决的——不仅仅是因为还没有找到正确的算法,而是因为这样的算法在逻辑上不存在。

于 2009-08-26T15:28:17.623 回答
0

又一个例子。我最近遇到了一个叫做冰雹数字的东西。这些数字形成一个具有这些规则的序列

f(n) is odd  -  f(n+1) = 3*f(n)+1
f(n) is even -  f(n+1) = f(n)/2

目前,假设所有起点最终都会到达1,然后重复,4,2,1,4,2,1,4,2,1...但是没有证据证明这一点。因此,现在确定一个数字在输入冰雹序列时是否终止的唯一方法是实际计算它,直到你到达 1。

这是我如何理解停机问题的关键。我的理解是,除非您实际运行该程序,否则您不能肯定知道程序将/不会停止。因此,您编写的任何可以肯定地为您提供停止问题答案的程序都必须实际运行该程序。

于 2009-07-10T19:58:54.777 回答
0

问题的精确定义是您需要编写一个执行以下操作的程序: - 采用任意程序 - 确定程序是否在给定任意有限输入到程序中时停止

然而,这是一个非常高的标准。停机问题有许多部分解决方案,但没有通用解决方案。更糟糕的是,即使找到部分解决停机问题的程序也很困难:

BBC h2g2 关于停机问题的文章

如果您确实解决了停机问题,请在rentacoder.com 等网站上为您工作。几个月前,一位名叫 ATuring 的用户在其中发布了一篇帖子,他提供了一份合同来解决停机问题。:)

于 2009-07-10T18:30:22.583 回答
0

我建议阅读以下内容:http://en.wikipedia.org/wiki/Halting_problem,尤其是http://en.wikipedia.org/wiki/Halting_problem#Sketch_of_proof以了解为什么无法解决此问题算法的方式。

于 2009-07-10T18:23:57.357 回答
0

这是我的尝试,带着一粒盐。

停止问题:是否有可能构建一个程序来判断任意程序是否会因任意输入而停止?

让我们称这样的程序为decider

现在假设这两个程序:

program_1 (input){
    loop forever
}

program_2 (input){
    halt
}

因为program_1,我们可以很容易地看出它永远不会在任何输入时停止。同样,我们可以说它program_2总是会在任何输入时停止。

我们可以通过查看他们的源代码而不实际执行程序来判断这一点。

是否可以decider始终查看源代码并分析控制结构以判断程序是否会在输入时停止?

要回答这个问题,假设以下程序:

program_3 (input) {
    
    ...func definition...

    result = func(input)

    if result = 12345

    then loop forever

    else halt
}

假设这func是一个将整数映射到整数的函数。并且还假设 func 没有封闭形式。例如func,可能是一个返回素数序列中输入的素数的函数。

现在我们decider会有麻烦了。为了分析它的源代码,program_3必须知道func(input)映射到什么。所以它必须以某种方式包含所有由func. 但是有无数个整数,因此有无数个这样的映射。所以decider不可能包含所有的映射细节,这进一步意味着decider无法分析program_3.

这回答了我们的问题。不decider,不能总是查看源代码并了解程序的行为方式。它可能适用于某些程序,但并非适用于所有程序。

那么你怎么认为decider可以告诉任何关于program_3. 它必须在其输入上执行/模拟程序以查看func返回的内容。

但假设 iffunc有以下定义:

func (input){
    ...definition of prime(k): return k-th prime number...
    
    result = prime(input)
    
    i = prime(input - 1)
    j = prime(input - 2)

    if(i mod j = 5)

    then loop forever

    else return result
}

现在func可以在某些输入上永远循环,从而导致program_3永远循环。这意味着由于decider必须执行/模拟program_3,如果program_3碰巧永远循环,它也可能永远循环。

这最终解决了停机问题。不,我们不能创建一个decider可以判断任意程序是否会在输入时停止的方法。

于 2021-06-14T21:09:49.480 回答
0

这是一个快速、相对简单的证明:

假设您的朋友告诉您他们已经完成了:他们有一个程序(称为Halts(X)),它查看某个函数X,并告诉您它是否会停止(而不是永远运行)。他们说这绝对适用于任何人X,无论多么疯狂!要查看它们是否正确,您可以使用以下示例函数:

function P() {
    while (Halts(P)) { /* loop */ }
} 

如果Halts(P)为真,则P永远循环。但如果Halts(P)为假,则P停止。这是一个矛盾!不幸的是,你的朋友一定是弄错了——至少有一个X他们的程序做出了错误的预测。而且我们甚至没有看他们的代码——所以任何告诉你他们已经完成的人总是会因为同样的原因而犯错。

这并不是说他们无法接近,因为大多数常见的程序都更容易 - 他们只需要在更困难的情况下说“不知道”。有各种正在进行的研究来解决更常见的情况,并确保您首先避免编写这些棘手的程序。然而,为那些太棘手的程序提出有用的限制......远非简单。

资料来源:我记得最初在其他地方看到过这个证明,但这与此处维基百科文章中克里斯托弗·斯特拉奇 (Christopher Strachey) 的证明基本相同,并且类似于上面 ahawker 的回答。

于 2021-11-09T10:36:57.633 回答
-1

假设您编写了一个算法,可以检查任意一段代码并判断它是否停止。

现在给你的算法本身检查。

于 2009-07-10T18:27:29.237 回答
-1

您可能会发现,考虑一个不修剪自己草坪的人的故事,并问自己他是否修剪自己的草坪,这可能会有所帮助。

于 2009-07-10T18:40:11.053 回答
-1

学习这个程序:

for (x= 1; ; x++)
  for (y= 1; y <= x; y++)
    z= pow(x * x * x + y * y * y, 1./3.)
    if (z == int(z))
      stop;

它会停止吗?编译器可以预测它是否会停止吗?

[请注意,此示例不能证明停止问题的不可能性]

于 2021-06-15T16:24:30.560 回答