我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。
递归算法:直接或者间接不断反复调用自身来达到解决问题的方法。要求原始问题可以分解为相同问题的子问题。、
需要:
1 递归边界 2 自身调用
特点分析:
递归思路简单清晰,如果分析出将很快得到结果;递归将多次调用,使用到堆栈,算法效率低,费时费内存。
常用场景:阶乘,斐波纳契数列、汉诺塔问题,整数划分,枚举排列及二叉树,图的搜索相关问题。
例题1: Hanoi问题
有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至B杆:每次只能移动一个圆盘;大盘不能叠在小盘上面。问:如何移?最少要移动多少次?
设hanoi(n,x,y,z)表示将n个圆盘从x通过y移动到z上则有递归模型:
hanoi:move(n,x,z) when n==1
hanoi:hanoi(n-1,x,z,y);move(1,x,z);hanoi(n-1,y,x,z) when n>1
- void move(int no,char s,char d)
- { cout<< “第”<<no<< “个盘,从”<<s<< “移动到”<<d<< endl;
- }
- void hanoi(int n,char x,char y,char z){
- if(n== 1)move(n,x,z);
- else {
- hanoi(n -1,x,z,y);
- move(n,x,z);
- hanoi(n -1,y,x,z);
- }
- }
验证hanoi(3,’a’,’c’,’b’):
第1个盘,从a移动到b
第2个盘,从a移动到c
第1个盘,从b移动到c
第3个盘,从a移动到b
第1个盘,从c移动到a
第2个盘,从c移动到b
第1个盘,从a移动到b
例题2: 整数划分问题
整数划分问题是算法中的一个经典命题之一,有关这个问题的讲述在讲解到递归时基本都将涉及。所谓整数划分,是指把一个正整数n写成如下形式:
n=m1+m2+…+mi,其中1<=mi<=n,则称{m1,m2,…,mi}为n的一个划分。如果{m1,m2,..,mi}最大值不超过m,即max(m1,m2,…,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m)。例如n=4时,有4},{3,1},{2,2},{2,1,1},{1,1,1,1}这样的5个划分则f(n,4)=5。下面将用递归法求f(n,m):
a)当n==1时,不论m何值,都只有一种划分{1};
b)当m==1时,不论n何值,都只有一种划分{1,1,…,1};
c)当n==m时,
分划分中包含n时只有一种;
当划分中不包含n即最大值小于m时有f(n,m-1)种;
故f(n,n)=1+f(n,n-1);
d)当n<m时,类似f(n,n)
e)当n>m,这是最一般情况,
若划分中包含m,即{{x1,x2,…,xi},m},x1+x2+…+xi=n-m,则f(n,m)=f(n-m,m);
若划分中不包含m,即划分中值都比m小,f(n,m)=f(n,m-1)
故总数 f(n, m) = f(n-m, m)+f(n,m-1)
则递归模型:
f(n,m)=1 when n==1 or m==01
f(n,m)=f(n,n) when m>n
f(n,m)=1+f(n,m-1) when m==n
f(n,m)=f(n-m,m)+f(n,m-1) when m<n
- int splitint(int n,int m){
- if(n== 1||m== 1) return 1;
- if(n<m) return splitint(n,n);
- else if(n==m) return ( 1+splitint(n,m -1));
- else return(splitint(n-m,m)+splitint(n,m -1));
- }
例题3: 枚举排列问题
输出一个数的枚举排列或者一个序列的全排列,递归生成是一种很方便的做法。
先说枚举排列,输入一个整数n,按照大小输出1~n的所有排列如n=3时所有·排列结果:123,132,213,231,312,321
先输出以1开头的排列
再输出以2开头的排列
。。。
最后输出以n开头的排列
故这需要一个1-n的循环即可,循环内部就是一个排列生成过程。
以i开头的排列的特点是,第一位是i,后面是(1,2,…,i-1,i+1,..,n)的排列,按照定义(1,2,…,i-1,i+1,..,n)也必须按照大小顺序排列,故可以采用递归。设递归函数为permutation(int a[],int n,int cur),其递归模型:
输出已排好序列a when n==cur,cur为当前需要确定的元素位置
for 从小到达每个元素v
permutation(a+v,n,cur+1)
- void permutation(int a[],int n,int current){
- if(current==n){
- for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
- }
- else {
- for( int i= 1;i<=n;i++)
- { int f= 0;
- for( int j= 0;j<current;j++)
- if(a[j]==i)f= 1; //a[0]-a[current-1]是已经排好的
- if(f== 0){a[current]=i;permutation(a,n,current+ 1);}
- }
- }
- }
具体分析:
当第一次进入循环时,i=1,最后输出以1开始的两个序列123 132
上述排列程序只使用任意两个元素均不相同的序列,若有一个序列P,并且P中含有相同的元素,则根据上面程序进行修改
由于数组P可能有重复元素故需要注意两点:
1 为避免排列序列重复,首元素应避免一样;
2 非首元素重复允许
- void permutation1(int a[],int n,int p[],int current){
- if(current==n)
- { for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
- }
- else {
- for( int i= 0;i<n;i++)
- if(!i||p[i]!=p[i -1]) //防止重复
- {
- int f= 0; int num= 0;
- for( int j= 0;j<n;j++) if(p[i]==p[j])num++;
- for( int j= 0;j<current;j++)
- if(a[j]==p[i])f++;;
- if(f<num){a[current]=p[i];permutation1(a,n,p,current+ 1);}
- }
- }
- }
216题也类似:
Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
Example 1:
Input: k = 3, n = 7
Output:
[[1,2,4]]
Example 2:
Input: k = 3, n = 9
Output:
[[1,2,6], [1,3,5], [2,3,4]]
- class Solution {
- public:
- void helper_combination13(int k,int start,int target,vector<int>&item,vector< vector<int> >&res){
- if(target< 0||(target!= 0&&item.size()==k)) return;
- if(item.size()==k&&target== 0){res.push_back(item); return;}
- for( int i=start;i< 10;i++)
- {
- item.push_back(i);
- helper_combination13(k,i+ 1,target-i,item,res);
- item.pop_back();
- }
- }
- vector< vector< int> > combinationSum3( int k, int n){
- vector< vector< int> >res;
- vector< int> item;
- helper_combination13(k, 1,n,item,res);
- return res;
- }
- };
例题4: 二叉树或者图的问题
这一类问题基本上是递归最常用的场景,在分析树或者图时已经应用很多不再赘述。
转发其它博文:五大经典算法一 递归与分治