关于经典打靶问题的研究

这个问题是:一共10环的靶,打10枪中90环,一共有多少种打法。

最直观能想到的就是穷举:

int count = 0;
            
for (int i1 = 0; i1 <= 10; i1++)
                
for (int i2 = 0; i2 <= 10; i2++)
                    
for (int i3 = 0; i3 <= 10; i3++)
                        
for (int i4 = 0; i4 <= 10; i4++)
                            
for (int i5 = 0; i5 <= 10; i5++)
                                
for (int i6 = 0; i6 <= 10; i6++)
                                    
for (int i7 = 0; i7 <= 10; i7++)
                                        
for (int i8 = 0; i8 <= 10; i8++)
                                            
for (int i9 = 0; i9 <= 10; i9++)
                                                
for (int i10 = 0; i10 <= 10; i10++)
                                                    
if (i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 == 90)
                                                    {
                                                        Console.WriteLine(
"{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}", i1, i2, i3, i4, i5, i6, i7, i8, i9, i10);
                                                        count
++;
                                                    }
            Console.WriteLine(count); 

但这是很慢的一种做法。

其实有很多组合,通过直接计算就能提前判断出能不能中90环,比如头几枪环数很低,后面就算每枪十环也到不了90环,那后面几枪都不用打了;或者10枪都中10环。出现这些情况之后。

要把这些判断加到每一枪(也就是上面代码的每一层循环),明显重复代码太多。所以可以用递归,把每一层循环提取出来。

 

这是递归版本的代码 (参考了网上一些现成的代码):

class Program
    {
        
static void Main(string[] args)
        {
            elements 
= new List<int>() { 012345678910 }; //定义每一枪可能中的环数。
            combinations = new int[10]; //定义一共要打几枪。这个数组会保存每一种打中90环的方法中每一枪的环数。

            count 
= 0;

            finalResult 
= 90//定义最终需要的环数。如果这里是99,那实际上只有一种组合。
            
            Compute(combinations.Length, 
0, combinations);

            Console.WriteLine(count);
            Console.WriteLine(actualCount);
        }

        
static int finalResult;

        
static int count;
        
static int actualCount = 0;
        
static List<int> elements;
        
static int[] combinations;

        
static void Compute(int step, int total, int[] datas)
        {
            count
++;

            
if (total > finalResult || total + elements[elements.Count - 1* (step) < finalResult)
            {
                
return;
            }

            
if (step == 0)
            {
                
if (total == finalResult)
                {
                    
string result = "";
                    
for (int i = 0; i < datas.Length; i++)
                    {
                        result 
+= datas[i].ToString() + "  ";
                    }
                    Console.WriteLine(count.ToString() 
+ "   " + result);
                    actualCount
++;
                }
                
return;
            }

            
for (int i = 0; i < elements.Count; i++)
            {
                datas[step 
- 1= elements[i];
                Compute(step 
- 1, total + elements[i], datas);
            }
        }

   }  

 用这种方法,只需要判断1847561次,比起第一种方法要判断10000000000次,确实性能提高不少。

 

根据一般研究算法的套路:

第一步,写出一个直观算法,代码很直观,但算法复杂度超级高;

第二步,写出一个优化过的算法(可能通过一些数学或逻辑技巧等优化),性能达到以可以接受的范围,但相应的代码可读性降低不少;

第三步,通过数学技巧(如数学归纳法)对算法进行建模,得出公式,最终得出时间复杂度为1的算法。

相信对于这个打靶问题,也是存在一个公式的,不过我的数学不好,不知道有没有牛人能提供一个解决方案。

 

 

posted on 2011-03-25 19:08  林大虾  阅读(630)  评论(3编辑  收藏  举报

导航