`
sw1982
  • 浏览: 511480 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

益智题的递归:)

阅读更多

 题目如下:

 * 十个射击运动员打靶,靶一共有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. }

 
分享到:
评论

相关推荐

    递归:如何用三行代码找到“最终推荐人”?.pdf

    ### 递归:如何用三行代码找到“最终推荐人”? #### 一、递归的概念及应用场景 递归是一种非常强大的算法和技术,在计算机科学领域有着广泛的应用。它不仅仅是一种解决问题的方法,更是一种思考问题的方式。当...

    11 递归:如何利用递归求解汉诺塔问题?.mp4

    11 递归:如何利用递归求解汉诺塔问题?.mp4

    Labview实现递归:斐波那契数列

    在数学上它以递归的方式进行定义,指这样的一个数列:0、1、1、2、3、5、8、13、21、34、55、89、144……,即前两个数为分别为0和1,从第3项开始,每项的值都等于其前两项之和。斐波那契数列Fib(n)用公式表示为: ...

    非递归:矩阵中的路径(回溯法)

    Java实现矩阵中的路径(回溯法),采用非递归的回溯法,使用栈模拟递归过程。代码注释详细,可正常运行

    10递归:如何用三行代码找到“最终推荐人”?.pdf

    递归是一种非常重要的编程技巧,它允许函数调用自身,直到满足一个基本情况为止。理解递归对于掌握数据结构和算法至关重要,因为很多复杂问题都可以通过递归的方式简化解决。递归通常分为两个部分:递(decreasing)...

    一些常见的递归示例: 计算阶乘 斐波那契数列 递归遍历树结构

    递归基准条件:这是递归停止的条件,用于防止无限递归。 递归步骤:函数调用自身来解决更小的子问题。 注意事项 递归深度:递归深度过大可能会导致栈溢出。在编写递归程序时,应当确保基准条件能够有效地终止递归。 ...

    c语言 二叉树应用:创建、递归非递归遍历、计算结点、分支、交换子树

    递归先序遍历二叉树: 递归中序遍历二叉树: 递归后序遍历二叉树: 非递归先序遍历二叉树: 非递归中序遍历二叉树: 非递归后序遍历二叉树: 非递归中序遍历二叉树(算法2): 层次遍历二叉树: 递归计算单...

    爱的递归:WIP

    "爱的递归:WIP"可能是一个项目或教程,旨在通过递归技术来探讨和展示编程中的创意应用。让我们深入了解一下递归及其在JavaScript中的应用。 递归是一种算法,它通过调用自身来解决问题或执行任务。这个过程可以...

    递归:python递归

    在编程领域,递归是一种强大的技术,它涉及函数或过程在执行过程中调用自身来解决问题。在Python中,递归尤为常见,因为它提供了一种简洁而优雅的方式来解决复杂的问题。本篇文章将深入探讨Python中的递归,包括其...

    递归:所有类型的递归和递归示例

    在编程领域,递归是一种强大的概念,它涉及函数或过程调用自身来解决问题。在C语言中,递归被广泛用于解决复杂问题,因为它能够简化代码结构并使其更易于理解。本文将深入探讨C语言中的各种递归类型,并提供一些递归...

    汉诺塔问题的非递归算法

    非递归算法解决了这个问题,提供了一个不同于传统递归解决方案的方法。对于n个圆盘的情况,非递归算法的核心在于通过交替执行两个步骤来移动圆盘: 1. **步骤1**:根据圆盘数量的奇偶性,按顺时针方向将圆盘1从当前...

    递归:使用递归解决问题

    递归是一种在编程中解决问题的重要方法,特别是在Python这样的高级语言中。它涉及到函数或过程调用自身,通常用于处理分治、树形结构、回溯等复杂问题。递归的核心概念在于它解决问题的方式是自相似的,即问题的解决...

    剑指offer(java版67题)

    面试题 1:二维数组中的查找(考点: 数组) 1 面试题 2:替换空格(考点: 字符串) 2 面试题 3:从尾到头打印链表(考点: 链表) 2 面试题 4:重建二叉树(考点: 树) 4 ...面试题 10:矩形覆盖(考点: 递归和循环) 8

    递归实现:汉诺塔问题

    ### 递归实现:汉诺塔问题 #### 经典问题背景 汉诺塔(Hanoi Tower)问题是一个经典的递归问题,源自一个古老的故事。传说在印度的一个神殿里,有三根金刚石柱子,第一根柱子上自上而下按大小顺序叠着64个金盘。...

    函数的递归调用与分治策略

    函数的递归调用与分治策略

    数据结构二叉树遍历递归,非递归

    在本主题中,我们将深入探讨二叉树的三种主要遍历方法:中序遍历、前序遍历和后序遍历,以及如何通过递归和非递归的方式实现这些遍历。 首先,让我们理解递归遍历的概念。递归是一种解决问题的方法,它将问题分解为...

    05_JavaSE面试题:递归与迭代.avi

    05_JavaSE面试题:递归与迭代

    扫雷递归:Nur Besser和Natürlicher的Web矿山机器人

    扫雷递归:Nur Besser和Natürlicher的Web矿山机器人

    二叉树的操作--递归非递归遍历、结点个数、树深度

    遍历递归的先中後序, 非递归的先中後序, 计算出深度 结点数 /* 运行结果: ------------------------ 请先序输入二叉树(如:ab三个空格表示a为根节点,b为左子树的二叉树) ab c 先序递归遍历二叉树: a b c 先序...

    递归习题.PDF

    递归是一种重要的编程思想,它的本质是将大问题分解为小问题,然后通过小问题的解决方法来解决大问题。在理解递归之前,我们首先要明白递归程序的结构。一个递归程序通常包含三个主要部分:递归的出口、分解子问题的...

Global site tag (gtag.js) - Google Analytics