算法设计思想----归纳法
1、问题描述:
社会名流是指在n个人中,一个被所有人知道但是不知道其他人的人。若用有向图描述此问题,可以表示为:若A认识B,则从A到B有一条边。问题需要从n个人中,找到社会名流。
(在图中,由于C不认识其他人,而其他人都认识C,所以C是社会名流)
同样可以推断出,在一个图中社会名流至多有一人。若多于一人,假设A与B都是社会名流,由于A是社会名流,所以B认识A,A不认识B,同理B不认识A,A认识B。矛盾。所以在一个图中,社会名流的个数为0或者1个。
2、解答:
算法1:
n个人(假定为1...n)。
从第一个人开始,将第一个人<1>作为候选人,询问其他n-1个人是否都认识<1>,并且<1>不认识其他人。(此步骤询问2(n-1)个问题)
重复第二步,直到找到社会名流或所有人不是社会名流,算法结束。
算法的复杂度很容易计算,共需要2(n-1) + 2(n-1) + 2(n-1) ..... = 2(n-1) X n = 2n(n-1)个问题,复杂度为O(n2)。
算法2:
思想:
使用归纳法来处理此问题,从算法1可以看出,问题主要是逐个排除候选人。当问题规模为N时,有N个候选人。所以我们将候选人的个数定义为归纳法的变量。
我们假设问题的规模为n-1时,问题可解。基础情况为n = 1时,最多只需询问其余的n-1一个人,共2(n-1)个问题便可确定问题的解。
问题的关键为如何将问题的规模n变为n-1。
我们考虑任意两个人A和B,若A到B有边,则A一定不是社会名流。相反,若A到B没边,则B一定不是社会名流。由此可知,对任意两个人,只需判断A到B有没有边,便可排除一个候选人。这样,我们将问题由N变为N-1。
实现:
将算法分为两个阶段,第一个阶段将问题的规模变为1.第二个阶段判断剩余的一个人是否是社会名流。
代码:
public int findKnown(boolean[][] relation){
//relation代表了n个人的关系,为一个NxN的方阵
//relation[i][j] 代表从i到j是否有边,有边为true,无边为false
int length = relation.length;
int i;
//第一阶段
int can; // 候选人
Stack<Integer> candidate = new Stack<Integer>();
for(i = 0; i < length ; i++){
candidate.add(i);
}
can = candidate.pop();
while(candidate.size() > 0){
int temp = candidate.pop();
if(relation[can][temp]){
//从can到temp有边,删除can
can = temp;
}
//无边can保持原值
}
//得到can,进入检测阶段
for(i = 0 ; i < length ; i++){
if(i == can)
continue;
if(relation[i][can] == true && relation[can][i] == false){
continue;
}else{
return -1;
}
}
return can;
}
总结:不要总是从n-1扩展到n,有时应尝试将问题的规模降低,由n至n-1。