我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。
递归算法:直接或者间接不断反复调用自身来达到解决问题的方法。要求原始问题可以分解为相同问题的子问题。、
需要:
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: 二叉树或者图的问题
这一类问题基本上是递归最常用的场景,在分析树或者图时已经应用很多不再赘述。
转发其它博文:五大经典算法一 递归与分治
我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。
递归算法:直接或者间接不断反复调用自身来达到解决问题的方法。要求原始问题可以分解为相同问题的子问题。、
需要:
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: 二叉树或者图的问题
这一类问题基本上是递归最常用的场景,在分析树或者图时已经应用很多不再赘述。