在看《信息检索导论》的时候看到了这个算法的实现,书里是用来演示如何将两个term的倒排列表求交集。伪代码如下:
INTERSECT( p1, p2 )
1 answer ← {}
2 while p1 != NIL and p2 != NIL do
3 if docID( p1) = docID( p2 ) then
4 ADD( answer, docI D( p1 ) )
5 p1 ← next( p1 )
6 p2 ← next( p2 )
7 else if docID( p1 ) < docID( p2 ) then
8 p1 ← next( p1 )
9 else p2 ← next( p2 )
10 return answer
乍一看这段代码和归并排序的归并过程比较像,但是这个是只取两个数组中共同的元素。这里数组假设按从小到大排序,且没有重复。实际上这里数组也可以是链表,本文以数组为例,链表也是一样的。
其正确性大致看感觉应该没问题,但是想严格地说明其正确性似乎就感觉有些挠头发,没有一个特别直观的能一句话说明其正确性的方法。想了一会儿,还是得做一些定义。
首先给出第一个定义,也是一个循环不变式:
在循环的每次迭代开始的时候,数组A的当前元素A[i]和数组B的当前元素B[j]满足一下条件:
A[i] > B[1] ... B[j-1]
B[j] > A[1] ... A[i-1]
为了让算法在初始的时候满足这个条件,可以为每个数组的前面加上正无穷这个元素。这个循环不变式的证明可以使用数学归纳法,具体过程省略。
接下来为本算法所要完成的目标做一个进一步的说明:
本算法所要做的,对于每一个数组中的某个元素k来说,实际上是要找到对应的另一个有序数组中大于等于k的最小的元素。由于数组都有序,因此实际上就是另一个数组中,从第一个元素遍历,大于等于k的第一个元素。
当找到了这个另一个数组中大于等于k的第一个元素,实际上也就知道了另一个数组是否有等于k的元素。如找到的这个元素大于k,则可以确知另外的数组中没有相等的元素(可以利用数组有序的性质很好证明),因此可以跳过k这个元素。而如果相等,则是找到,可以从两个数组中分别跳到下一个元素,继续查找。
基于上面的思想,就不难理解算法的主干了。只是这里元素k不定,由于两个数组之间完全是对称的,其关系是相互的,因此每次跳过的k是两个数组的当前元素中较小的那个。因为较小的那个可以认为是找到了另一个数组中大于等于自己的最小的元素(也就是另一个数组中的当前元素,可以自行证明)。而较大的则不能这么说,因为另一个数组中后面还有可能有元素比它小。
嗯,这样算法的正确性基本上能说明白了。
(以上的叙述为了方便说得比较简单,还请理解)