递归函数
递归函数:在一个函数中再次再次调用该函数自身的行为叫做递归。如下为公式n! =n * (n - 1)!
int fact (int n) {
if(n== 0) return 0;
return fact n*fact(n - 1);
}
if(n == 0) 这个限制条件使递归有结束的出口,如果没有这一个条件,程序将无限制的运行下去。
接下在看斐波那契数列:a₀= 0;a₁=1;an=an-1+an-2(n>1) ;
int fib( int n){
if(n<= 1) return n;
return fib(n - 1) + fib(n -2 );
}
实际上这个函数时间性能很差,通过分析可以发现,在递归过程中 有很多重复的递归过程,如果能将重复的子递归结果存储起来,那么就不需要再次计算。
int memo[MAX_N +1];
int fib(int n){
if(n<=1) return n;
if(memo[n] != 0) return memo[n];
return memo[n]=fib(n-1) + fib(n - 2);
}
栈
栈是支持push和pop的两种操作的数据结构。push是从栈顶端放入数,pop是从栈顶端取出数。这种操作被称为后进先出方式。
C++标准库中,stack :: pop移除最顶端的数据 ; stack::top访问最顶端数据。
#include<iostream>
#include<stack>
#include<cstdio>
using namespace std;
int main(){
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
printf("%d\n", s.top());
s.pop();
printf("%d\n", s.top());
s.pop();
printf("%d\n", s.top());
s.pop();
return 0;
}
队列
队列是一种先进先出的数据结构。
#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
int main(){
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
printf("%d\n", q.front());//访问队尾
q.pop(); //移除队尾
printf("%d\n", q.front());
q.pop();
printf("%d\n", q.front());
q.pop();
return 0;
}
深度优先搜索DFS
深度优先搜索,它从某个状态开始,不断的转移状态直到无法转移,然后回退到前一个状态,继续转移到其他状态,如此不断重复,直到找到最终解。根据深度优先搜索的特点,可以采用递归函数来实现深度优先搜索。
例题:部分和问题:
给定整数 a1 、a2 、…… 、an,判断是否可以从中选出若干数,使它们的和恰好为K。
限制条件:1 ≤ n ≤ 20 ;-108≤ ai ≤ 108;-108≤ k≤108;
输入样例:n=4
a={1,2,4,7}
k=13
输出样例:Yes(13 = 2+4+7)
//给出整数a1~an,判断是否可以从中找出若干,使它们的和为k
#include<iostream>
using namespace std;
int n,k, * a; //n为输入数据个数,k为所要的求和,a*为输入的数据数组
void init(){
cin>>n;
a=new int[n];
for(int i=0;i<n;i++){
cin>>a[i];
}
cin>>k;
}
bool dfs(int i,int sum){
if(i==n) return sum=k;
if(dfs(i+1,sum) ) return true;
if(dfs(i+1,sum+a[i]))return true;
return true;
}
int main(){
init();
if(dfs(0,0)) cout<<"Yes";
else cout<<"No";
return 1;
}
该算法其实很简单,如上,dfs只用了五行代码。
假如我们输入的数组长度为5,它们分别为1、2、3、4、5,求它们任意和的总情况有多少种呢?
我们可以这样分析,可以把他作为概率论的问题,从中任意挑几个数,1出现或不出现有两种情况,2出现或不出现有两种情况,……。说以总情况为2^5种。
说以,我们可以以此来画一个二叉树,每层节点代表一个数,左边为不出现,右边为出现:
宽度优先搜索
宽度休闲搜索与深度优先搜索的不同之处在于搜索顺序不同。宽度优先搜索可以想象为按行搜索,深度优先搜索可以看作按列搜索。
问题
迷宫最短路径问题
给定一个大小为N*M的迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求从起点到终点所需的最小步数。请注意,本题假定从起点一定可以移动到终点。
限制条件:N*M<=100
输入样例: N=10,M=10;
#S######.#
......#..#
.#.##.##.#
.#........
##.##.####
....#....#
.#######.#
....#.....
.####.###.
....#...G#
输出: 22
宽度优先搜索需要使用队列,问题中使用dis[N][M]来保存路径长度;初始化时用充分大的常数INF来初始它。
因为向四个方向移动,说以可以设置一个结构struct Move{int dx,int dy};
移动方向的坐标轴为:
#include <iostream>
#include<queue>
#define MAX_N 100
#define MAX_M 100
using namespace std;
struct Move{
int dx;
int dy;
};
const static int INF=10000000;
typedef pair<int,int> P;
char maze[MAX_N][MAX_M];
int dir[MAX_N][MAX_M+1];
int N,M; //迷宫大小
int sx,sy;//起点坐标
int gx,gy;//终点坐标
Move moved[]={{1,0}, {0,1}, {-1,0}, {0,-1} }; //移动方向:右,下,左,上
int bfs(){
queue<P> que;
for(int i=0;i<N;i++){
for(int j=0;j<M;j++){
dir[i][j]=INF;
}
}
que.push(P(sx,sy));//起点加入队列
dir[sx][sy]=0;//将起点的距离设置为0
while(que.size()!=0){
P p=que.front();
que.pop();
if(p.first==gx && p.second == gy)
break;
//四个方向循环
for(int i=0;i<4;i++){
int nx=p.first+moved[i].dx;
int ny=p.second+moved[i].dy;
//判断该位置是否可到达,且是否已经到过
if(nx>=0 && nx<N && ny>=0 && ny<M && maze[nx][ny] != '#' && dir[nx][ny] ==INF){
que.push(P(nx,ny));
dir[nx][ny]=dir[p.first][p.second]+1;
}
}
}
return dir[gx][gy];
}
void init(){
cin>>N>>M;
for(int i=0;i<N;i++){
for(int j=0;j<M;j++){
cin>> maze[i][j];
if(maze[i][j]=='S') {sx=i,sy=j;}
if(maze[i][j]=='G') {gx=i,gy=j;}
}
}
}
int main(){
init();
cout<<bfs()<<endl;
return 1;
}