USING INDUCTION TO DESIGN 使用归纳法设计算法 [4/14]

接上文:http://blog.csdn.net/jj12345jj198999/article/details/6604842

 

但是如果1被排除了,那么2也会因为同样的原因被删去。但是,这样得到的结果集不是最大的,因为我们很容易就能看到可以只删去2即可。这个问题其实是让我们去找到一种通用的方法来决定集合中包含哪些元素而已。

使用归纳法求解的思想集中在缩减问题的规模上,这种方法使用起来也很灵活。我们可以通过寻找一个要么属于S中的元素或要么不属于S中的元素来缩减问题规模。我们将在后面进行这一操作。我们先使用简洁明了的归纳假设:

归纳假设:我们已经知道如何去解决一个含n-1个元素集合的问题。

最基本的情况很简单:如果集合中只含有一个元素,集合必须映射到自身,这就是一个一一映射。假设我们已经有了一个含有n个元素的集合A,现在我们寻找一个满足问题条件的子集S。很显然找到一个不属于S集合的元素要比找到一个包含在S集合中元素简单。我们可以认为任何一个元素i如果没有被其他元素映射,那么i不可能包含在S中。(换句话说,在图右侧的元素i,如果没有一条边与之相连,那么i不可能在S中)否则的话,如果i∈S,假设S中有k个元素,那么这k个元素最多映射到k-1个元素,因此这个映射不可能时一一映射。如果存在这样的i,我们仅需要把i从集合中移除即可。我们现在有一个含有n-1个元素且让函数f映射到自身的集合A'=A-{i}。通过归纳假设我们知道如何去求解A'。如果不存在这样的i,那么映射就是一一映射,这样我们就解决了问题。

这种解法的关键在于我们必须移除i。我们在上面证明了i不可能属于S集。这是归纳法的优势之处。一旦我们移除了一个元素缩减了问题的规模我们就算完成了。然而我们必须格外小心,缩减后的问题和原问题几乎是一样的。(除了规模外)对于集合A和函数f的唯一条件就是f让A映射到自身。由于并没有元素映射到i,这个条件对于A-{i}后的集合依然成立。当没有元素可以移除的时候这个算法也就执行结束了。

实现:上面的算法是通过使用递归的过程加以描述的。在每一步中我们找到一个没有其他元素映射到它的元素,移除它,然后继续递归执行。然而实现方法并不需要用递归。我们可以对每一个元素i使用一个计数器c[i]。初始情况下c[i]需要和映射到i的元素数目相等。这可以通过扫描数组(进行n步)以及增加对应的计数器值而计算出来。然后我们把所有计数值为0的元素放在一个队列中。在每一步执行中,我们移除队列中的元素j(同时也移除集合中的对应值)。减小c[f(j)]的值,如果c[f(j)]=0,我们就把f(j)放在队列中。当队列为空时算法执行结束。算法描述如下:

映射算法(f:整型数组[1..n])
begin
  S:=A{A是从1到n的数字构成的数组}
  for j:=1 to n do c[j]:=0;
  for j:=1 to n do increament(c[f(j)]);
  for j:=1 to n do
    if c[j]=0 then put j in Queue;
  while Queue is not empty do
     remove i from the top of the queue;
     S:=S-{i};
  decrement c[f(i)];
  if c[f(i)]=0 then put f(i) in Queue
end;

 

 

复杂度:初始化部分需要O(n)的操作时间,每一个元素最多有一次机会被放置到队列中,把元素从队列中移除只需要常数时间。步骤的总数是O(n)。

总结:在本例中减少问题的规模主要在从集合中删除元素上。因此我们试图在不改变问题条件的情况下寻找一种最简单的方法来移除元素。由于对于函数的唯一的要求是它必须把A映射到自身,故我们会很自然地选择一个没有其他元素映射到的元素。


检查线段的包含情况【Q3】

问题:输入是一条线上一系列间断线段的集合I1,I2...In。对于每一个线段Ij都给出了它的两个端点Lj(左端点),Rj(右端点)。我们想标记出所有包含在其他线段中的线段。换句话说,一个线段Ij必须被标记出来,如果存在另一条线段Ik(k≠j)并且满足Lk≤Lj以及Rk≥Rj。为了简化起见我们假定所有的线段都是不一样的(例如任意两个线段都不可能同时具有相同的左端点和右端点,但是它们可能有两个端点中的某一个相同)。图2展示了这样的一个线段集合。(为了更好的展示,每一条线段都放在另一条线段上面而不是放在一条线上)

 

USING INDUCTION TO DESIGN 使用归纳法设计算法 [4/14]_第1张图片
                                  图2:线段集合

 

 

用直观的方法使用归纳法主要是移除一个线段I,对于剩下的线段使用递归的方法加以解决,然后在检查把I放回后的效果。问题在于把线段I放回需要检查所有其他的线段来判断他们中是否包含I或者被I所包含。这需要检查I和其余n-1条线段,算法中使用的比较将达到n-1+n-2+...+1=n(n-1)/2次。为了获得一个更好的算法我们需要做两件事:首先我们选择一条特殊的线段加以移除,其次我们尽可能多的使用从更小规模问题的解决方案中收集到的信息。

 

你可能感兴趣的:(USING INDUCTION TO DESIGN 使用归纳法设计算法 [4/14])