递归与分治
递归的概念:直接或间接调用自身的算法称为递归算法;用函数自身定义的函数称为递归函数。
分治的概念:将一个规模为n的问题分解为若干个规模为子k的问题;每个子问题相互独立且与原问题相同;将各个子问题的解合并得到原问题解。
例题:
1.排列问题
问题描述:设R={r1,r2,…rn}是要进行排列的n个元素,Ri=R-{ri}。集合X中元素的全排列记为Perm(X)。(ri)Perm(X)表示在全排列Perm(X)的每个排列上加上前缀ri得到的排列。R的全排列可归纳定义如下:
当n=1时,Perm®=®,其中r是集合R中唯一元素。
当n>1是时,Perm®由(r1)Perm(R1),(r2)Perm(R2)…(rn)Perm(Rn)构成。
算法思想:
边界条件:确定最后一个元素时可确定此时排列顺序
递归:确定第一个元素后,逐个划分然后递归调用依次确定第二第三直到最后一个元素。
void Perm(int list[],int p,int r){ //产生list[p:r]的所有全排列
if(p=r) //只剩下一个元素时,输出排列序列
{
for(i=0;i<r;i++){
printf("%d",list[i]);
}
}
else{
for(int i=p;i<r;i++){ //还有多个元素待排列,递归产生排列
swap(list[p],list[i]); //逐个划分,确定第一个元素
perm(list,p+1;r); //对还未确定的list[p+1:r]进行全排列
swap(list[p],list[i]); //逐个划分确定第一个元素
}
}
}
void swap(int a,int b){ //交换
int temp=a;
a=b;
b=temp;
}
2.hanoi塔问题
算法思想:当只有一个圆盘可直接将其移动到b上,n>1时需要塔座c作为辅助塔座。此时要将余下n-1个圆盘按照规则从塔座a移动到c上,让后将剩下的最大的圆盘移动到b上,最后再设法将余下n-1个圆盘从塔座c移动到b上。由此可见n个圆盘移动问题就可以分解成二次n-1的问题
边界条件:只剩最后一个圆盘时,直接将其时移动到塔座b上
递归:依次将还未移动的最大圆盘移动到塔座b上,对余下未移动到b上的圆盘进行递归,直到圆盘全部移动到塔座b上
void hanoi(int n,int a,int b,int c){ //将n个与圆盘以c未中介,从a移动到b
if(n==1) //当只有一个圆盘时,直接将圆盘从a移动到b
move(n,a,b);
else{
hanoi(n-1,a,c,b); //将n-1个元素,以b为中介从a移动到c
move(n,a,b); //将第n个元素从a移动到b
hanoi(n-1,c,b,a); //将剩余n-1个元素以a为中介从c移动到b
}
}
3.线性时间选择/规划
找出n个元素中第k小的元素
int RandomSlect(int a[],int p,int r,int k){
if(p==r){ //当递归到只剩下一个元素时,这个元素即为第k小元素
return a[p];
}
int i=partition(a,q,r); //根据枢轴元素,将a[]随机划分为小于枢轴元素和大于枢轴元素二部分
j=i-p+1; //枢轴元素为第j小元素
if(k==j)
return a[i];
else if(k<j)
return RadomSelet(a,p,i-1,k); //当要k<j时,左半找第k小元素
else
return RandomSelet(a,i+1,r,k-j); //k>j时,原问题可转化为在右半部分找第k-j小元素
}
int partition(int a[],int low,int high){
int pivot=a[low]; //选取当前表中第一个元素为枢轴元素,对表进行划分
while(low<high){ //跳出循环条件
while(low<high&&a[high]>pivot){
high--;
}
a[low]=a[high]; //将比枢轴元素小的移动到左端
while(low<high&&a[low]<pivot){
low++;
}
a[high]=a[low]; //将比枢轴元素大的移动到右端
}
a[low]=pivot; //直到到low=high时,此时枢轴元素为最终存放位置low/high
return low; //返回存放位置
}
4.整数划分
问题描述:将正整数n表示成一系列正整数之和。
如p(6)=11 6有11种划分方式。
6
5+1
4+2,4+1+1
3+3,3+2+1,3+1+1
2+2+2,2+2+1+1,2+1+1+1+1
1+1+1+1+1+1
算法思想:可引入最大加数,在正整数n的所有划分中,将最大加数不超过m的划分个数记作q(n,m)可以建立如下关系式
n=1,m=1 q(n,m) =1 当n为1时或者最大加数为1时只能有一个排列顺序
n<m q(n,m) = q(n,n) 当n<m时最大加数可变为n
n=m q(n,m)=1+q(n,n-1) 当n=m时,排列个数为1+q(n,n-1)
n>m q(n,m)=q(n,m-1)+q(n-m,m) n<m时的划分
int q(int n,int m){ //对n进行划分,求排列个数
if(n<1||m<1)
return 0;
else if(n=1||m=1)
return 1;
else if(n<m)
return q(n,n);
else if(n=m)
return 1+q(n,m-1);
else
return q(n,m-1)+q(n-m.m);
}
5.大整数乘法
问题描述:
算法思想:
6.棋盘覆盖
问题描述:
算法思想:
将一个2^k * 2^k的问题分割为4个k-1的问题,其余3个无特殊方格的子棋盘,将其转化为特殊棋盘,再用一个L型骨牌覆盖这3个较小棋盘的会合处。这三个子棋盘上被L型骨牌覆盖的方格就成为特殊方格,从而将原问题转化为4个较小的棋盘覆盖问题。递归的使用这种分割,直到棋盘简化为1*1棋盘
void ChessBoard(int tr,int tc,int dr,int dc,int size){ //tr,tc棋盘左上角行列号,dr,dc特殊方格的行列号;size 棋盘尺寸
if(size==1){ //当简化为棋盘1*1时覆盖完毕
return;
}
int t=L++; //t为L型骨牌号
int s=size/2;
//覆盖左上角棋盘
if(dr<tr+s&&dc<tc+s) //特殊方格在此棋盘中
ChessBoard(tr,tc,dr,dc,s); //棋盘覆盖
else{
Board[tr+s-1][tc+s-1]=t; //用t覆盖特殊方格
ChessBoard(tr,tc,tr+s-1,tc+s-1,s); //棋盘覆盖
}
//右上角
if(dr<tr+s&&dc>=tc+s){
ChessBoard(tr,tc,dr,dc,s);
}
else{
Board[tr+s-1][tc+s]=t;
ChessBoard(tr,tc,tr+s-1,tc+s-1,s);
}
//左下角
if(dr>=tr+s&&dc<tr+s)
ChessBoard(tr,tc,dr,dc,s);
else{
Board[tr+s-1][tc+s-1]=t;
ChessBoard(tr,tc,tr+s-1,tc+s-1,s);
}
//右下角
if(dr>=tr+s&&tc>=dc+s){
ChessBoard(tr,tc,dr,dc,s);
}
else{
Board[tr+s][tc+s]=t;
ChessBoard(tr,tc,tr+s,tc+s,s);
}
}
7. 最接近点对
问题描述:
算法思想:
bool Cpair(S,d){
n=|s|; //n作为点集s的个数
if(n<2){
d=INT_MAX;
return false;
}
m=midst(s); //s作为各点坐标的中位数
S1=lefts(s,left,m); //s1为中位数左边的点集
S2=rights(s,m,right); //s2为中位数右边的点集
Cpair(S1,d1);
Cpair(S2,d2);
p=max(S1);
q=min(S2);
d=min(d1,d2,q-p);
return true;
}
8.Strassen矩阵乘法
9.牛牛问题(补充)
问题描述:有一头母牛,假设每年年初生一头小牛,每头母牛从第四年开始,每年年初也生一头小牛,请编写程序实现第n年共有多少头母牛。
int GetNiuniu(int n){
int a,b,c,d;
int count=0;
if(n<=4){
count=n;
printf("%d",count);
}
else{
int i=n-4;
a=b=c=d=1;
for(i=0;i<n;i++){
d=c+d;
c=b;
b=a;
a=d;
}
count=a+b+c+d;
printf("%d",count)
}
return 0;
}