五大经典算法一 递归与分治

我们要讲到分治算法,我觉得有必要说一下递归,他们就像一对孪生兄弟,经常同时应用在算法设计中,并由此产生许多高效的算法。

递归算法:直接或者间接不断反复调用自身来达到解决问题的方法。要求原始问题可以分解为相同问题的子问题。、

需要:

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


     
     
  1. void move(int no,char s,char d)
  2. { cout<< “第”<<no<< “个盘,从”<<s<< “移动到”<<d<< endl;
  3. }
  4. void hanoi(int n,char x,char y,char z){
  5. if(n== 1)move(n,x,z);
  6. else {
  7. hanoi(n -1,x,z,y);
  8. move(n,x,z);
  9. hanoi(n -1,y,x,z);
  10. }
  11. }
验证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


     
     
  1. int splitint(int n,int m){
  2. if(n== 1||m== 1) return 1;
  3. if(n<m) return splitint(n,n);
  4. else if(n==m) return ( 1+splitint(n,m -1));
  5. else return(splitint(n-m,m)+splitint(n,m -1));
  6. }
例题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)


     
     
  1. void permutation(int a[],int n,int current){
  2. if(current==n){
  3. for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
  4. }
  5. else {
  6. for( int i= 1;i<=n;i++)
  7. { int f= 0;
  8. for( int j= 0;j<current;j++)
  9. if(a[j]==i)f= 1; //a[0]-a[current-1]是已经排好的
  10. if(f== 0){a[current]=i;permutation(a,n,current+ 1);}
  11. }
  12. }
  13. }

具体分析:


当第一次进入循环时,i=1,最后输出以1开始的两个序列123 132

上述排列程序只使用任意两个元素均不相同的序列,若有一个序列P,并且P中含有相同的元素,则根据上面程序进行修改

由于数组P可能有重复元素故需要注意两点:

1 为避免排列序列重复,首元素应避免一样;

2 非首元素重复允许


     
     
  1. void permutation1(int a[],int n,int p[],int current){
  2. if(current==n)
  3. { for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
  4. }
  5. else {
  6. for( int i= 0;i<n;i++)
  7. if(!i||p[i]!=p[i -1]) //防止重复
  8. {
  9. int f= 0; int num= 0;
  10. for( int j= 0;j<n;j++) if(p[i]==p[j])num++;
  11. for( int j= 0;j<current;j++)
  12. if(a[j]==p[i])f++;;
  13. if(f<num){a[current]=p[i];permutation1(a,n,p,current+ 1);}
  14. }
  15. }
  16. }

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]]



     
     
  1. class Solution {
  2. public:
  3. void helper_combination13(int k,int start,int target,vector<int>&item,vector< vector<int> >&res){
  4. if(target< 0||(target!= 0&&item.size()==k)) return;
  5. if(item.size()==k&&target== 0){res.push_back(item); return;}
  6. for( int i=start;i< 10;i++)
  7. {
  8. item.push_back(i);
  9. helper_combination13(k,i+ 1,target-i,item,res);
  10. item.pop_back();
  11. }
  12. }
  13. vector< vector< int> > combinationSum3( int k, int n){
  14. vector< vector< int> >res;
  15. vector< int> item;
  16. helper_combination13(k, 1,n,item,res);
  17. return res;
  18. }
  19. };

例题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


  
  
  1. void move(int no,char s,char d)
  2. { cout<< “第”<<no<< “个盘,从”<<s<< “移动到”<<d<< endl;
  3. }
  4. void hanoi(int n,char x,char y,char z){
  5. if(n== 1)move(n,x,z);
  6. else {
  7. hanoi(n -1,x,z,y);
  8. move(n,x,z);
  9. hanoi(n -1,y,x,z);
  10. }
  11. }
验证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


  
  
  1. int splitint(int n,int m){
  2. if(n== 1||m== 1) return 1;
  3. if(n<m) return splitint(n,n);
  4. else if(n==m) return ( 1+splitint(n,m -1));
  5. else return(splitint(n-m,m)+splitint(n,m -1));
  6. }
例题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)


  
  
  1. void permutation(int a[],int n,int current){
  2. if(current==n){
  3. for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
  4. }
  5. else {
  6. for( int i= 1;i<=n;i++)
  7. { int f= 0;
  8. for( int j= 0;j<current;j++)
  9. if(a[j]==i)f= 1; //a[0]-a[current-1]是已经排好的
  10. if(f== 0){a[current]=i;permutation(a,n,current+ 1);}
  11. }
  12. }
  13. }

具体分析:


当第一次进入循环时,i=1,最后输出以1开始的两个序列123 132

上述排列程序只使用任意两个元素均不相同的序列,若有一个序列P,并且P中含有相同的元素,则根据上面程序进行修改

由于数组P可能有重复元素故需要注意两点:

1 为避免排列序列重复,首元素应避免一样;

2 非首元素重复允许


  
  
  1. void permutation1(int a[],int n,int p[],int current){
  2. if(current==n)
  3. { for( int i= 0;i<n;i++) cout<<a[i]<< " "; cout<< endl;
  4. }
  5. else {
  6. for( int i= 0;i<n;i++)
  7. if(!i||p[i]!=p[i -1]) //防止重复
  8. {
  9. int f= 0; int num= 0;
  10. for( int j= 0;j<n;j++) if(p[i]==p[j])num++;
  11. for( int j= 0;j<current;j++)
  12. if(a[j]==p[i])f++;;
  13. if(f<num){a[current]=p[i];permutation1(a,n,p,current+ 1);}
  14. }
  15. }
  16. }

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]]



  
  
  1. class Solution {
  2. public:
  3. void helper_combination13(int k,int start,int target,vector<int>&item,vector< vector<int> >&res){
  4. if(target< 0||(target!= 0&&item.size()==k)) return;
  5. if(item.size()==k&&target== 0){res.push_back(item); return;}
  6. for( int i=start;i< 10;i++)
  7. {
  8. item.push_back(i);
  9. helper_combination13(k,i+ 1,target-i,item,res);
  10. item.pop_back();
  11. }
  12. }
  13. vector< vector< int> > combinationSum3( int k, int n){
  14. vector< vector< int> >res;
  15. vector< int> item;
  16. helper_combination13(k, 1,n,item,res);
  17. return res;
  18. }
  19. };

例题4: 二叉树或者图的问题

这一类问题基本上是递归最常用的场景,在分析树或者图时已经应用很多不再赘述。


猜你喜欢

转载自blog.csdn.net/yangxingpa/article/details/81517244