算法之递归

什么是递归?

百度百科:程序调用自身的编程技巧称为递归( recursion)。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

一个比喻:知乎上的某位同行对递归觉得最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思(来源——知乎李鹏

几个小案例 原地址

阶乘的运算

描述: n! = n*(n-1)*...2*1

function test(n){
    if(n <= 1){
        return 1;
    }else{
        return n*test(n-1);
    }
}
console.log(test(5));  //120

//下面使用arguments.callee来代替被递归的函数,
//目的是为了防止函数名的紧密耦合。同时简化了代码
function test(n){
    if(n <= 1) return 1;
    return n*arguments.callee(n-1);
}
console.log(test(5)); //120

斐波那契数列

描述:斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……求第n个数是多少

function test(n){
    if(n <= 0) return 0;
    if(n <= 1) return 1;
    return arguments.callee(n-1) + arguments.callee(n-2);
}
console.log(test(6)); //8

走楼梯问题

描述:楼梯有n阶台阶,上楼可以一步上1阶,也可以一步上2阶或者3阶,计算共有多少种不同的走法。

function test(n){
    if(n <= 0) return 0;
    if(n == 1) return 1;
    if(n == 2) return 2;
    if(n == 3) return 4;
    return arguments.callee(n-1) + arguments.callee(n-2)+arguments.callee(n-3);
}
console.log(test(6)); //24

思路:这其实就是一个斐波那契数列的一种实现。我们分析的时候,可以转化成小规模的子类问题。当到达指定阶梯的最后一步的时候,可以有三种种情况,一是上一步,二是上两步,三是上三步。所以总的方法是F(n) = F(n-1) + F(n-2) + F(n-3)。然后自然就成了各自的小计算,不断循环下去,直到判断条件的发生。

最大公约数

描述:给两个数,如果两个数相等,最大公约数是其本身。如果不等,取两个数相减的绝对值和两个数中最小的数比较,相等则为最大公约数,不等则继续上面的算法,直到相等。

function test(a,b){
    if(a == b) return a;
    return arguments.callee(Math.abs(a-b),Math.min(a,b));
}
console.log(test(15,12)); //3

汉诺塔

描述:百度百科

function test(n,src,aux,dest){
    if(n > 0){
        test(n-1,src,dest,aux);
        console.log("移动第"+n+"个圆盘从"+src+"到"+dest);
        test(n-1,aux,src,dest);
        }
}
console.log(test(3,"A","B","C")); 

思路:
在我没有体会到递归的精粹前,我对这个问题简直百思不得其解。我一直问自己,我怎么知道下一个该去哪里?后来,我就知道,我其实更关心的是,最后那一个该怎么走。这个怎么说呢?我们可以从头想起,我们如果只有1个盘,我们可以让它到C柱,也可以到B柱。自然两个盘,也可以实现。3个盘,也是可以的吧。那我们就讲4个盘的情况。4个盘要完成就要将A上的圆盘,完全转移到C上。我们把前3个盘当作一个整体放到B上,然后第4个盘就可以到C上了,然后我们再将前三个放到C上,就成功了。那前三个盘,又可以重新当作一个新游戏,让前两个盘,当一个整体,依次类推。这样我们只需要关心大的整体的事,其它的就转成小规模问题解决就好了。

二分法快排

描述:使用二分法,对一个数组进行由小到大的排序。参考博客

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <script>
      function quickSort(arr) {
        if(arr.length <= 1) return arr;
        var leftArr = [];
        var rightArr = [];
        var pivot = Math.random(arr.length / 2);
        var baseNum = arr.splice(pivot, 1);

        arr.forEach(function(num) {
          if(num < baseNum) {
            leftArr.push(num);
          } else {
            rightArr.push(num);
          }
        });
        return quickSort(leftArr).concat(baseNum, quickSort(rightArr));
      }
      console.log(quickSort([1, 4, 5, 6, 2, 7, 13, 8, 9]));
    </script>
  </body>
</html>

DOM树的递归

描述:获取一个节点的所有父节点的tagName

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title></title>
  </head>
  <body>
    <div id="box">
      <div id="content">
        <div id="last"></div>
      </div>
    </div>

    <script>
      var arr = [];
      var getParent = function(node) {
        node = node.parentNode;
        if(node.tagName) {
          arr.push(node.tagName);
          getParent(node);
        }
      }
      getParent(document.getElementById("last"));
      console.log(arr); //["DIV", "DIV", "BODY", "HTML"]
    </script>
  </body>
</html>

看了以上这么多算法题,不知道各位老铁掌握了多少。如果还是晕晕的,没关系,接下来还有大量的习题加强理解。

1、一个递归算法必须包括()?

终止条件和递归部分

2、任何一个递归过程是否都可以转换成非递归过程?

正确
详细讨论地址:https://www.zhihu.com/question/20418254

3、给定下列程序,那么执行的结果为多少?

function foo(x,y){
   if(x <= 0||y <= 0){
          return 1;
   }else{
          return 3*foo(x-6,y/2);
   }
}
console.log(foo(20,13));

解析:
foo(20, 13) = 3 * foo(14, 6) = 3 * 3 * foo(8, 3) = 3 * 3 * 3 * foo(2, 1) = 3 * 3 * 3 * 3 * foo(-4, 0) =3 * 3 * 3 * 3 * 1 = 81

4、给定下列程序,那么执行的结果为多少?

function f(x){
     if(x <= 2){
          return 1;
      }else{
          return f(x-2)+f(x-4)+1;
     }
}
console.log(f(10));

针对这样的题目在别的地方看到的比较好的方法是用树来表示
                                     10
                             8                  6
                       6         4        4       2
                   4     2    2  0    2   0
                2   0
图中树的节点数是15,所以是调用了15次

5、只有那种使用了局部变量的递归过程在转换成非递归过程时才必须使用栈?

错误
解析:递归工作栈里面包括返回地址、本层的局部变量和递归调用的形参代换用实参,所以正常情况下,无论递归过程有没有使用局部变量,转换为非递归过程都需要用栈来模拟这个递归调用过程

6、有一段楼梯台阶有15级台阶,以小明的脚力一步最多只能跨3级,请问小明登上这段楼梯有多少种不同的走法?

解析:请查看案例“走楼梯问题”

7、4个圆盘的Hanoi塔,总的移动次数为多少?

设f(n)为n个圆盘的hanoi塔总的移动次数,其递推方程为f(n)=f(n-1)+1+ f(n-1)=2*f(n-1)+1。理解就是先把上面n-1个圆盘移到第二个柱子上(共f(n-1)步),再把最后一个圆盘移到第三个柱子(共1步),再把第二柱子上的圆盘移动到第三个柱子上(共f(n-1)步)。
而f(1)=1;于是f(2)=3,f(3)=7,f(4)=15。故答案为C。
进一步,根据递推方程其实可以得出f(n) = 2^n - 1。

8、下列函数的执行结果为多少?

function f(i){
     if(i>1){
          return i*f(i-1);
    }else{
          return 1;
    }
}
console.log(f(5));

这里展开如下:
f(5) = 5*f(4)
      = 5*4*f(3)
      = 5*4*3*f(2)
      = 5*4*3*2*f(1)
      = 5*4*3*2*1
      = 120

9、递归次数与各元素的初始排列有关。如果每一次划分后分区比较平衡,则递归次数少;如果划分后分区不平衡,则递归次数多。递归次数与处理顺序无关。

10、n!后面有多少个0,6!=1*2*3*4*5*6=720.720后面有1个0,n=10000,求n!

10000/5=2000 有2000个能被5整除
2000/5=400     这2000个里面能被5整除有400个(2000个已被5除过1次。能除第二次的有400)
400/5=80         同理 80个
80/5=16           同理 16个
16/5=3余1        同理 3个
结果2000+400+80+16+3=2499

11、对递归程序的优化的一般的手段是什么?

尾递归优化

12、一个递归算法如下,那么f(f(9))需要计算多少次f函数(注:这种题目,画树图最直观快速)

function f(i){
    if(i<=3){
          return 1;
    }else{
          return f(i-2)+f(i-6)+1;
   }
}

一、先算内层f(9)
    [1] 计算 f(9) = f(7) + f(3) + 1;
    [2] 计算[1]中 f(7) = f(5) + f(1) + 1;
    [3] 计算[2]中 f(5) = f(3) + f(-1) + 1;
    [4] 计算[3]中 f(3) = 1;
    [5] 计算[3]中 f(-1) = 1;
        {至此f(5)可计算得: f(5) = 1 + 1 + 1 = 3}
    [6] 计算(1)中f(1) = 1;
        {至此f(7)可计算得 :f(7) = 3 + 1 + 1 = 5}
    [7] 计算[1]中f(3) = 1;
        {至此f(9)可计算得:f(9) = 5 + 1 + 1 = 7}
    计算f(9)一共调用了7次函数

二、计算外层f(7)
    由上面步骤可知,计算f(7)调用了5次函数
    所以一共调用了函数7+5=12次

13、递归函数最终会结束,那么这个函数一定有一个分支不调用自身

14、对n个记录的线性表进行快速排序为减少算法的递归深度,每次分区后,先处理较短的部分

15、当n=5时,下列函数的返回值是多少

function f(i){
    if(i<2){
          return i;
   }else{
          return f(i-1)+f(i-2);
   }
}
console.log(f(5));

解法一:
f(0)=0;
f(1)=1;
f(2)=f(1)+f(0)=1;
f(3)=f(2)+f(1)=2;
f(4)=f(3)+f(2)=3;
f(5)=f(4)+f(3)=5;

解法二:

16、设有递归算法如下,那么f(f(8))时需要计算多少次f函数?

function x(i){
   if(i<=3){
          return i;
   }else{
          return x(i-n)+x(i-4)+1;
   }
}

解法一:
x(8)=x(6)+x(4)+1  递归计算x(8)第一次调用
x(6)=x(4)+x(2)+1  递归计算x(6)第二次调用
x(4)= x(2)+x(0)+1  递归计算x(4)第三次调用
x(4)= x(2)+x(0)+1   递归计算x(4)第四次调用
之后再调用x()计算黑体部分的结果(5次,加上前面4次,一共9次),最后x(8)返回值为9

接着计算x(9)
x(9)=x(7)+x(5)+1  递归计算x(9)第一次调用
x(7)=x(5)+x(3)+1  递归计算x(7)第二次调用
x(5)=x(3)+x(1)+1  递归计算x(5)第三次调用
x(5)=x(3)+x(1)+1  递归计算x(5)第四次调用
之后再调用x()计算黑体部分的结果(5次,加上前面4次,一共9次),最后x(8)返回值为9

解法二:
根据题意,易得x(3) = x(2) = x(1) = x(0) = 1
x(8) = x(6) +x(4) +1
       = x(4) + x(2) +1 + x(2) + x(0) +1 + 1
       = x(2) + x(0) +1 + 1 + 1 +1 + 1 +1 + 1
       = 9
x(8)  这个就调用了9次函数x(int n)
同理可得x(9)也是调用了9次函数x(int n)
所以总共18次。

17、在递归算法执行过程中,计算机系统必定会用到的数据结构是

18、递归函数中的形参是自动变量

时间: 2024-10-22 06:31:58

算法之递归的相关文章

Java数据结构和算法之递归

四.递归 递归是函数调用自身的一种特殊的编程技术,其应用主要在以下几个方面:   阶乘 在java当中的基本形式是: Public  void  mothed(int n){//当满足某条件时: Mothed(n‐1): } 递归二分查找 Java二分查找实现,欢迎大家提出交流意见.  /** *名称:BinarySearch *功能:实现了折半查找(二分查找)的递归和非递归算法. *说明: *     1.要求所查找的数组已有序,并且其中元素已实现Comparable<T>接口,如Integ

数据结构与算法5: 递归(Recursion)

数据结构与算法5: 递归(Recursion) 写在前面 <软件随想录:程序员部落酋长Joel谈软件>一书中<学校只教java的危险性>一章提到,大学计算机系专业课有两个传统的知识点,但许多人从来都没搞懂过,那就是指针和递归.我也很遗憾没能早点熟练掌握这两个知识点.本节一些关键知识点和部分例子,都整理自教材或者网络,参考资料列在末尾.如果错误请纠正我. 思考列表: 1)什么程序具有递归解决的潜质? 2)递归还是非递归算法,怎么选择? 3)递归程序构造的一般模式 1.递归定义 首要引

二分查找算法(递归与非递归两种方式)

首先说说二分查找法. 二分查找法是对一组有序的数字中进行查找,传递相应的数据,进行比较查找到与原数据相同的数据,查找到了返回1,失败返回对应的数组下标. 采用非递归方式完成二分查找法.java代码如下所示. /* * 非递归二分查找算法 * 参数:整型数组,需要比较的数. */ public static int binarySearch(Integer[]srcArray,int des){ //第一个位置. int low=0; //最高位置.数组长度-1,因为下标是从0开始的. int h

java数据结构与算法之递归思维(让我们更通俗地理解递归)

[版权申明]转载请注明出处(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/53452971 出自[zejian的博客] 关联文章: java数据结构与算法之顺序表与链表设计与实现分析 java数据结构与算法之双链表设计与实现 java数据结构与算法之改良顺序表与双链表类似ArrayList和LinkedList(带Iterator迭代器与fast-fail机制) java数据结构与算法之栈(Stack)设计与实现 j

全排列算法的递归与非递归实现

全排列算法的递归与非递归实现 全排列算法是常见的算法,用于求一个序列的全排列,本文使用C语言分别用递归与非递归两种方法实现,可以接受元素各不相同的输入序列. 题目来自leetcode: Given a collection of numbers, return all possible permutations. For example, [1,2,3] have the following permutations: [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3

编程算法 - 背包问题(递归) 代码(C)

背包问题(递归) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 有n个重量和价值分别为w,v的物品, 从这些物品中挑选出总重量不超过W的物品, 求所有挑选方案中价值总和的最大值. 即经典动态规划问题. 可以使用深度优先搜索, 把每个部分都遍历到, 选取最优解, 但不是最好的方法. 代码: /* * main.cpp * * Created on: 2014.7.17 * Author: spike */ /*eclipse cdt, gc

[原创] 算法之递归(3)- 链表操作

算法之递归(3)- 链表操作 递归(2)尝试了一个单链表的遍历,同时又分析了如何添加自己的操作,是在递归调用之前,还是在递归调用之后. 今天,打算将问题深入一下,即添加相应的操作在递归的过程中. (免责声明:下面的解法纯属娱乐 ,另外,示例代码未经编译和调试,许多想法未经实践验证.) 查找链表当中倒数第N个节点. 解法一 逐层递归,遍历到最后一个节点,并从返回的节点一次向后递归,遍历N次,找到倒数第N个节点. private LNode targetNode = null; private LN

[原创] 算法之递归(2)- 链表遍历

算法之递归(2)- 链表遍历 在递归(1)中,简单的介绍了递归的思想,并且通过一个例子简单阐述了递归是如何工作的,并且递归的实现是以线性结构来表示的.之所以用线性的,是因为其易于理解:如果使用树结构,将加大对问题的难度,不利于初学者理解递归的思想. 为什么用递归 关 于为什么用递归,我个人的理解是递归不要违背算法的初衷,即期待传入xxx值,加工后返回xxx值.不要为了递归而递归,容易造成对函数语义的奇异.另 外,通过递归,可以让代码更加整洁,短小,精湛,优美.当然,还会存在一定程度的性能损耗:不

[原创] 算法之递归(1)

算法之递归(1) 最近事情太多了,很少有时间可以完全静下来认真研究一些东西:每当专注的写代码的时候,总是被其他事情打断.还好,礼拜天来了,总算可以抽出一些时间了J <代码之美>第一章有一个关于模式匹配的问题,即根据正则表达式在相应的文本中找到匹配的值,找到返回1,没找到返回0.撇开具体的实现,里面优美的递归调用倒是深深地吸引了我.于是,我开始重新思考递归,毕竟有些细节之前的思考还不够到位. 什么是递归 我的印象中的递归是函数调用自身来完成预先定义的操作.当然,实际涉及的内容更多,比如退出递归的