一、分治
分治其实就是将一个大问题分解为一个个小问题,然后再将每个子问题求出结果,最后将子问题结果合并为原问题的解。分治问题常常用递归方式来解决
二、递归
递归一定要记住递归体与临界点,下面为递归的两个例子
1.输出1…n的全排列
思路:可以分解成输出1为开头的全排列,2开头的全排列,3开头的全排列……的子问题来解决,然后利用一个hashtable数组,记录这个数字是否以及存在数组P中了,如果没有的话就加入,然后hashtable变为true,最后递归结束就把hashtable变成false,下面例子为生成n=3的全排列.从p[0]开始填。
import java.util.*;
public class Main(){
private int p=new int [4];
private boolean hashtable= new boolean [4];
public void init(){
for(int i=0;i<4;i++){
p[i]=0;
hashtable[i]=false;
}
}
public void genertateP(int index){
if(index==4){
for(int i=0;i<4;i++){
System.out.print(p[i]+" ");
return ;
}
for(int i=1;i<4;i++){
if(hashtable[i]==false){
p[index]=i;
hashtable[i]=true;
genertateP(index+1);
hashtable[index]=false;
}
}
public static void main (String []args){
genertatep(0);从p[0]开始填
}
2.n皇后问题
利用递归回溯法:就是对于每一列,进行行数的全排列,然后如果不在对角线上的就成立输出。
import java.util.*;
import java.Math.*;
public class Main(){
private int []P=new int [100];//用来存每一列的行位置,下标为列,元素为行数
private boolean []hashtable=new boolean [100];//用来判断数字有无用过
private int count=0;
private int n=0;//一共有n行
public Main(int index){
this.n=n;
}
public class generateP(int index){
if(index==n+1){
count++;
return;
}
for(int i=1;i<n;i++){
if(hashtable[i]==false){
boolean flag=false;
for(int j=1;j<index;i++){//检测之前的每一列的行数是否符合皇后布局
if(Math.abs(j-index)==Math.abs(p[j]-i){
flag=true;
}
}
if (flag==false){
hashtable[i]==true;
p[index]=i;
generateP(index+1);
hashtable[i]=false;
}
}
}
public static void main(String []args){
Main a=new Main(100);
for(int i=0;i<100;i++){
hashtable[i]=false;
}
generateP(1);
System.out.print(count);
}
三、记忆化递归
1.解释
记忆化递归其实就是将每次递归的结果记忆下来,这样当在某一次递归时,如果要用到以前的递归结果,那么就可以直接调用。而动态规划就是用来解决那些具有相同步骤事。当需要用到动态规划的时候,可以直接用记忆化递归来代替动态规划。
https://blog.csdn.net/su_bao/article/details/81181975
2.记忆化递归的求解模板
红色区域是边界区域。蓝色区域是为了求解子问题。绿色区域是当问题求结果,则直接返回。即:
①第一步:简历一个数组用来存递归的结果同时确定递归出口临界值
②第二步:当if(!m[i]),即当没有递归过这次时,就开始执行原来递归形式的递归体,然后将执行结果,存到数组中
③第三步:如果以前执行过,就直接返回。return数组的值
3.例题
(1)Fibonacci数列
https://zhuanlan.zhihu.com/p/73579773
(2)洛谷
解决:
C++
#include<iostream>
using namespace std;
int a[1005];
int count(int n)
{
if(n==1)return 0;
long long ans=0;
if(a[n]==0)//如果没有计算过这个数,计算并储存在a[n]中
{
for(int i=1;i<n;i++)
if(i*2<n||i*2==n)//其实就相当于for(int i=1;2*i<=n;i++)
ans+=count(i)+1;//加一的原因是因为,题目中数量的计算是包括它本身的
return a[n]=ans;
}
else//计算过这个数的话直接返回这个数对应值
return a[n];
}
int main ()
{
int n;
cin>>n;
cout<<count(n)+1;
return 0;
}
Java
import java.util.*;
public class Main(){
private int m=new int [1001];
private int n=0;
private count=0;
Main(int index)
{
this.n=index;
}
public int generate(int index){
if(index==0){
return 0;
}
if(index==1){
return 0;
}
if(!m[index]){
int t=0;
for(int i=1;i<=index/2;i++){
t=t+generate(i)+1;//易错点:要加上1,因为还有他自己本身也算一个
}
m[index]=t;
}
return m[index];
}
public int print(int n){
return m[n];
}
public static void main(){
Main a=new Main(6);
a.generate(6);
System.out.print(a.print(6));
}
}
四、快速幂
快速幂就是用来快速计算一个数的m次幂的方法,问题引入如下:
给定一个正整数a、b、m,其中a<10^ 9 ,b<10^ 9 , 1<m<10^ 9 ,求a^ b%m
以后这种类型的解决思路:
采用递归的方法
①当b是奇数时,有a^ b=a*a^(b-1)
②当b为偶数时,有a^ b=a^ ( b/2) *a^(b/2)
import java.util.*;
public class Main(){
Main();
public long binaryPow(long a,long b,long m){
if(b==0) return 1;
if(b%2==1) return a*binaryPow(a,b-1,m);
if(b%2==0) {
long mul=binaryPow(a,b/2,m);
return mul*mul;
}
}
注意:
①如果初始值 a有可能大于m时,那么进入函数前就可以先让a对m取模,根据数学性质可以知道,是不变的(aa)%m=a%ma%m
②如果一开始m=1的话,那么无论多少取模都为0,所以在进入函数的时候,可以设一个分支点。