递归与循环

一.递归与循环

递归,说白了就是自己调用自己。理论上,任何的循环都可以重写为递归形式,所有的递归也可以被表述成循环的形式,本文主要介绍如何将循环重写为递归。

二.循环改递归的两大要点

  • 发现逻辑“相似性”
  • 不要忘记递归的“出口”

小例子:

public class demo{
    public static void main(String[] args){
        for(int i=0;i<10;i++){
            System.out.println(i);
        }
    }
}

上述代码的输出结果是打印出数字“0-9”。

这是利用循环实现,接下来我们将循环改写为递归:

public class Main{
    public static void f(int n){
        if(n<0) return;//出口
        f(n-1);//打印0 ~ n-1
        System.out.println(n);//打印n
    }
    public static void main(String[] args){
       f(9);
    }
}

思路解释:“踢皮球”-常用来形容政府职能部门职责不清,相互推诿,比如你要到部门A盖个章,A叫你先去部门B盖章,B叫你去部门C盖章...这种将工作以踢皮球的方式甩给其他人做的方式其实很适用于递归。

但在递归中,如果你甩给其他人的工作和分配给你的工作完全一样,则程序陷入死循环,一直没有人进行工作,一直在甩锅,因此,递归中为:自己做一小部分事情,剩下的事情再甩给其他人去做。

我们要做的是打印除数字“0 - 9”,那么,我完全可以自己负责打印其中一个数(代码中表现为打印末尾数字9),将剩下的数字“0 - 8”交予其他人去打印,比如A打印9,B负责打印“0-8”,B又打印8,并将“0-7”甩给C去负责,C又打印7,剩下的甩给D......

每次甩给下个人的工作都少了一点,在代码中表现为每次进行调用的参数都不一致,这就是发现逻辑“相似性”,每次调用的都是f(int n)这个方法,但每次参数都发生了变化,即f(n-1)

但若一直递归下去,程序将陷入死循环,当n=0时,又会调用f(-1),这显然不正确,因此,我们需要给递归加上一个出口限制,即if(n<0) return;,当n<0时,代表0~9都已经打印完毕,我们需要终止递归。

:并不一定非得A打印9,将“0~8” 甩给B...也可以A负责打印首位0,将“1~9”甩给B...这种情况有些许不一样(其实就是参数发生了变化),代码如下:

public static void f2(int begin,int end){
    if(begin>end) return;
    System.out.println(begin);
    f2(begin+1,end);
}

由上可看出,循环转递归,可能并非只有一种解法,也可存在多种改写形式,但都遵循“相似性”+“出口”的原则。

三.构造相似性

  • 如果没有明显的相似性,需要主动构造
  • 不能相似的原因很可能是缺少参数
  • 递归与数学上的递推公式很类似

在循环转递归的过程中,可能并不具有非常明显的相似性,很可能是因为缺少参数的原因,这时候就需要我们去主动构造相似性,通常为增加参数来实现。

例子1:

public class demo{
    public static int addAll(int[] a){
        int x=0;
        for(int i=0;i<a.length;i++){
            x+=a[i];
        }
        return x;
    }
    public static void main(String[] args){
        int[] a={3,4,11,8};
        int sum=addAll(a);
        System.out.println(sum);
    }
}

上述代码的功能为将数组a中的所有元素求和并打印。接下来,将循环改写为递归:

public class Main{
    public static int f(int[] a,int begin){
        if(begin==a.length) return 0;//出口
        int x=f(a,begin+1);
        return x+a[begin];
    }
    public static void main(String[] args){
        int[] a={3,4,11,8};
        int sum=f(a,0);
        System.out.println(sum);
    }
}

转为递归求和,我们一开始想到的肯定是定义一个求和方法,方法内传入要求和的数组,即 f(int[] a) ,但这样踢皮球每次甩给别人的都是原封不动的任务,导致程序进入死循环,因此我们需要加入一个参数 int begin 作为当前对象负责求和的起始元素的下标,这样,A只需要将 begin+1~end 的求和工作甩给B去做,B只需要再将拿到的begin进行+1操作,然后甩给C去做......待B传回求和的值,A再将B传回的值加上a[0]就可以得到数组所有元素之和了。

:除了上述解法之外,还有以下解法:

  1. [0......end-1] [end],将数组拆成两部分,A负责end,将0~end-1甩给B去做;

  2. 折半求和:

    public class Main{
        public static int f(int[] a,int begin,int end){
         if(begin>end) return 0;
         int middle = (begin+end)/2; //取中值
         if(begin==end){
             return a[end];
         }
         return f(a,begin,middle)+f(a,middle+1,end);     
        }
    
        public static void main(String[] args){
            int[] a={3,4,11,8};
            int sum=f(a,0,a.length-1);
            System.out.println(sum);
        }
    }
    

例子2:

public class demo{
    public static boolean isSameString(String s1,String s2){
        return s1.equals(s2);
    }
    public static void main(String[] args){
        System.out.println(isSameString("abc","abcd"));
    }
}

上述代码的功能为比较两个字符串是否相同。接下来,将循环改写为递归:

public class Main {
    public static boolean f(String s1, String s2) {
        if(s1.length()!=s2.length()) {
            return false;
        }
        if(s1.length()==0) {//出口:当比较至两字符串为空时表示两字符串各字符均相同
            return true;
        }
        if(s1.charAt(0)!=s2.charAt(0)) {
            return false;
        }
        return f(s1.substring(1),s2.substring(1));
    }
    public static void main(String[] args) {
        System.out.println(f("abc", "abcd"));
    }
}

四.递归调用

  • 递归调用仅仅是被调用函数恰为主调函数(自己调用自己)
  • 注意每次调用的层次不同
  • 注意每次分配形参并非同一个变量
  • 注意返回的次序
image

如图所示:A运行到节点a,调用B,B运行到节点b,调用C,C执行结束之后,将结果返回节点b,B从节点b开始继续运行,B执行结束之后将结果返回节点a,A再从节点a开始继续运行...

该执行过程要求每一次节点中断调用其他方法,必须记录下该中断节点的环境信息,作用是为了调用结束返回结果之后原程序能够从上次中断位置起继续执行,在计算机中,通过一个结构来实现该效果(栈:先进后出):

A执行至a时将要去调用B,此时将节点a的环境信息通过压栈的方式保存在栈结构中,同理,在b节点处又将节点b的环境信息通过压栈的方式保存在栈结构中;

当C执行结束返回结果给B时,通过弹栈的方式将b进行出栈操作,这样B就可以从b开始继续运行,同理,当B执行完毕返回结果给A时,通过弹栈的方式将a进行出栈操作,这样A就可以从a开始继续运行。

逐层深入,逐层返回。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352