递归

递归是一种方法(函数)调用自己的编程技术。

知道递归这两个字的时候是在大学生活时代的C语言的课堂上,当时老师提到了递归,说这是一种一个函数自己调用自己的方法,然后就非常感兴趣,感觉非常的神奇,但是当自己实际用到的时候,却怎么都理解不了他的好处,它是怎么实现之类的,就不像 if-else 语句这种,学的时候就可以在大脑中刻画一个流程图出来,然后可以很快的理解,所以当时出于对知识的敬畏,就没有在深入研究下去了。现在重新接触到它,决定重新啃啃着硬骨头了

递归算法的特征:

  1. 调用自身
  2. 当它调用自身的时候,它这样做是为了解决更小的问题
  3. 存在某个足够简单的问题的层次,在这一层算法不需要调用自己就可以直接解答,且返回结果。

这里写图片描述

首先要从一个最基础的三角数字问题说起:简单的表示就是一个数字序列具有以下的特点:就是该数列中的第 n 项是由第 n-1 项加 n 得到的。
1. 1
2. 1 + 2 = 3
3. 1 + 2 + 3 = 6
4. 1 + 2 + 3 + 4 = 10

现在我们要求输入一个整数n,输出其在三角数列中的值:
使用递归解决的代码:

/* 递归解决最基本的三角数字问题 */
public class Triangle {
    static int theNumber;
    public static int triangle(int n) {
        if (n == 1) {                    // 当递归到1的时候 停止迭代 直接返回1
            System.out.println("Returning 1");
            return 1;
        } else {                           // 没到1的话 继续递归
            int temp = (n + triangle(n-1));
            System.out.println("Returning " + temp);
            return temp;
        }
    }
    public static void main(String[] args) {
        theNumber = 7;
        System.out.println("Entering: n=" + theNumber);
        int theAnswer = triangle(theNumber);
        System.out.println(theAnswer);
    }
}
上面的代码有一些输出语句,如果输入的数字比较小的话,我们就可以比较清楚的看到其内部的执行过程。
 关于上面的代码的执行过程,我的理解是:(假如 n=4)
    1. n = 4, return (n + triangle(n-1))的时候,等待--------第一次进入 triangle
    2. n = 3, return (n + triangle(n-1))的时候,  等待--------第二次进入 triangle
    3. n = 2, return (n + triangle(n-1))的时候,  等待--------第三次进入 triangle
    4. n = 1, return (n + triangle(n-1))的时候,  不等待,返回1--------第四次进入 triangle
    5. 返回至第三次的函数,n = 2 + 1 = 3;
    6. 返回至第二次的函数,n = 3 + 3 = 6;
    7. 返回至第一次的函数,n = 6 + 4 = 10;
    8. 结束!!!
也就是说你输入 n;代码就得 n 次进入迭代函数,进行 n 次返回,n次判断,才得到你的结果,所以其实细看它内部的实现,就会发现:
    !!!递归只是在概念上简化了算法,而其内部并不是很有效率。但是其仍然是一种非常有意思的算法!

递归应用的另外一种情况 – 变位字:

所谓变位字的概念就是将一个单词全部分割为字符,然后将他们全排列:

解决这个问题的思路是:首先得让这个单词的中的每一个字符都有排在首位的机会,然后再让剩下的字符全排列。比如我们输入的单词拥有四个字符,那么就让着四个字母都有在首位的机会,然后剩下的三个字母全排列,三个字母全排列也不是那么简单的,怎么办呢?继续呀,让这三个字母也和上面的情况一样,给他们其中的每个字母都拥有在首位的机会,然后剩下的两个字母全排列,两个字母全排列应该不是很复杂吧,互换位置就是两种不同的情况,这样问题就解决了!!!

先上代码:
/* 迭代-变位字 */
public class anagram {  
    static int size;
    static int count;
    static char[] arrChar = new char[100];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Please entering a word:");
        String input = sc.next();
        size = input.length();  // 初始化输入字符串的长度
        count = 0;              // 作用:输出计数
        for (int j=0; j<size; j++)
            arrChar[j] = input.charAt(j);   // 将输入的字符串转化为数组
        doAnagram(size);    // 进行迭代操作 达到字符变位目的
        sc.close();
    }

    // 变位业务
    public static void doAnagram(int newSize) {
        if (newSize == 1) 
            return;
        for (int j=0; j<newSize; j++) {
            doAnagram(newSize - 1); // 迭代
            if (newSize == 2)   // 如果需要交换的子字符串或者原字符串的长度为2的话 就输出交换的结果 
                                // 因为下一次的迭代已经不会发生字符的交换了
                displayWord();  
            rotate(newSize);    // 原字符串 或者 子字符串的字符交换函数
        }
    }
    // 字符交替子函数
    public static void rotate(int newSize) {
        int j ;
        int position = size - newSize;  // position 表示从哪一位开始交换位置 (这里的position表示是要进行字符交换的原字符串或者子字符串的首字符索引值)
        char temp = arrChar[position];  // (将要交换的原字符串或子字符串的首字符做备份)
        for (j = position+1; j<size; j++)   // 进行交换
            arrChar[j-1] = arrChar[j];      // 交换的结果就是 (原字符串或者子字符串)整体向左移动一个单位
        arrChar[j-1] = temp;                // 再将原来备份好的第一个字符放到最后一位
    }
    // 打印函数
    public static void displayWord() {
        if (count < 99)
            System.out.print(" ");
        if (count < 9)
            System.out.print(" ");
        System.out.print(++count + " ");    // 计数作用在这里实现
        for (int j=0; j<size; j++)
            System.out.print( arrChar[j] ); // 打印当前字符数组的状态
        System.out.print("  ");
        if (count%6 == 0)                   // 规定每行输出 6 个结果
            System.out.println();
    }   
}
假如输入 ter 的话这个输出的结果是:
  1 ter    2 tre    3 ert    4 etr    5 rte    6 ret 
代码执行过程解析:
        首先经过初始化的字符数组  现在的内容是ter
        1. 第一次进入 doAnagram,开始循环1第一次循环 循环3次 -------ter
        2. 第二次进入 doAnagram,此时size已经变为2,开始循环2 循环2次 -------ter
        3. 开始循环2的第一次循环,第三次进入 doAnagram ,此时size已经变为了1,会直接返回 ------- ter
        4. 继续循环2的第一次循环,判断 size 为2,输出当前的 字符串 ter,进行子字符串的字符交换 ----tre
        5. 开始循环2的第二次循环,第四次进入 doAnagram ,此时size已经变为了1,会直接返回 ------- tre
        6. 继续循环2的第二次循环,判断 size 为2,输出当前的 字符串 tre,进行子字符串的交换 ,循环2结束 ------ ter
        7. 继续循环1第一次循环,判断 size 为3,直接进行原字符串的字符交换,循环1第一次循环结束,循环1第二次循环开始 ------------ ert
        8. 第五次进入 doAnagram,此时 size 已经变为2,开始循环3 循环2次 ------ ert
        9. 开始循环3的第一次循环,第l六次进入 doAnagram ,此时size已经变为了1,会直接返回 ------ ert
        10. 继续循环3的第一次循环,判断 size 为2,输出当前的 字符串 ert,进行子字符串的字符交换    --------etr
        11. 开始循环3的第二次循环,第七次进入 doAnagram ,此时size已经变为了1,会直接返回 ------ etr
        12. 继续循环3的第二次循环,判断 size 为2,输出当前的 字符串 etr,进行子字符串的交换 ,循环2结束 ------ ert
        13. 继续循环1第二次循环,判断 size 为3,直接进行原字符串的字符交换,循环1第一次循环结束,循环1第三次循环开始 ----- ret
        14. 。。。。。(由于码字太累了 实在不行了 没有思路画个图出来  只能用文字表述了 剩下的 循环1的第三次循环和上面的类似)

递归的二分查找

 解决这个问题的思路就是:
    先看二分查找的思维,它对自己每次查找的范围都用一个  当前范围的最大下标和最小下标来表示,然后用两个下标的平均数去做一个判断的依据,其实大体的模式就是这样的。然后他也有对应的结束的条件,要是找到了就返回对应的下标,没有找到就返回数组的大小,每次查找的范围也在逐步的缩减,从这么看,它完全符合 递归的所有特征,所以实现起来按照递归的正常实现去实现就行了。
/* 使用递归实现二分查找 */
public class binarySearch2 {
    private int[] arr;
    private int nItems;

    public binarySearch2(int max) {
        arr = new int[max];
        nItems = 0;
    }

    int find( int searchkey, int lowerBound, int upperBound) {      // 递归的二分查找函数
        int curIn = (lowerBound + upperBound)/2;
        if (arr[curIn] == searchkey)    // 找到要找的元素
            return curIn;
        else if (lowerBound > upperBound)    // 数组中没有要找的元素
            return nItems;
        else {
            if (curIn > searchkey)    // 当目标在左半部分的时候
                return find(searchkey, lowerBound, curIn-1);
            else                      // 当目标在右办部分的时候
                return find(searchkey, curIn+1, upperBound);
        }
    }

    public static void main(String[] args) {
        int max = 50;
        binarySearch2 Arr = new binarySearch2(max);
        for (int i=0; i<max; i++) {
            Arr.arr[i] = (int)Math.ceil(Math.random()*99);
            Arr.nItems ++;
        }
        Arrays.sort(Arr.arr);    // 数组工具类将无序数组,进行排序
        int a = Arr.find(4, 0, max-1);
        if (a == max) {
            System.out.println("生成的随机数中,没有 4 ");
        } else {
            System.out.println("生成的随机数组中有4 且在有序数组中的下标为:" + a);
        } 
    }
这里的用递归实现二分查找,其实就是和递归的基本的那个例子差不多:其实仔细看递归就两点:
 1. 自己调用自己
 2. 在函数中有对应的结束条件,结束之后将对应的值返回至上一级函数,再逐级返回,最后到达最外层的函数,得到结果!但是这个例子中和上面两个例子不一样的地方在于,在的递归的方法中含有两个对自身的递归的调用,分别对应于问题的两个部分,虽然只有一个是真正的执行了。

经典的汉诺塔问题:

诺塔问题是一个比较经典的问题,具体汉诺塔是什么东西,就不在细说了,直接说用递归解决该问题的思路。
现在有 A B C 三个柱子,要求是将在 A 柱上的 N 个盘子,全部移到 C 上,且每次只能移动一个盘子,每个柱子上的盘子只能是大的在下,小的在上。
    假设我们现在有 4 个盘子在 A 上,那我们可以先将上面比较小的三个盘子,先放在 B 上,然后将最大的盘子放在 C 上,然后再将 B 上的三个较小的盘子放在 C上 ,游戏结束!
    那么我会想,还能这么玩吗?直接将三个盘子一起移动 ,不是一次只能移动一个吗?当然,一次只能移动一个,那是因为移动三个盘子到 B 的过程,已经被递归完成了。我们假设要移动的盘子有两个,按照上面的思路,你还会觉得有问题吗?
/*汉诺塔问题(递归解决)*/
public class towers {
    static int nDisks = 3;

    public static void doTowers(int topN, char from, char inter, char to) { // topN 就代表着目前的子塔还有几个碟子
        if (topN == 1) {
            System.out.println("Disk 1 from " + from + " to " + to);
        } else {
            doTowers(topN-1, from, to, inter);     
            System.out.println("Disk " + topN + " from " + from + " to " + to);             
            doTowers(topN-1, inter, from, to);
        }
    }   
    public static void main(String[] args) {
        doTowers(nDisks, 'A', 'B', 'C');
    }
}
就像上面的代码中描述的那样,它的 doTowers 函数中的 else 目录下有三条语句,这三条语句就分别代表的上面说到的三个步骤,移动子塔、移动最大的,再移动子塔。其实着个问题也可以说是一个甩锅问题,你看首先处理四个碟子的人要去解决这个问题,然后它觉得好烦呀,就交给了处理三个盘子的人,他说,你把这三个碟子的事解决了,我把最大的那个解决了就行,然后处理三个碟子问题的人也是觉得很烦,他又将这个问题甩给了解决两个碟子问题的人,他还说,你把着俩个碟子的事解决了,我将最大的碟子解决了就行了,然后处理两个碟子问题的人也是觉得烦,又甩给了处理一个碟子问题的人,他还说了和其他人类似的话,这时候处理一个碟子问题的人不能再甩锅了,他老实的把一个碟子的问题解决了,这时候处理两个碟子问题的人当然又可以把自己两个碟子的问题解决,三个碟子、四个碟子最后都解决了,我觉得这就是递归算法的核心思想 —– 甩锅!!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值