zoukankan      html  css  js  c++  java
  • 算法之路——递归

    • 递归(recursion):程序调用自身的编程技巧。

      • 递归满足2个条件:

        1)有反复执行的过程(调用自身)

        2)有跳出反复执行过程的条件(递归出口)


    • 递归大家都懂是什么意思,在现实生活中我们也很容易去操作,但是在程序员的世界递归确实是很多人都头疼的一大难题。我总结了递归特色的一句话:简约而不简单

    • 本博主打算通过四个案列来详细的剖析递归里面的思想

      • 阶乘算法

      • 全排列算法

      • 汉诺塔问题

      • 斐波那契数列


    • 阶乘

      阶乘应该是递归里面比较简单的问题了,所以我放在第一个将,在这里并不是越往后的越难,我个人觉得斐波那契数列和阶乘是同级别的,都很简单,阶乘主要就是拿一个数不停的去和比自己小一的数据相乘,一直称到1为止。最后返回数字就是阶乘的数据。这个可以用循环也可以用递归,既然我们本章主题是递归,那我们就用递归来实现吧。

    //阶乘
    int recursive(int i)
    {
        int sum = 0;
        if (0 == i)
            return (1);
        else
            sum = i * recursive(i-1);
        return sum;
    }
    • 全排列问题:

      解法一、在数组里进行两两不重复交换,每次交换得到的新数组组成的数据就是全排列的一种情况。 这个有点类似我们排序中的冒泡排序。
      这里写图片描述
      这个逻辑进行下去就是不断的交换,每次交换完成就会产生一组数据,我们只要输出就行了

    //全排列
    inline void Swap(int &a,int &b)
    {
        int temp=a;
        a=b;
        b=temp;
    }
    void Perm(int list[],int k,int m)
    {
        if (k == m-1) 
        {
            for(int i=0;i<m;i++)
            {
                printf("%d",list[i]);
            }
            printf("n");
        }
        else
        {
            for(int i=k;i<m;i++)
            {
                Swap(list[k],list[i]); 
                Perm(list,k+1,m);
                Swap(list[k],list[i]); 
            }
        }
    }

    解法二、对新数据进行遍历,遍历会拿到每一条数据,拿到数据我们在判断这个数据是否已经被使用了,是则抛弃,否则选择。在递归进入结束条件时,我们输出我们的新数据,最后跳出本条递归。跳出之后我们就该将最近选择的数据状态置为未使用状态,方便下一次遍历使用。这样就会形成全排列。

    public static void Circle(Object[] arry,Object[] visit,Integer index){
            if(index==arry.length){
            //自己写的输出list集合的方法。读者自己写
                outArry(resultArry);
                return ;//递归结束;开始想外层跳出
            }else{
                for(int i=0;i<arry.length;i++){
                    if((Integer)visit[i]==0){
                        resultArry[index]=arry[i];
                        //将该数字置为选中状态,下次再配到该数字则弃用该数字
                        visit[i]=1;
                        index++;
                        Circle(arry, visit, index);
                        index--;
                        //跳出循环,应该讲最近使用的数字状态恢复为0
                        visit[i]=0;
                    }
                }
            }
        }

    解法二不足之处:在解法二中我将数组写死了,不能根据用户的变化而将记录数组进行改变,后来我进行改进,具体代码如下:

    private static List<Object> resultArry=new ArrayList<Object>();
        private static List<Integer> state=new ArrayList<Integer>();
        public static void Circle(Object[] arry,Integer index){
            if(index==arry.length){
                outList(resultArry);
                return ;//递归结束;开始想外层跳出
            }else{
                for(int i=0;i<arry.length;i++){
                    if(state.get(i)==0){
                        resultArry.add(arry[i]);
                        state.set(i, 1);
                        index++;
                        Circle(arry, index);//递归的体现
                        resultArry.remove(arry[i]);
                        index--;
                        state.set(i, 0);
                    }
                }
            }
        }

    效果对1、2、3进行全排列:
    这里写图片描述
    效果对1、2、3、4进行全排列:
    这里写图片描述

    • 汉诺塔问题

      这里写图片描述

      这里写图片描述

    • 思路梳理:

      上图中想实现a–>b,我们必须先将a中上面两个盘子拿到借助柱子B上。那么下面我们要考虑的是如何将A中两个盘子保持顺序的拿到B上呢,这时我们就需要借助C柱来完成,这就需要用递归了。因为我们最终问题是将A上三个盘子借助B按顺序拿到C上,我将问题转换成了将A上的两个盘子借助C拿到B上。ABC在方法里我用1、2、3代替。如果我将方法设为public static void hanoi(int n,int p1,int p2,int p3)
      也就是说一开始我们调用的是hanoi(3,1,2,3)
      到我们里面进行分析后变为hanoi(2,1,3,2)(这里转化理解醉关键)
      下面我们就一层一层向内部挖掘,那么,我们该什么时候结束这个循环递归呢?答案当然是在A柱上就剩一个盘子的时候我们就结束了,也即是方法hanoi(1,1,2,3) 这个时候我们只用将A柱上的一个盘子直接拿到C盘上

      还有一点就是在我们都执行过了,现在我们回到我第一次剖析的地方,这个时候我们已经成功的将A柱上两个盘子拿到了B柱上了。这个时候我们就可以将A柱上最后一个盘子拿到C盘上,拿完之后我们该做什么呢,拿完之后我们可以将问题看成将B柱上的两个盘子借助A柱按顺序拿到C柱上。用方法表示为hanoi(2,2,1,3),这部我们继续走上面的思想就会完成。

      代码:

    public static void hanoi(int n,int p1,int p2,int p3)
        {
            if(1==n){
                System.out.println("盘子从"+p1+"移动到"+p3);
            }
            else
            {
                hanoi(n-1,p1,p3,p2);
                System.out.println("盘子从"+p1+"移动到"+p3);
                hanoi(n-1,p2,p1,p3);
            }
        }

    效果:

    这里写图片描述


    • 斐波拉契数列

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

      这个数列从第三项开始,每一项都等于前两项之和。

      有趣的兔子问题:

      一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子?

      分析如下:

      第一个月小兔子没有繁殖能力,所以还是一对;

      两个月后,生下一对小兔子,总数共有两对;

      三个月以后,老兔子又生下一对,因为小兔子还没有繁殖能力,总数共是三对;

      …… 

      依次类推可以列出下表:

    经过月数 幼崽对数 成兔对数 总体对数
    0 1 0 1
    1 0 1 1
    2 1 1 2
    3 1 2 3
    4 2 3 5
    5 3 5 8
    6 5 8 13
    7 8 13 21
    8 13 21 34
    9 21 34 55
    10 34 55 89
    11 55 89 144
    12 89 144 233
    //斐波那契
    long Fib(int n)
    {
     if (n == 0)
      return 0;
     if (n == 1)
      return 1;
     if (n > 1)
      return Fib(n-1) + Fib(n-2);
    }

    近期本博主将主要更新算法一类的博文,可能更新进度会慢点!




    做最好的自己,每天进步一点点

  • 相关阅读:
    AOC的服务还不错
    浅谈Java、MySQL的中文排序问题
    祝cnBlogs的Blogger们新年快乐!
    GT 3.9.4以及今天的工作
    堆排序
    桶排序
    常用排序算法稳定性分析
    VS2010远程调试环境配置详解
    基数排序
    如何修改数据库的服务器名称
  • 原文地址:https://www.cnblogs.com/zhangxinhua/p/8319244.html
Copyright © 2011-2022 走看看