递归与分治

1.递归

1.1递归概念

直接或间接地调用自身的算法称为递归算法。用函数自身给出定义的函数称为递归函数。解决递归问题的关键是找到递归式边界条件

1.2递归相关问题
1.2.1Fibonacci数列的第 n 项

Fibonacci 数列:0,1,1,2,3,5,8,13,21,34,……
递归式:f(n)=f(n-1)+f(n-2) (n>=2);
边界条件:f(1)=1,f(0)=1;

int fibonacci(int n){ if (n <= 1) return 1; return fibonacci(n-1)+fibonacci(n-2); } 
1.2.3汉诺塔移动问题

在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置N个金盘。把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
分析 : 此问题可以采用逆推的方式解决,当A上只有一个的时候,B上
有n-1个,从A到C需要移动一次,B到C则成为了n-1层汉诺塔的问题。可以得出递归式:f(n)=2f(n-1)-1;
边界条件:一个汉诺塔的时候需要移动一次即:f(1)=1;

void move(char from ,char to) { cout<<“Move “<<from<<“to”<<to<<endl; } void hanoi(int n, char first, char second, char third) { if(n==1) move(first,third); else{ hanoi(n-1,first,third,second); move(first,third); hanoi(n-1,second,first,third); } } int main(){ int m; cout<<“the number of diskes:"; cin>>m; cout<<"move “<<m<<“ diskes:\n"; hanoi(m,'A','B','C'); } 
1.2.4集合全排列问题

设计一个递归算法生成n个元素{r1,r2,…,rn}的全排列(n!种)。
设R={r1,r2,…,rn}是要进行排列的n个元素,
Ri=R-{ri}。
集合X中元素的全排列记为perm(X)。
(ri)perm(X)表示在全排列perm(X)的每一个排列前加上前缀得到的排列。
分析: R的全排列可归纳定义如下:
当n=1时,
即为边界条件 perm®=®其中r是集合R中唯一的元素;
当n>1时,perm®由
(r1) perm(R1)
(r2) perm(R2)

(rn) perm(Rn)构成
2个数的全排列

for(int j=2;j<=m;j++){ swap(list[2],list[j]); //从第三位置开始的全排列; swap(list[2],list[j]); } 

3个数的全排列

for(int j=1;j<=m;j++){ swap(list[1],list[j]); //从第二位置开始的全排列; swap(list[1],list[j]); } 

集合全排列

//产生从元素k~m的全排列,作为前k-1个元素的后缀 void Perm(int list[], int k, int m){ //构成了一次全排列,输出结果 if(k==m){ for(int i=0;i<=m;i++) cout<<list[i]<<" "; cout<<endl; } else //在数组list中,产生从元素k~m的全排列 for(int j=k;j<=m;j++){ swap(list[k],list[j]); Perm(list,k+1,m); swap(list[k],list[j]); } } void Perm(int list[], int k, int m){ if(k==m){ for(int i=0;i<=m;i++) cout<<list[i]<<" "; cout<<endl; } else for(int j=k;j<=m;j++) { swap(list[k],list[j]); Perm(list,k+1,m); swap(list[k],list[j]); } } 
1.2.5正整数划分问题

将正整数n表示成一系列正整数之和:n=n1+n2+…+nk,
其中n1≥n2≥…≥nk≥1,k≥1。
正整数n的这种表示称为正整数n的划分。求正整数n的不同划分个数。
例如正整数6有如下11种不同的划分:
6;
5+1;
4+2,4+1+1;
3+3,3+2+1,3+1+1+1;
2+2+2,2+2+1+1,2+1+1+1+1;
1+1+1+1+1+1。
分析:
整数6的划分方法数 =
最大加数等于6的划分方法数 +
最大加数等于5的划分方法数 +
最大加数等于4的划分方法数 +
最大加数等于3的划分方法数 +
最大加数等于2的划分方法数 +
最大加数等于1的划分方法数
进一步缩小
整数6的划分方法数 =
最大加数等于6的划分方法数 +
最大加数小于6的划分方法数
即为
整数6的划分方法数 =
最大加数不大于6的划分方法数
将最大加数n1不大于m的划分个数记作q(n,m)
具体分析

  1. q(n,1)=?,n≥1;
    当最大数n1不大于1时,任何正整数n只有一种划分形式: n=1+1+…+1(共n个)。
  2. q(n,m)=? m≥n
    q(n,n);最大加数n1实际上不能大于n。因此,q(1,m)=1。
  3. q(n,n)=?
    1+q(n,n-1);正整数n的划分由n1=n的划分和n1≤n-1的划分组成。
  4. q(n,m)=? n>m>1
    q(n,m-1)+q(n-m,m),n>m>1;正整数n的最大加数n1不大于m的划分由n1=m的划分和n1≤m-1 的划分组成。
    写成函数为:在这里插入图片描述
#include<iostream> using namespace std; int zshf(int n,int m){ if(n<1) return 0; else if(n==1||m==1) return 1; else if(n<m) return zshf(n,n); else if(n==m) return zshf(n,m-1)+1; return zshf(n,m-1)+zshf(n-m,m); } int main(){ int n; n=zshf(6,3); cout<<n<<endl; return 0; } 

2.分治

2.1分治基本思想

1.分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。
2.对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。
3.将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。
4.分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

2.1分治解决问题特征

1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

2.1分治相关问题

1.二分查找
二分查找需要一个默认的前提,那就是查找的数列是有序的。
二分查找的思路比较简单:
1) 选择一个标志i将集合分为二个子集合
2) 判断标志L(i)是否能与要查找的值des相等,相等则直接返回
3) 否则判断L(i)与des的大小
4) 基于判断的结果决定下步是向左查找还是向右查找
5) 递归记性上面的步骤

def binarySearch(L,e,low,high): if high == low: return L[low] == e mid = (low+high)//2 if L[mid]==e: return True elif L[mid]>e: if low == mid: return False else: return binarySearch(L,e,low, mid-1) else: return binarySearch(L,e,mid+1,high) def search(L,e): result = binarySearch(L,e,0,len(L)-1) print result L = range(10); e = 7; search(L,e); 

2.循环赛日程表问题
设有n=2k个选手要进行循环赛,设计一个满足以下要求的比赛日程表:
1)每个选手必须与其他n-1个选手各赛一次;
2)每个选手一天只能赛一次;
3)循环赛一共进行n-1天。
分析:将日程表依次拷贝,进而合成新的日程表

#include <iostream> using namespace std; #define MAX 100 int a[MAX][MAX]; void Copy(int tox, int toy, int fromx, int fromy, int r){ //一个矩形的拷贝 for(int i=0;i<r;i++) for(int j=0;j<r;j++) a[tox+i][toy+j]=a[fromx+i][fromy+j]; } #include <iostream> using namespace std; #define MAX 100 int a[MAX][MAX]; void Copy(int tox, int toy, int fromx, int fromy, int r){ //一个矩形的拷贝 for(int i=0;i<r;i++) for(int j=0;j<r;j++) a[tox+i][toy+j]=a[fromx+i][fromy+j]; } void Table(int k){ //k为分割次数 int i,r; int n=1; //n为参赛人数 for(i=1;i<=k;i++) n=n*2; for(i=0;i<n;i++) a[0][i]=i+1; //k次拷贝,r矩形区域的宽度 for(r=1;r<n;r=r*2) for(i=0;i<n;i+=2*r) {//1次拷贝,通过多对矩阵的拷贝完成 Copy(r, r+i, 0, i, r); //左上角拷贝到右下角 Copy(r, i, 0, r+i, r); //右上交角拷贝到左下角 } } int main(){ Table(3); for(int i=0;i<=7;i++) { for(int j=0;j<=7;j++) cout<<a[i][j]<<" "; cout<<endl; } return 0; } 

3.快速排序
分析: 首先选第一个数作为分界数据,
将比它小的数据存储在它的左边,比它大的数据存储在它的右边,它存储在左、右两个子集之间。
这样左、右子集就是原问题分解后的独立子问题。
再用同样的方法,继续解决这些子问题,直到每个子集只有一个数据,就完成了全部数据的排序工作。

int select (int left, int right, int k){ if(left>=right) return a[left]; int i=left; int j=right+1; int pivot=a[left]; while(true){ do{ i=i+1;}while(a[i]<pivot); do{ j=j-1;}while(a[j]>pivot); if(i>=j) break; swap(a[i],a[j]); } if(j-left+1==k) return pivot; a[left]=a[j]; a[j]=pivot; if(j-left+1<k) //j-left+1 :左区元素的个数 return select(j+1,right,k-j+left-1); //在右区中找 else return select(left,j-1,k); } 

4.棋盘覆盖问题
在一个2k×2k个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。
在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。
问题:对于给定的特殊棋盘,设计棋盘覆盖方案
例如:
输入:
2 // k,棋盘的边长为2k
0 1 //特殊方格的坐标( 用2k *2k 的矩阵表示一个棋盘)
输出:
2 0 3 3
2 2 1 3
4 1 1 5
4 4 5 5
分析: 当k>0时,将2k×2k棋盘分割为4个2k-1×2k-1 子棋盘(a)所示。特殊方格必位于4个较小子棋盘之一中,
其余3个子棋盘中无特殊方格。
为了将这3个无特殊方格的子棋盘转化为特殊棋盘,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,如 (b)所示,从而将原问题转化为4个较小规模的棋盘覆盖问题。
递归地使用这种分割,直至棋盘简化为棋盘1×1。

void CB(int tr,tc,dr,dc,size){ if (size == 1) return; int t = tile++; // L型骨牌顺序号 s = size/2; // 分割棋盘 // 处理左上角子棋盘 if (dr < tr + s && dc < tc + s) // 特殊方格在此棋盘中 CB(tr, tc, dr, dc, s); else { // 用 t 号L型骨牌覆盖右下角 board[tr + s - 1][tc + s - 1] = t; // 覆盖其余方格 CB(tr, tc, tr+s-1, tc+s-1, s); } // 处理右上角子棋盘 if (dr < tr + s && dc >= tc + s) // 特殊方格在此棋盘中 CB(tr, tc+s, dr, dc, s); else {// 此棋盘中无特殊方格 // 用 t 号L型骨牌覆盖左下角 board[tr + s - 1][tc + s] = t; // 覆盖其余方格 CB(tr,tc+s,tr+s-1,tc+s, s); } // 处理左下角子棋盘 if (dr >= tr + s && dc < tc + s) // 特殊方格在此棋盘中 CB(tr+s, tc, dr, dc, s); else { board[tr + s][tc + s - 1] = t; // 覆盖其余方格 CB(tr+s, tc, tr+s, tc+s-1, s); } // 处理右下角子棋盘 if (dr >= tr + s && dc >= tc + s) // 特殊方格在此棋盘中 CB(tr+s, tc+s, dr, dc, s); else { board[tr + s][tc + s] = t; // 覆盖其余方格 CB(tr+s, tc+s, tr+s, tc+s, s);} } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值