证明搜索
上一节我们已经学习了合一,本节我们继续学习Prolog是如何通过搜索知识库去决定输入的查询是否能够满足。我们将会学习证明搜索,并通过简单的一个例子去涵盖这个基础的概念。
假设我们有如下的知识库:
f(a). f(b). g(a). g(b). h(b). k(X) :- f(X), g(X), h(X).
如果我们查询:
?- k(Y).
这个查询的结果十分明显,即k(b),但是Prolog是如何将其求解出的了?让我们继续看。
Prolog读入整个知识库,然后尝试将查询k(Y)和知识库中的事实或者规则头部进行合一。搜索知识库是自上而下的,并在第一个可能合一的位置,找到对应的信息。这个例子只是有一种
可能:k(Y)和规则,k(X) :- f(X), g(X), h(X) 的头部合一。
当Prolog将查询中的变量和事实或者规则中的变量合一时,会生成一个新的变量(比如_G34)去代表共享值,所以原始的查询转换为:
k(_G34)
并且Prolog知识库中的规则转换为:
k(_G34) :- f(_G34), g(_G34), h(_G34).
现在是什么情况了?查询说:“我想要找到符合属性k的人”。规则说:“如果要找到符合属性k的人,那么这个人也必须符合属性f,g和h”。所以如果Prolog找到符合属性k,g和h的信
息,那么就能满足原始的查询。所以Prolog使用下面的目标去替代原始查询:
f(_G34), g(_G34), h(_G34).
如果使用图形演示,会使得这个过程更加形象,如下:
在盒子中的都是查询或者目标。具体地讲,我们的原始目标是证明k(Y),所以它出现在最顶层的盒子中。当我们将k(Y)和数据库中的规则头部合一时,_G34作为分享值的新中间变量,
被赋值给X,Y,所以我们有了新的目标:f(_G34), g(_G34), h(_G34),出现在第二个盒子中。
现在,无论是否存在一系列的子目标需要证明,Prolog都会通过自左向右的顺序,尝试一个一个的去满足。在最左侧的目标是f(_G34),即“想找到一个满足属性f的信息”。Prolog尝
试搜索自上而下搜索知识库。第一个找到能够和查询合一的是事实,f(a)。这会满足目标f(_G34),并且剩余另外两个目标。现在,当我们将f(_G34)和f(a)合一,_G34会被初始化为a,
而且这个初始化会将目标列表中所有的_G34都替换为a,所以剩余的目标列表看上去是这样的:
g(a), h(a)
当前的证明搜索图形如下:
但是g(a)是知识库中的事实,所以剩余目标列表中的第一个目标已经满足了,所以我们还剩下:
h(a)
证明搜索的图形如下:
但是无法再证明h(a),最后一个目标。因为关于属性h,我们通过知识库能够知道的唯一事实是,h(b),它无法和目标h(a)合一。
所以,接下来会发生什么?Prolog会承认犯了错误,并且检查是否在对目标进行合一时,有其他可能的值。通过上面图形展示的路径,Prolog会回到可以有合一替代的节点。
现在,在知识库中没有其他可以和g(a)合一的选择了,但是存在其他与f(_G34)合一的方式。存在多种合一可能的节点被称为选择节点。Prolog会保持对选择节点的追踪,所以
当它发现一种选择是错误的时候,能够回到选择节点并且尝试其他的路径。这个过程被称为回溯,它是Prolog证明搜索的基础。
那么继续我们的例子,Prolog回溯到选择节点。这个节点在上面图形中是下面的目标列表:
f(_G34), g(_G34), h(_G34).
Prolog必须重新开始工作,他必须尝试重新满足第一个目标。在知识库中,通过将事实f(b)和f(_G34)合一,可以满足目标。这个合一会将_G34初始化为b,所以剩余的目标列
表是:
g(b), h(b).
g(b)也是知识库中事实,也能够被满足,所以剩余:
h(b).
而且,这个也是知识库中的事实,所以这个目标也满足了。现在Prolog的目标列表已经为空。这意味着原始查询所需要的每一个目的都满足了,所以原始查询是能够成功的,并且,
Prolog也发现了为了达成目标而并且进行的初始化,即将变量Y初始化为b。
考虑当我们输入“;”查询是否有其他解决方案也很有趣:
这会强制Prolog进行回溯,去搜索是否有其他可能。但是上面的例子没有其他解决方案了,因为知识库中没有其他可能再去将h(b),g(b),f(_G34)或者k(Y)进行合一,所以Prolog
会回答false。另一方面,如果存在另外包含了k的规则,Prolog会按照我们之前描述的方式继续尝试,即在知识库中自上而下的搜索,从左自右地满足目标列表,如果有失败就回溯到
选择节点。
让我们看看整体的搜索过程,如下图:
这个图形是树的形式,事实上,这是我们第一个关于搜索树的例子。这种树的非叶子节点是为了满足证明搜索不同步骤的当前目标列表,树的边保存了为了满足当前目标而对知识库
中的事实或者规则进行合一的变量初始化信息(注意,当前目标是指目标列表的第一个即最左边的目标),叶子如果还包含没有被满足的目标,那么就是Prolog失败的点(错误的路径,
没有解决方案存在);叶子如果不包含任何的目标,就是一种可能的解决方案。从根节点到成功的叶子节点的路径,会给出为了满足原始目标而必须进行的变量初始化的值的全部信息。
接下来我们看另外一个例子,假设我们有如下的知识库:
loves(vincent, mia). loves(marcellus, mia). jealous(A, B) :- loves(A, C), loves(B, C).
如果我们查询:
?- jealous(X, Y).
对应的搜索树如下:
在知识库中只有一种针对jealous(X, Y)的合一方式,即使用如下的规则:
jealous(A, B) :- loves(A, C), loves(B, C).
所以我们的目标列表变成了如下:
loves(_G5, _G6), loves(_G7, _G6).
现在,我们需要在知识库中针对loves(_G5, _G6)进行合一。有两种方式可以做到(事实1和事实2),所以这也是一个选择节点。这两种情况,都是导致目标列表剩余loves(_G7, mia),
这个目标也可以通过知识库中的两个事实满足。最后所有的叶子节点都没有不能满足的目标,所以意味着一种有四种满足原始查询的解决方案。通过路径上的变量初始化信息,可以得出
如下的四种解决方案:
1. X = _G5 = vincent; Y = _G7 = vincent
2. X = _G5 = vincent; Y = _G7 = marcellus
3. X = _G5 = marcellus; Y = _G7 = marcellus
4, X = _G5 = marcellus; Y = _G7 = vincent
请仔细分析上面的例子,并确保能够完全理解。