益智题的递归:)

 题目如下:

 * 十个射击运动员打靶,靶一共有10环,10人打中90环的可能性有多少种? 请用递归算法编程实现。

 

 

关于递归,其求解过程类似一个stack,先进后出。先是由上而下调用,直至寻找到出口,然后自下而上计算返回结果。

其关键点:

1.寻找到一个“出口”,就是递归表达式在这一步可以计算出一个值。

2.定义递归表达式。注意在定义表达式n的时候,下一步n-1往往被考虑成可解的结果。(类似数学的推论证明)

 

 

一。第一步 尝试

拿到这个题目分析了一下,然后写了第一版代码:

  1.     /**
  2.      * 递归计算函数
  3.      * 
  4.      * @param personId 人数,从0开始计算
  5.      * @param currentScore  当前人打的环数,从0--10 
  6.      * @param scoreHistory  环数记录
  7.      */
  8.     private static void Caculate(int personId,int currentScore,
  9.             int[] scoreHistory) {
  10.         
  11.         //出口
  12.         if0 == personId) {//.最后一个人的时候,离90还剩下0-10环即可以满足一次
  13.             if(90-sumup(scoreHistory)> =0 && 90-sumup(scoreHistory)<=10) {
  14.                 currentScore = 90-sumup(scoreHistory);
  15.                 scoreHistory[personId] = currentScore;
  16.                 printlog(scoreHistory);
  17.             }
  18.         }else { //迭代
  19.             scoreHistory[personId] = currentScore;
  20.             //计算下一个人,下个人可能打出0--10环,因此需要循环
  21.             for(int i =0; i<=10;i++) {
  22.                 Caculate(personId-1,i,scoreHistory);
  23.             }
  24.         }
  25.     }

这个递归函数的出口也找到了,表达式也找到了,应该可以求解,对吧。继续下一步,貌似可以调用函数求解咯:)。。

  1.     public static void main(String[] args) {
  2.         for(int i =0; i<=10;i++) {
  3.             Caculate(9,i,store);
  4.         }
  5.     }

做到这里才发现,原来这个递归居然没有入口,这里是犯了第一个错误。也就是调用函数的时候,以personId=9人为起点,可是这个人的currentScore是多少呢。。。居然还需要用一个for循环去尝试,效率显然让人无语。

 

二。第二步 修改

 

这时候考虑修改入口,虽然无法猜出第一个人打几环,但是可以肯定的是,总剩余环数为90。修改递归函数如下:

  1.     /**
  2.      * 递归函数
  3.      * @param personId 人数,从0开始计算
  4.      * @param remanentScore 当前剩余环数 <=90
  5.      * @param scoreHistory 环数记录
  6.      */
  7.     private static void Caculate(int personId,int remanentScore
  8.             ,int[] scoreHistory) {
  9.         if(0 == personId) { //出口,最后一个人打出剩余环数就ok
  10.             if(remanentScore>=0 && remanentScore<=10) {
  11.                 scoreHistory[personId] = remanentScore;
  12.                 printlog(scoreHistory);
  13.                 counter++;
  14.             }
  15.         }else {
  16.             for(int i=0; i<=10 ; i++) {//这个人可能打出0-10环
  17.                 scoreHistory[personId] = i;
  18.                 remanentScore -= i;
  19.                 Caculate(personId-1,remanentScore,scoreHistory);
  20.             }
  21.         }
  22.     }

这里就犯第二个错误了。。呵呵。18,19行的一个非常隐蔽的小bug,debug调了n久才发现!

错就错在将remanentScore进行了自减。通常在函数结尾处理一个不再使用的变量,没有什么影响的。但是这个递归函数进入到for循环,代表的是当前递归并未到出口,就是没有结束!变量的错误操作会影响到后面的计算。

 

解决办法就是将18,19两行改成    Caculate(personId - 1, remanentScore - i, scoreHistory); 传递正确的参数而不影响当前递归值栈

 

三。第三步 优化


第二步实现的递归,跟套10个for循环的效率应该是一样差。每种组合都需要跑一遍,判断。 而我们知道,如果前面2个人打了不到10环,后面8个人全中10也不可能达到90环。那么来优化一下递归函数。

  1.     /**
  2.      * 递归函数
  3.      * @param personId 人数,从0开始计算
  4.      * @param remanentScore 当前剩余环数 <=90
  5.      * @param scoreHistory 环数记录
  6.      */
  7.     private static void Caculate(int personId,int remanentScore
  8.             ,int[] scoreHistory) {
  9.         if(remanentScore > (personId+1)*10) {
  10.             return;
  11.         }
  12.         
  13.         if(0 == personId) { //出口,最后一个人打出剩余环数就ok
  14.             if(remanentScore>=0 && remanentScore<=10) {
  15.                 scoreHistory[personId] = remanentScore;
  16.                 printlog(scoreHistory);
  17.                 counter++;
  18.             }
  19.         }else{
  20.             for (int i = 0; i <= 10; i++) {//这个人可能打出0-10环
  21.                 scoreHistory[personId] = i;
  22.                 //              remanentScore-=i;
  23.                 //              Caculate(personId-1,remanentScore,scoreHistory);
  24.                 Caculate(personId - 1, remanentScore - i, scoreHistory);
  25.             }
  26.         }
  27.     }

在函数开始就增加分数判断,不合适的直接pass 。

 

总共的循环次数为10的十次方10000000000 ,满足条件的次数仅92378,因此这一个简单的判断是惊天地泣鬼神的!

 

在我机器上运行优化过的递归时间为8688ms,而没有优化过的需要时间35766ms,显然是数量级的提高。

 

下面是最终JAVA代码:(改日写个Python的。。。好歹也算是混了个眼熟)

  1. package datastructure;
  2. /**
  3.  * 十个射击运动员打靶,靶一共有10环,10人打中90环的可能性有多少种? 请用递归算法编程实现
  4.  * 请用递归算法编程实现
  5.  * @author wei.songw
  6.  * Sep 20, 2008 9:28:46 PM
  7.  */
  8. public class Shot {
  9.     static int counter = 0;
  10.     /**
  11.      * 打印函数,输出一次可能性的所有环数
  12.      * @param arr
  13.      */
  14.     private static void printlog(int[] arr) {
  15.         if (arr!=null && arr.length>0) {
  16.             for (int i = 0; i < arr.length; i++) {
  17.                 System.out.print(arr[i]+"  ");
  18.             }
  19.             System.out.println("\n");
  20.         }
  21.     }
  22.     
  23.     /**
  24.      * 计算一个一个数组中,所有元素的总和
  25.      * @param scores
  26.      * @return
  27.      */
  28.     private static int sumup(int[] scores) {
  29.         int sum = 0;
  30.         if(scores!=null && scores.length>0) {
  31.             for(int i=0;i<scores.length;i++) {
  32.                 sum += scores[i];
  33.             }
  34.         }
  35.         return sum;
  36.     }
  37.     
  38. //  /**
  39. //   * 递归计算函数  try-1
  40. //   * 
  41. //   * @param personId 人数,从0开始计算
  42. //   * @param currentScore  当前人打的环数,从0--10 
  43. //   * @param scoreHistory  环数记录
  44. //   */
  45. //  private static void Caculate(int personId,int currentScore,
  46. //          int[] scoreHistory) {
  47. //      
  48. //      //出口
  49. //      if( 0 == personId) {//.最后一个人的时候,离90还剩下0-10环即可以满足一次
  50. //          if(90-sumup(scoreHistory)> 0 && 90-sumup(scoreHistory)<10) {
  51. //              currentScore = 90-sumup(scoreHistory);
  52. //              scoreHistory[personId] = currentScore;
  53. //              printlog(scoreHistory);
  54. //          }
  55. //      }else { //迭代
  56. //          scoreHistory[personId] = currentScore;
  57. //          //计算下一个人,下个人可能打出0--10环,因此需要循环
  58. //          for(int i =0; i<=10;i++) {
  59. //              Caculate(personId-1,i,scoreHistory);
  60. //          }
  61. //      }
  62. //  }
  63.     
  64. //  /**
  65. //   * 递归函数 try-2
  66. //   * @param personId 人数,从0开始计算
  67. //   * @param remanentScore 当前剩余环数 <=90
  68. //   * @param scoreHistory 环数记录
  69. //   */
  70. //  private static void Caculate(int personId,int remanentScore
  71. //          ,int[] scoreHistory) {
  72. //      if(0 == personId) { //出口,最后一个人打出剩余环数就ok
  73. //          if(remanentScore>=0 && remanentScore<=10) {
  74. //              scoreHistory[personId] = remanentScore;
  75. //              printlog(scoreHistory);
  76. //              counter++;
  77. //          }
  78. //      }else {
  79. //          for(int i=0; i<=10 ; i++) {//这个人可能打出0-10环
  80. //              scoreHistory[personId] = i;
  81. //              remanentScore -= i;
  82. //              Caculate(personId-1,remanentScore,scoreHistory);
  83. //          }
  84. //      }
  85. //  }
  86.     
  87.     /**
  88.      * 递归函数  try-3 正确版
  89.      * @param personId 人数,从0开始计算
  90.      * @param remanentScore 当前剩余环数 <=90
  91.      * @param scoreHistory 环数记录
  92.      */
  93.     private static void Caculate(int personId,int remanentScore
  94.             ,int[] scoreHistory) {
  95. //      if(remanentScore > (personId+1)*10) {
  96. //          return;
  97. //      }
  98.         
  99.         if(0 == personId) { //出口,最后一个人打出剩余环数就ok
  100.             if(remanentScore>=0 && remanentScore<=10) {
  101.                 scoreHistory[personId] = remanentScore;
  102.                 printlog(scoreHistory);
  103.                 counter++;
  104.             }
  105.         }else{
  106.             for (int i = 0; i <= 10; i++) {//这个人可能打出0-10环
  107.                 scoreHistory[personId] = i;
  108.                 //              remanentScore-=i;
  109.                 //              Caculate(personId-1,remanentScore,scoreHistory);
  110.                 Caculate(personId - 1, remanentScore - i, scoreHistory);
  111.             }
  112.         }
  113.     }
  114.     
  115.     
  116.     public static void main(String[] args) {
  117.         int[] store =  new int[10];
  118.         long start = System.currentTimeMillis();
  119.         Caculate(9,90,store);
  120.         System.out.println(counter);
  121.         long end = System.currentTimeMillis();
  122.         
  123.         long cost = end-start;
  124.         System.out.println("用时:ms "+ cost);
  125.     }
  126. }

 

你可能感兴趣的:(编程,算法,python)