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

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

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

需要:

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 二叉树或者图的问题

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

下面再讲分治法








  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值