以此类推——深入理解递归算法以及应用场景

1 递归的思想

以此类推是递归的基本思想
具体来讲就是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时,因为解决大问题的方法和解决小问题的方法往往是同一个方法,所以就产生了函数调用它自身的情况。另外这个解决问题的函数必须有明显的结束条件,这样就不会产生无限递归的情况了。

递归的两个条件

  • 可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用
  • 存在一种简单情境,可以使递归在简单情境下退出。(递归出口

2 怎么更好地理解递归算法

递归:你打开面前这扇门,看到屋里面还有一扇门(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开,。。。, 若干次之后,你打开面前一扇门,发现只有一间屋子,没有门了。 你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这钥匙开了几扇门。递归就是有去(递去)有回(归来)

循环:你打开面前这扇门,看到屋里面还有一扇门,(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,(前面门如果一样,这门也是一样,第二扇门如果相比第一扇门变小了,这扇门也比第二扇门变小了(动静如一,要么没有变化,要么同样的变化)),你继续打开这扇门,。。。,一直这样走下去。 入口处的人始终等不到你回去告诉他答案

3 应用场景

3.1 Fibonacci数列.

提到递归,我们可能会想到的一个实例便是斐波那契数列。斐波那契数列就是如下的数列:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …,
总之,就是第N(N > 2)个数等于第(N - 1)个数和(N - 2)个数的和。用递归算法实现如下:

Fibonacci数列

实现代码:

package com.boer.tdf.act.demo.recursion;

/**
 * Fibonacci数列.
 *
 * 提到递归,我们可能会想到的一个实例便是斐波那契数列。斐波那契数列就是如下的数列:
 *
 *   0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …,
 *   总之,就是第N(N > 2)个数等于第(N - 1)个数和(N - 2)个数的和。用递归算法实现如下:

 */
public class Fibonacci {

    /**
     * Fibonacci数列
     * @param n 数的位置
     * @return
     */
    public static int fibonacci(int n) {
        if (n < 0) return -1;
        if (n == 0) return 0;
        if (n == 1) return 1;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }

    public static void main(String[] args) throws Exception {

        Fibonacci fibonacci = new Fibonacci();

        System.out.println(fibonacci.fibonacci(10));

        /**
         * 运行结果:55
         */

    }
}

3.2 阶乘

还有就是求一个数的阶乘,也会用到递归,这个比较简单,直接给出实现代码:

package com.boer.tdf.act.demo.recursion;

/**
 * 阶乘 .
 * 还有就是求一个数的阶乘,也会用到递归,这个比较简单,直接给出实现代码
 */
public class Factorial {

    public static int factorial(int n) {
        int sum = 0;
        if (0 == n)
            return 1;
        else
            sum = n*factorial(n-1);
        return sum;
    }

    public static void main(String[] args) throws Exception {
        Factorial factorial = new Factorial();
        System.out.println(factorial.factorial(6));

        /**
         * 运行结果:720
         */
    }

}

在求解6的阶乘时,递归过程如下所示。

阶乘递归的过程

我们会惊奇的发现这个过程和栈的工作原理一致对,递归调用就是通过栈这种数据结构完成的。整个过程实际上就是一个栈的入栈和出栈问题。然而我们并不需要关心这个栈的实现,这个过程是由系统来完成的。
那么递归中的“递”就是入栈,递进;“归”就是出栈,回归

3.3 汉诺塔问题

汉诺塔问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

汉诺塔是根据一个传说形成的数学问题:


汉诺塔示意图(图片来自网络)

有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:

  1. 每次只能移动一个圆盘;
  2. 大盘不能叠在小盘上面。
    提示:可将圆盘临时置于B杆,也可将从A杆移出的圆盘重新移回A杆,但都必须遵循上述两条规则。
    问:如何移?最少要移动多少次

下面是汉诺塔的递归求解实现:

package com.boer.tdf.act.demo.recursion;

/**
 * 汉诺塔问题.
 *  汉诺塔是根据一个传说形成的数学问题:
 */
public class Hanoi {

    public static void hannoi(int n, String from, String buffer, String to) {

        if (n == 1) {
            System.out.println("Move disk " + n + " from " + from + " to " + to);
        } else {
            hannoi(n - 1, from, to, buffer);
            System.out.println("Move disk " + n + " from " + from + " to " + to);
            hannoi(n - 1, buffer, from, to);
        }

    }

    public static void main(String[] args) throws Exception {

        Hanoi hanoi = new Hanoi();

        hanoi.hannoi(4,"1","2","3");

        /**
         * 运行结果:
         * Move disk 1 from 1 to 2
         Move disk 2 from 1 to 3
         Move disk 1 from 2 to 3
         Move disk 3 from 1 to 2
         Move disk 1 from 3 to 1
         Move disk 2 from 3 to 2
         Move disk 1 from 1 to 2
         Move disk 4 from 1 to 3
         Move disk 1 from 2 to 3
         Move disk 2 from 2 to 1
         Move disk 1 from 3 to 1
         Move disk 3 from 2 to 3
         Move disk 1 from 1 to 2
         Move disk 2 from 1 to 3
         Move disk 1 from 2 to 3
         */

    }
}

3.4 排列组合

对于一个长度为n的串或者n个字符(数字、节点)组成的字符串数组,它的全排列共有A(n, n)=n!种。这个问题也是一个递归的问题。如1,2,3,全排列可得到:{123,132,213,231,312,321},输出任意个数字母、数字的全排列 。

用递归算法实现代码如下:

package com.boer.tdf.act.demo.recursion;

/**
 * 输出任意个数字母、数字的全排列 .
 *
 *   对于一个长度为n的串或者n个字符(数字、节点)组成的字符串数组,
 * 它的全排列共有A(n, n)=n!种。这个问题也是一个递归的问题。
 * 如1,2,3,全排列可得到:{123,132,213,231,312,321}。


 */
public class Permutation {

    public static void permutation(String[] nums, int m, int n) {

        String t;
        if (m < n - 1) {
            permutation(nums, m + 1, n);
            for (int i = m + 1; i < n; i++) {
                // 可抽取Swap方法
                t = nums[m];
                nums[m] = nums[i];
                nums[i] = t;

                permutation(nums, m + 1, n);
                // 可抽取Swap方法
                t = nums[m];
                nums[m] = nums[i];
                nums[i] = t;
            }
        } else {
            for (int j = 0; j < nums.length; j++) {
                System.out.print(nums[j]);
            }
            System.out.print(",");
        }
    }

    public static void main(String[] args) throws Exception {

        String[] str=new String[]{"1","2","3"};

        Permutation permutation = new Permutation();

        permutation.permutation(str,0,str.length);

        /**
         * 输出结果:123,132,213,231,321,312,
         */

    }
}

3.5 归并排序

归并排序也是递归的典型应用,其思想:将序列分为若干有序序列(开始为单个记录),两个相邻有序的序列合并成一个有序的序列,以此类推,直到整个序列有序。

归并排序

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。

工作原理

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  4. 重复步骤3直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

代码实现

package com.boer.tdf.act.demo.recursion;

/**
 * 归并排序.
 */
public class MergeSort {

    static int number=0;

    public static void main(String[] args) {
        int[] a = {14, 12, 15, 13, 11, 16 };
        printArray("排序前:",a);
        MergeSort(a);
        printArray("排序后:",a);
    }

    private static void printArray(String pre,int[] a) {
        System.out.print(pre+"\n");
        for(int i=0;i<a.length;i++)
            System.out.print(a[i]+"\t");
        System.out.println();
    }

    private static void MergeSort(int[] a) {
        System.out.println("开始排序");
        Sort(a, 0, a.length - 1);
    }

    private static void Sort(int[] a, int left, int right) {
        if(left>=right)
            return;

        int mid = (left + right) / 2;
        // 二路归并排序里面有两个Sort,多路归并排序里面写多个Sort就可以了
        Sort(a, left, mid);
        Sort(a, mid + 1, right);
        merge(a, left, mid, right);

    }


    private static void merge(int[] a, int left, int mid, int right) {

        int[] tmp = new int[a.length];
        int r1 = mid + 1;
        int tIndex = left;
        int cIndex=left;
        // 逐个归并
        while(left <=mid && r1 <= right) {
            if (a[left] <= a[r1])
                tmp[tIndex++] = a[left++];
            else
                tmp[tIndex++] = a[r1++];
        }
        // 将左边剩余的归并
        while (left <=mid) {
            tmp[tIndex++] = a[left++];
        }
        // 将右边剩余的归并
        while ( r1 <= right ) {
            tmp[tIndex++] = a[r1++];
        }

        System.out.println("第"+(++number)+"趟排序:\t");
        // 从临时数组拷贝到原数组
        while(cIndex<=right){
            a[cIndex]=tmp[cIndex];
            // 输出中间归并排序结果
            System.out.print(a[cIndex]+"\t");
            cIndex++;
        }
        System.out.println();
    }

}

3.6 趣味问题——年龄。

有5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比第一个人大两岁。最后问第一个人,他说是10岁。请问第五个人多大?用递归算法实现。

package com.boer.tdf.act.demo.recursion;

/**
 * 趣味问题——年龄。
 *
 *  有5个人坐在一起,
 *  问第五个人多少岁?他说比第4个人大2岁。
 *  问第4个人岁数,他说比第3个人大2岁。
 *  问第三个人,又说比第2人大两岁。
 *  问第2个人,说比第一个人大两岁。
 *  最后问第一个人,他说是10岁。
 *
 *  请问第五个人多大?用递归算法实现。.
 */
public class Recursion01 {

    /**
     * 用循环计算这道题
     * @param num 人数
     * @return
     */
    public static int getAge01(int num) {
        int age = 10;

        while (num>1) {
            age += 2;
            num -= 1;
        }
        return age;
    }


    /**
     * 换成递归计算这道题
     * @param num 人数
     * @return
     */
    public static int getAge02(int num) {
        if (num==1) return 10;
        return getAge02(num-1)+2;
    }

    /**
     * 换成尾递归
     * @param num  人数
     * @param acc  岁数
     * @return
     */
    public static int getAge03(int num,int acc) {
        if (num == 1)
            return acc;
        return getAge03(num-1,acc+2);
    }

    public static void main(String[] args) throws Exception {

        Recursion01 recursion01 = new Recursion01();

        System.out.println(recursion01.getAge01(5));

        System.out.println(recursion01.getAge02(5));

        System.out.println(recursion01.getAge03(5,10));

        /**
         * 运行结果:
         * 18
         18
         18
         */
    }
}

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

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,188评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,733评论 0 15
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,278评论 0 35
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,260评论 0 2
  • 一. 写在前面 要学习算法,“排序”是一个回避不了的重要话题,在分析完并查集算法和常用数据结构之后,今天我们终于可...
    Leesper阅读 2,535评论 0 40