传球问题
传球问题
问题描述
A,B,C,D,E 五个人互相传球,由 A 开始第一次传球,经 5 次传球后传回到 A 的手上,其中 A 与 B 不会相互传球,C 只会传给 D,E 不会传给 C,共有多少种传法?
分析问题
按照问题的描述可以画出如下的有向图:
传球的模式可以简化为:
A -> □ -> □ -> □ -> □ -> A
实际上可以把它看成一个路径问题,从 A 出发,最终又回到 A。
根据限定条件,尝试不同的路径,经过 5 次传球后,如果能够到达 A,就说明这次尝试的路径是正确的。
可以尝试递归加上回溯解决这个问题。(似乎和找迷宫出路是一样的)
编写代码
使用 ECMAScript 6 编写, node v8.9.4 编译运行。
首先设计一个 Person 类,用来构造 A,B,C,D,E 五个对象。他们内部有一个数组 ableToNext
,用来存放可以传球的对象。
class Person {
constructor(name) {
this.name = name;
this.ableToNext = new Array();
}
}
此外有个 persons 对象,存放 5 个人和当前拥有球的人。然后根据当前拥有球的人中存有的 ableToNext
数据,将球传给下一个人,递归地找出所有的可能情况。用一个数组 ballStack
作为栈,用来保存传球的情况。因此栈的大小就是传球的次数。
递归函数 passBall 为:
function passBall(person) {
person.ableToNext.forEach( next => {
// /*
if( ballStack.length < (MaxBallPassCount - 1) && next == persons.finalOwner ) {
// finalOwner doesn't get the ball at the meantime
return;
}
// */
attmptCount++;
ballStack.push( next.name );
persons.ballOwner = next;
// log( ballStack.length +": "+ next+ " " );
passBall(next);
});
// all possiblility have been attempted, get back to last one
ballStack.pop();
}
递归终止的条件是传球 5 次。如果传球的次数达到了 5 次,那么当前拥有球的人就不必继续传球了。
// end condition of recursive
if( ballStack.length == MaxBallPassCount ) {
if( persons.ballOwner == persons.finalOwner ) {
validCount++;
console.log( validCount, ballStack );
}
ballStack.pop();
return;
}
在这里会把所有有效的情况打印出来。不过对于这个问题,由于在 5 次传球的过程中,A 是可以在中间出现的,所以递归函数应该是这样的:
function passBall(person) {
...
person.ableToNext.forEach( next => {
attmptCount++;
ballStack.push( next.name );
persons.ballOwner = next;
// log( ballStack.length +": "+ next+ " " );
passBall(next);
});
...
}
这样得到的有效情况是 30 种:
如果说在前 4 次传球过程中,球不能传到 A,那么有效情况是 18 种:
总结
这个问题比较简单,核心内容就是递归函数,最后只要根据限定的次数结束递归就可以得到所有的情况了。
还有 一种思路 是找出所有可能的情况,再根据限制条件进行过滤。
详细的代码在 Github
本博客由 BriFuture 原创,并在个人博客(WordPress构建) BriFuture's Blog 上发布。欢迎访问。
欢迎遵照 CC-BY-NC-SA 协议规定转载,请在正文中标注并保留本人信息。