打靶算法分析

问题: 一个设计运动员打靶,靶一共10环,连开10环打中90环的可能性有多少?请用第归算法实现?

分析:
1)每次打靶可能的得分范围是什么?
靶有10个环,那么当打中时,分数可为1-10,如果未打中得分为0,所以每次打靶得分的范围为0-10,共有11中可能
2)计算有多少种可能最直接的方法:
打10次靶,分别记录这10次打靶过程,用循环来完成
for(int i1=0;i1<=10;i++)
{
      
for(int i2=0;i2<=10;i2++)
      {
           
for(int i3=0;i3<=10;i3++)
           {
                  
---
                   
for(int i10=0;i10<=10;i10++)
                   {
                           
if(i1+i2+i3+.+i10=90)
                           {
                                 
//一种可能
                           }
                   }
                  
---
           }
      }

但是这样做有两点不足:
1)如果题目改为连打1000枪,得分为900的可能性,估计这种写法的要哭了
2)考虑不周全,如果第一次打靶得分为0,还有9次机会,这9次机会,就要求枪枪都是满分,如果第二枪,得分不是10,那第三枪不用打就知道可能没有可能性了。就比如乒乓球比赛一样,5局3胜制,如果进行了3局都是一个人胜利的话,比赛这时候就可以宣告结束。而继续下去就是浪费时间和精力
2。采用第归的方法来解决上述问题
   第归就是自己调自己,如果没有结束限制的话,第归的效果和dead loop是一样的,但是第归正常情况下都会有结束标志,而且第归的意义就在于完成循环层数不明确或者层数明确但是数值非常大的情形。使用它的注意点就是第归函数肯定要具有一个或者一个以上的形参,没有参数的第归就形成了死循环。而且第归中函数每次调用自己的时候,需要小心谨慎的控制参数。尽量防止死循环的产生,第归和栈关系密切。
要实现上述功能,第归函数要完成的功能主要有:
 1)当传入的当前打靶次数为小于1,或者大于规定次数的时候,应该退出第归函数的执行
2)当余下的打靶次数中每次都得满分,但能无法达到目标分数的时候,应该退出第归
3)如果没有上述两种情况,就应该执行第归
实现代码:
最后结果为:92378
总结:这个问题主要考察了程序员的逻辑思考能力和对第归函数的应用。十分简单。但逻辑一定要清楚,分析问题的方法一定要准确。
作者:jillzhang
出处:http://jillzhang.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2007-02-01 14:37 Robin Zhang 阅读(3313) 评论(30)  编辑 收藏 网摘 所属分类: 算法

  回复  引用  查看    
#1楼2007-02-01 15:49 | Dflying Chen      
两点疑问:
1 为什么不直接用概率公式算呢?这样最快
2 10环和1环的打中概率不一样吧?

  回复  引用  查看    
#2楼[楼主]2007-02-01 16:04 | jillzhang      
@Dflying Chen
不是概率,是求出全部的可能性
2 10环和1环的打中概率不一样吧?
现实中的确概率不一样,如果考虑概率的问题,估计这就麻烦了,题目中没有说那环命中几率,估计应该查看的不是概率问题

  回复  引用  查看    
#3楼[楼主]2007-02-01 16:07 | jillzhang      
@Dflying Chen
另外,偷偷告诉老兄,小弟数学门门不及格,概率是抄人家的才勉强得70的。所以数学公式,我一个都没记住。唉,真应该学学高数,线代,概率,复变,模糊数学啥的了,都学过,都忘了。

  回复  引用  查看    
#4楼2007-02-01 16:16 | 韦恩卑鄙      
老弟 这用不到大学数学 只需要高中知识。。。
  回复  引用  查看    
#5楼[楼主]2007-02-01 16:29 | jillzhang      
@韦恩卑鄙
您提醒一下,用哪个数学公式能处理这个问题

  回复  引用  查看    
#6楼2007-02-01 17:02 | Adrian.H      
说是求"可能性"不妥当吧, 应该是求"得90环可能的排列种数"
  回复  引用  查看    
#7楼2007-02-01 18:06 | Cat Chen      
概率问题,就不应该如此用计算机去求解。

抛开大学的概率课程不说,懂得排列组合的就可以算,或许因此楼上说高中公式就能解决。其实公式用了并不代表你懂得严密的概率理论,只是每个人从小心中默认“等概率事件”的定义而已。如此说来,这种题小规模数量级在小学奥数题中出得不少,是不是应该说聪明的小学生都应该懂?

  回复  引用  查看    
#8楼2007-02-01 18:08 | Cat Chen      
@Adrian.H
这种可能性问题本来就是通过求排列组合获得的,因为我们假设了某些东西为等概率事件。例如再复杂组合的硬币投掷问题,我们都假设一个硬币正面出现和反面出现是等概率事件,而历史上确实有人去投了上万次硬币证明这一点。

  回复  引用  查看    
#9楼2007-02-01 19:20 | Adrian.      
@Cat Chen
但打中每个环数的概率是不一样的, 假设击中盘中任意一处的概率是相等的, 那么打中任何一环的概率为{环的面积/盘面的面积}, 很显然, 环的面积和环的得分不是成正比的.
那么, 同样是90分的情况:
例如一种是: 9 9 9 9 9 9 9 9 9 9
另一种是: 9 9 9 9 9 9 9 9 10 8
它们的概率不相等. 后面一种的理论概率更大((9^2)*2 < (10^2 + 8^2)).
So, 对于这个问题, 每种得分的每种情况不是一定等概率的.
所以我说题目使用"可能性"这个词欠妥. 排列的种数不能代表"得到90分的概率"

  回复  引用  查看    
#10楼2007-02-01 19:26 | 目标年薪三千万      
就计算机实现而言,在.NET里面我不知道怎么实现,Random类提供的是伪随机函数.
图明显画错了......
"是否打够次数"没有"是"分支,其他的错误就懒得看了,感觉很乱......

  回复  引用  查看    
#11楼[楼主]2007-02-01 19:45 | jillzhang      
修改了一下
  回复  引用  查看    
#12楼2007-02-01 20:07 | 目标年薪三千万      
程序大的逻辑基本算对的,但第42行有错误,应该为
42 if(cNum>=totalScore) //总分>需要分数
按照楼主"不可能打到就休息"的理论,提前打满了也不需要泄愤,所以没有
currentShot==totalShot的条件
另外,应该增加一个失败次数公共字段,来计算失败次数,因为总的可能性不是10000!!!
最后才能得到可能性

  回复  引用  查看    
#13楼[楼主]2007-02-01 20:14 | jillzhang      
@目标年薪三千万
您再仔细调试一下吧,程序肯定是对的。不是说if(cNum>=totalScore)的时候才认为可能情况+1,而是只要我判断最后一次我只要能满足目标分数,不管最后一次多少分,0分也可以,10分也可以。
这个判断在
//以后枪枪都中10都不能满足条件,game over
37 if(((totalShot-currentShot+1)*10)<(totalScore-cNum))
38 {
39 return;
40 }
所谓的game over不是整个第归都结束了,而是对每次尝试的结束。
仍然要做其它不同类新的尝试

  回复  引用  查看    
#14楼[楼主]2007-02-01 20:16 | jillzhang      
也就是说执行到
37 if(((totalShot-currentShot+1)*10)<(totalScore-cNum))
38 {
39 return;
40 }
没有return,并且currentShot=10的时候,那这就是一种可能了

  回复  引用  查看    
#15楼[楼主]2007-02-01 20:19 | jillzhang      
@目标年薪三千万
感觉你还是对第归认识不够清楚,第归终的return只是结束掉本次函数调用而已

  回复  引用  查看    
#16楼2007-02-01 22:41 | Cat Chen      
@Adrian.
对哦,那么这个真的是简单的排列组合问题,连概率论都不需要用到。

“使用它的注意点就是第归函数肯定要具有一个或者一个以上的形参,没有参数的第归就形成了死循环。”——其实这个不一定要是型参,全局变量或者类成员变量也可以。

事实上,所有的递归都可以用递推写,这样能够更有效防止栈溢出。

  回复  引用  查看    
#17楼2007-02-02 00:54 | 目标年薪三千万      
@jillzhang
那么,请问
1.请走一遍逻辑,为了简便,假设只要开1枪,5环算成功.

2.根据"不可能打到就不用再打"的理论,那么,如果提前达到了目标,也就不用打了,比如10次打90环,前9次就已经打到,那么第10次呢,应该不用打,也就是说,这是一次情况,而不是10次情况.

  回复  引用  查看    
#18楼[楼主]2007-02-02 08:11 | jillzhang      
1.请走一遍逻辑,为了简便,假设只要开1枪,5环算成功.
这种可能性只有一种
首先传递1,0
1)既没有打多也没有打少
2)以后都打中,会超过总分
3)当前次数为要求次数,可能性+1,第归不执行,退出
-------------------------------------------
2.你第二个问题,你说的是一种可能,但还有其它可能,最后一枪必须打中0-10环,所以,最后一枪就有11种可能

  回复  引用  查看    
#19楼[楼主]2007-02-02 08:56 | jillzhang      
其实这个问题就是简单的排列组合问题,给他的用第归算法限制住了,还好兄弟们眼光犀利,多谢大家
其实这个问题和10个苹果,分给10个人,一共有多少种分法是一模一样的

  回复  引用  查看    
#20楼2007-02-02 09:38 | 目标年薪三千万      
@jillzhang
我就不知道题目的意义了,究竟是正好打中目标环数算成功呢,还是超过目标环数也算成功?
你第一个问题和第二个问题的回答貌似本身在这个问题上矛盾了.

如果算超过的,那么第一个问题就有5,6,7,8,9,10六种情况,而不是1种!

如果不算超过的,那么第二个问题只有一种情况,而不是11种!

但你的程序中很明显是超过目标分数,还要开枪的算法.

  回复  引用  查看    
#21楼2007-02-02 09:45 | 目标年薪三千万      
如果无论如何都要开满需要的枪数,那么这道题目是和分苹果,执筛子无异,那么
总的次数=总枪数*Count(可能环数)
但楼主说的"怎么打都不够分数,就不用浪费时间打(多种情况只算一次)"的逻辑,这就完全不一样了.

  回复  引用  查看    
#22楼2007-02-02 11:31 | LIVE      
这问题可以如下:
如果有10个变量x1,x2,......,x9,x10,并且其和为90,即x1+x2+.....+x9+x10=90,每个变量是0<=x<=10。
试列出每种组合!



  回复  引用  查看    
#23楼2007-02-02 11:36 | LIVE      
既然说到算法,那就得考虑内存和运行时间,一般这样的题目在ACM上要求是运行时间为1s,内存为64M,CPU也不能太好,800Mhz吧
  回复  引用  查看    
#24楼2007-02-02 12:23 | 目标年薪三千万      
想错了,的确是筛子问题.
  回复  引用  查看    
#25楼[楼主]2007-02-02 13:17 | jillzhang      
@目标年薪三千万
你还是没明白,不是每次都必须打完的亚
比如你第一枪0分,第二枪你打0-9分,程序退出这次第一枪0分的尝试,换成第一枪1分的尝试啦,如果继续的话,就和你说的必须打够了才行,但关键是以后都得满分都不能达标的话,你就不用打了,新一轮比赛就可以了

  回复  引用  查看    
#26楼2007-02-02 14:34 | 臭石头      
呵呵,好像大家讨论远了,博主只是为了说明递归算法,而大家倒计较起来概率了。
我个人更侧重于递归函数的实现。
static void Main(string[] args)
{
int Count = 10; // 次数
int Per = 10; // 最大环数,就是最大有10环
int Total = 90; // 要求得分9
Console.WriteLine("可能性:{0}%", (Double)run(Per, Count, Total) * 100 / Math.Pow(Per, Count));
Console.ReadKey();
}


/// <summary>
/// 计算打靶ii次,得分rr的可能性,最大环数kk
/// </summary>
static int run(int kk, int ii, int rr)
{
if (ii < 1 && rr == 0) return 1;
// 参数不正确
if (ii < 1) return 0;
// 就算全部打中,都赢不了的,剔除
if (ii * kk < rr) return 0;
// 全部打中才能得到这个分数,只有一种可能
if (ii * kk == rr) return 1;

int rs = 0;
// 本次打靶有kk+1种可能
for (int i = 0; i <= kk; i++)
{
// 计算ii-1次打靶,得到分数rr-i的可能
rs += run(kk, ii - 1, rr - i);
}
return rs;
}

  回复  引用    
#27楼2007-02-14 01:49 | longmang[未注册用户]
我的程序,跟楼主的运行结果一样:
--------------------------------------------------------
#define TIMES 10//总共打10次
#include <stdio.h>
int counts=0;//打中90环的次数

void shot(int sum,int times)
{
if ((10 * (TIMES - times + 1)) >= sum)
{
if (times > 10)
return;
for (int i=0; i<=10; i++)
{
if ((sum-i) == 0)
{
counts ++;
}
else if ((sum-i) > 0)
shot(sum-i,times+1);
}
}
}
void main()
{
shot(90,1);
printf("%d\n", counts);
}

  回复  引用    
#28楼2008-05-25 21:12 | 晓凡[未注册用户]
甲四次打靶分数为:“10、7、7、6”。
乙四次打靶分数为:“9、8、7、6”。
问甲乙谁赢了???

  回复  引用    
#29楼2008-12-12 09:30 | 严重问题[未注册用户]
不知道你的currentshot在什么情况下才等于totalshot,
是不是在你的递归调用中只有可能当打够10次后,并且满足90环条件下才可能,同时此时也就会推出递归循环。
而这种情况在你的递归中出现的次数应该是只有一次。
但你的sumrate只有在这种情况下才加1, 也就是你的递归结果应该是1




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 636889




相关文章:

相关链接: