递归与分治策略-第一节:递归和典型递归问题
Posted 快乐江湖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了递归与分治策略-第一节:递归和典型递归问题相关的知识,希望对你有一定的参考价值。
文章目录
一:LeetCode中有关递归和分治的题目
递归题目:链接
分治题目:链接
二:递归与分治概述
递归与分治:任何可以用计算机求解的问题所需要的计算时间都和其规模有关,如果问题的规模越小,解题所需的计算时间往往也越短,从而也比较容易处理,例如
- 对于 n n n个元素的排序问题:当 n = 1 n=1 n=1时不需要任何计算;当 n = 2 n=2 n=2时,只需要做一次比较即可排好序;当 n = 3 n=3 n=3时则两次。而当 n n n较大时,这个问题就不好处理了
所以要想直接解决一个较大的问题,有时是相当有困难的。所以分治法的设计思想是:将一个难以直接解决的大问题分割成一些规模较小的相同问题,以便各个击破,也即分而治之。如果原问题可以分割为 k k k个子问题, 1 < k ≤ n 1<k\\leq n 1<k≤n,且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题于原问题类型一致而其规模不断缩小,最终使子问题缩小到容易求出其解,由此自然引出递归算法。分治与递归像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效算法
三:递归基本概念
递归:递归算法是指直接或间接调用自身的算法;递归函数是指用函数自身给出定义的函数。在计算机算法设计与分析中,递归技术非常有用。使用递归技术往往可以使函数的定义和算法的描述简洁且易于理解,而且有些数据结构(例如二叉树)由于其本身具有递归特性,所以也特别使用递归的形式来求解
构造递归算法的基本步骤为
- 确定递归边界
- 描述递归关系
- 构造递归函数
四:典型递归问题分析
(1)阶乘
阶乘: n ! = n × ( n − 1 ) × . . . × 1 n!=n×(n-1)×...×1 n!=n×(n−1)×...×1,可递归定义如下
- 第一个式子是递归边界,如果没有边界就有可能会引发无穷递归
- 第二个式子是用较小自变量的函数来表示较大自变量的函数值,相当于问题规模减小
这个问题很简单,递归定义式很容易给出,所以编写代码如下
public class Factorial
public static int factorial_recurse(int n )
if(n == 1)return 1;
return n * factorial_recurse(n-1);
public static void main(String[] args)
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextInt())
int n = scanner.nextInt();
System.out.println(n+"!等于:" + factorial_recurse(n));
当然,递归解法也有其对应的非递归解法
public class Factorial
public static int factorial_none_recurse(int n)
int ret = 1;
for(int i = 1; i <= n; i++)
ret *= i;
return ret;
public static void main(String[] args)
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextInt())
int n = scanner.nextInt();
System.out.println(n+"!等于:" + factorial_none_recurse(n));
(2)Fibonacci数列
Fibonacci数列 :无穷数列【1 1 2 3 5 8 13 21 34 55…】称为Fibonacci数列,在Fibonacci数列中从第三个数字开始,每一个数字都是前两个数字之和,这是一个典型的递归问题,其递归定义式如下
很明显这个问题需要两个递归边界
问题还是比较简单,所以编写代码如下
public class Fibonacci
public static int fibonacci_recurse(int n)
if(n <= 1)return 1;
return fibonacci_recurse(n-1) + fibonacci_recurse(n-2);
public static void main(String[] args)
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextInt())
int n = scanner.nextInt();
System.out.println("第" + n+"个fibonacci数为:" + fibonacci_recurse(n));
fibonacci数列的纯递归写法有很多的重复子问题,所以其时间复杂度极高。所以对于其递归写法,我们可以进行一定优化,引入一个“备忘录”去解决这一部分重复子问题
public class Fibonacci
public static int fibonacci_recurse(int n)
if(n <= 1)return 1;
return fibonacci_recurse(n-1) + fibonacci_recurse(n-2);
public static int fibonacci_recurse_optimize(int[] memo, int n)
if(n <= 1)return 1;
if(memo[n] != 0)return memo[n];//如果备忘录有这个元素那就不用递归了
memo[n] = fibonacci_recurse(n-1) + fibonacci_recurse(n-2);//保存下来
return memo[n];
public static void main(String[] args)
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextInt())
int n = scanner.nextInt();
int[] memo = new int[n+1];//备忘录,元素如果为0表示没有被记录
long recurse_start_time = System.nanoTime();
System.out.println("(递归)第" + n+"个fibonacci数为:" + fibonacci_recurse_optimize(memo, n));
long recurse_end_time = System.nanoTime();
long none_recurse_start_time = System.nanoTime();
System.out.println("(非递归)第" + n+"个fibonacci数为:" + fibonacci_recurse_optimize(memo, n));
long none_recurse_end_time = System.nanoTime();
System.out.println("递归用时:" + (recurse_end_time - recurse_start_time));
System.out.println("非递归用时:" + (none_recurse_end_time - none_recurse_start_time));
System.out.println("----------------------------------------------------");
scanner.close();
(3)排列问题
排列问题:设计一个递归算法生成 n n n个元素的全排列
采用递归解法,可以将规模为 n n n的全排列问题转换为规模为 n − 1 n-1 n−1的全排列问题。所以他可以看作是固定 [ 0 , k ] [0, k] [0,k]位,对 [ k + 1 , n ] [k+1, n] [k+1,n]位进行全排列,当 k + 1 = n k+1=n k+1=n时,递归结束
如下为决策树,每一个子决策树都是一个全排列问题,
代码如下
public class Permutations
public static void swap(int[] test, int k, int i)
int temp = test[k];
test[k] = test[i];
test[i] = temp;
public static void perm(int[] list, int k, int m)
//只有一个元素,递归结束,这一种排列情况可以输出了
if(k == m)
for(int i = 0; i <= m; i++)
System.out.print(list[i]);
System.out.println();
//否则开始递归
else
for(int i = k; i <= m; i++)
swap(list, k, i);
perm(list, k+1, m);
swap(list, k, i);
public static void main(String[] args)
int[] test = new int[]2, 3, 5, 7;
perm(test, 0, 3);
(4)整数划分
整数划分:将正整数 n n n表示成一系列整数之和,即 n = n 1 + n 2 + . . . + n k n=n_1+n_2+...+n_k n=n1+n2+...+nk
在这里,正整数 n n n的这种表示称为正整数 n n n的划分。正整数 n n n的不同划分个数称为正整数 n n n的划分数,记为 p ( n ) p(n) p(n),例如6有如下11种不同的划分方法,所以 p ( 6 ) = 11 p(6)=11 p(6)=11
6
5+1
4+2, 4+1+1
3+3, 3+2+1, 3+1+1+1
2+2+2, 2+2+1+1, 2+1+1+1+1
1+1+1+1+1+1
在正整数 n n n的所有划分中,用 q ( n , m ) q(n,m) q(n,m)表示最大加数 n 1 n_1 n1不大于 m m m的划分个数,正整数 n n n的划分数 p ( n ) = q ( n , n ) p(n)=q(n,n) p(n)=q(n,n)
- 当 n ≥ 1 n\\geq1 n≥1时, q ( n , 1 ) = 1 q(n,1)=1 q(n,1)=1,因为当最大加数最大为1时,任何正整数只有一种划分形式,也即全1
- 由于最大加数不可能大于 n n n,所以当 m ≥ n m \\geq n m≥n时, q ( n , m ) = q ( n , n ) q(n,m)=q(n,n) q(n,m)=q(n,n), q ( 1 , m ) = 1 q(1,m)=1 q(1,m)=1
- 正整数 n n n的划分由 n 1 = n n_1=n n1=递归与分治策略-第二节:分治和典型分治问题