初学递归应该看得文章

       记得最开始接触到递归,是在大一学c语言时候接触到的,教材上讲的最简单的递归就是求阶乘,f(n) = n * f(n-1),很简单的一句话,但是当时我还是花了很多时间研究代码的执行过程,懂了以后,碰见后面的汉诺塔则完全崩溃了,代码的执行过程完全理不清,因此,最初看递归代码或者写递归代码,一定是从宏观入手,所以一定要知道宏观的思路:

       我是老总,负责创建策略(递归方程),并且使用该策略完成第一步任务,然后将剩余任务给我手下,他使用我创造的策略完成第二步任务(与第一步任务的不同仅在于传递的实参不同),然后我手下再将他的剩余任务给了他的手下,他手下使用我创造的策略完成第三步任务(依然是实参不同而已)...... 以此一直往下分配,直到最后一步被完成(设置的递归出口)。

      上面一段话很容易理解,简单而言,递归就是 递出去,归回来

      要练习递归,可以从改变for循环开始,因为只要是循环问题一般都可以转化为递归实现,例如输出9-0(至于输出0-9则会在下面的回溯中讲到):

  for(int i=9; i>=0; --i) 
      System.out.println(i);

      先想策略:编写输出9-0,我只负责输出当前的数,其余的数由手下完成,过程如下:

      1、框架:负责打印n-0

  f(int n){

  }

     2、我负责的部分是输出当前的n:

  f(int n){
    System.out.println(n);
  }

    3、手下负责输出(n-1)-0:

f(int n){
    System.out.println(n);
    f(n-1);
}

    4、递归出口,当输完0后不再继续往下分配:

f(int n){
    if(n<0) return;
    System.out.println(n);
    f(n-1);
}
    完成了,这是最简单的for循环改变递归,。

    递归代码的形参很重要,如果感觉当前的形参数目不能解决问题,可以增加相应的参数,例如下面求数组的和:

private static int f(int[] a) {
		
}

    如果单纯使用数组做形参是没办法计算的,因为不知道当前计算到了数组的哪个位置,所以应该再加一个参数代表数组的当前位置:

private static int f(int[] a, int k) {
	return a[k] + f1(a, k + 1);
}

    上面的策略中,我负责将当前位置的值与手下计算的数组后面值的和相加,至于递归出口就很简单了,如下:

private static int f(int[] a, int k) {
	if (k == a.length)
		return 0;
	return a[k] + f1(a, k + 1);
}

    所以,为了练习递归,应该把你见到的for循环,都尽可能的改为递归,这样才能提高对递归的理解。

    对递归有了一定的感觉,就可以对他的执行过程研究研究了,其实他就是利用栈来实现的,首先将当前代码的运行环境(变量值,参数值,等)压栈,然后又调用自身,又压栈,最后到了递归出口,然后依次弹出栈,并将结果返回,因此,递归注重的往往是结果,而循环则注重过程,循环条件也必须明确,所以,有时候递归能解决的问题,循环不一定能解决。

    如果想在脑海里构建递归模型,可以使用下面的模型,最内层执行完后,就退出,然后回到上一层,上一层的所有变量值还是原来的值,这点需要注意。

f(,n,) 
{
	f(,n-1,) 
	{
		f(,n-2,) 
		{
			.
			.
			.
			f(,0,)
			{
						
			}
					
		}
	}
}

    经常与递归一块出现的一个词是回溯,也很简单,就是在上面的模型里面每一个f执行完后添加几行代码,这几行代码就是回溯的代码,如下黄色部分代码(不用管代码意思)所处的位置就是回溯的代码,它一定是每一个f执行完都要执行的代码:

  f(,n,) 
  {
        f(,n-1,) 
        {
	        f(,n-2,) 
	        {
	             f(,0,)
	             {
		          .
		          .
		          .
	             }
	             System.out.println();
	        }
	        System.out.println();
        }
        System.out.println();
}

    上面第一个例子是输出9-0,下面输出0-9则要用到回溯:

public static void f(int n) {
	if(n<0) return;
	f(n-1);
	System.out.println(n); //回溯
}

    上面的策略是先递归调用f,在递到出口时,开始归,但每次归的时候会执行回溯代码,因此会输出0-9;

    递归是需要练习的,经过大量的练习,就会掌握它,它是很强大的,而且用的多了就会感觉它的代码很简洁,尤其学到二叉树,处处是递归,因为二叉树本身就是一个递归概念,所以一定要掌握递归,因为它并不难。

   下面有几个例子供学习,从f2到f9难度递增:

import java.util.Arrays;
public class Main {
	public static void main(String[] args) {

//		boolean t = f2("abc", "abcd");
//		System.out.println(t);
//
//		int n = f3(5, 2);
//		System.out.println(n);
//
//		int a[] = { 2, 1, 3 };
//		f4(a, 0);
//
//		int n = f5("aabcd", "aac");
//		System.out.println(n);
//
//		String s = f6("abcd");
//		System.out.println(s);
//
//		int n = f7(4, 3);
//		System.out.println(n);
//
//		int n = f8(4, 1);
//		System.out.println(n);
//
		int a[] = new int[6];
		f9(3, a, 0);

	}

	// 将n分解为所有可能的加和,如3=2+1, 3=1+1+1
	private static void f9(int n, int[] a, int k) {
		if (n == 0) {
			for (int i = 0; i < k; ++i) {
				System.out.print(a[i] + " ");
			}
			System.out.println();
		}
                
                for (int i = n; i > 0; i--) {
			if (k > 0 && i > a[k - 1]) {
				continue;
			}
			a[k] = i;
			f9(n - i, a, k + 1);
		}
	}

	// 计算m个A,n个B共有几种排列顺序
	private static int f8(int m, int n) {
		if (m == 0 || n == 0) {
			return 1;
		}
		return f8(m - 1, n) + f8(m, n - 1);
	}

	// 计算杨辉三角形第m行n列的数
	private static int f7(int m, int n) {
		if (n == 0) {
			return 1;
		}
		if (m == n) {
			return 1;
		}
		return f7(m - 1, n - 1) + f7(m - 1, n);
	}

	// 返回s串的倒串
	private static String f6(String s) {
		if (s.length() == 0) {
			return "";
		}
		return f6(s.substring(1)) + s.charAt(0);
	}

	// 最大公共子序列
	private static int f5(String s1, String s2) {
		if (s1.length() == 0 || s2.length() == 0) {
			return 0;
		}
		if (s1.charAt(0) == s2.charAt(0)) {
			return f5(s1.substring(1), s2.substring(1)) + 1;
		}
		return Math.max(f5(s1.substring(1), s2), f5(s1, s2.substring(1)));
	}

	// 对数组a进行全排列
	private static void f4(int[] a, int k) {
		if (k == a.length - 1) {
			System.out.println(Arrays.toString(a));
		}
		for (int i = k; i < a.length; ++i) {
			int t = a[k];
			a[k] = a[i];
			a[i] = t;
			f4(a, k + 1);
			t = a[k];
			a[k] = a[i];
			a[i] = t;
		}
	}

	// n个球取m个球,不放回的取法
	private static int f3(int n, int m) {
		if (n < m) {
			return 0;
		}
		if (n == m) {
			return 1;
		}
		if (m == 0) {
			return 1;
		}
		return f3(n - 1, m - 1) + f3(n - 1, m);
	}

	// 比较两个串是否相等
	private static boolean f2(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 f2(s1.substring(1), s2.substring(1));
	}

}



    


  

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值