基本算法小结

递归
什么是递归呢?想要知道什么是递归,你就得先知道什么是递归;要想知道什么是递归,你就得先知道什么是递归…ok这就是递归的定义。
递归的要义:
1.明确递归的逻辑,确定递归的公式
2.明确递归的边界,即到哪一步为止
eg:Fibonacci数
F(0)=F(1)=1,F(n)=F(n-1)+F(n-2) n>=2;
显然,递归的公式已经给出,而递归的边界就是n为0和1的时候。ok现在两点要义都已明确,代码手到擒来。

#include<iostream>
using namespace std;
int F(int n)
{
	if(n==0 || n==1)//边界
		return 1;
	return F(n-1) + F(n-2);//公式
}
int main()
{
	int n;
	while(cin >> n)
		cout << F(n) << endl;
	return 0;
}

动态规划
何为动态规划?动态规划就是dp。是类似于数学归纳法,在求解一个比较棘手的大问题A(n)的时候,无法一下子得到最优解,但是我们可以从小问题开始解起,A(0)我们可以容易解出,由A(0)可以容易得到A(1),由A(1)容易得到A(2)以此类推可以解出A(n)。
dp问题的特点:
1.最优子结构:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
2.无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
3.重叠子问题:子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。
dp问题的解题思路:
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态
(1)问题的阶段
(2)每个阶段的状态
(3)从前一个阶段转化到后一个阶段之间的递推关系。
eg:分硬币:
如果我们有面值为2元、3元和5元的硬币若干枚,如何用最少的硬币凑够11元?
按照我们的解题思路,首先确定问题的阶段
因为是要凑够11元,所以共可以分为11个阶段,即d(1),d(2),d(3)…一直到d(n),其中d(n)表示的含义就是n元所需的最少硬币数量。为了方便起见,设个d(0),显然d(0)=0。
接下来便是确定每个阶段的状态
因为面值分别是2,3,5,所以说每个阶段的状态只会有三种变化。举个例子,i=6这个阶段的状态应该怎么求得呢?
首先,d(6)=d(6-2)+1,等于d(4)的数量加一个2元硬币;
第二,d(6)=d(6-3)+1,等于d(3)的数量加一个3元硬币;
第三,d(6)=d(6-5)+1,等于d(1)的数量加一个5元硬币。
因为要求最少的数量,所以d(6)取上述三种状态中的最小值。而我们是从d(0)一步步算到d(6)的,所以不用担心d(6)之前的值我们不知道,我们是知道的。
最后便是确定状态转移方程,根据上述分析,递归关系显而易见:
d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j个硬币的面值;
【这里面值是2,3,5,请自行思考面值是1,2,3,5应该怎么写】

#include<stdio.h>
#include<iostream>
using namespace std;
const int N = 100010;
int vj[N];//硬币的面值 
int d[N];

int main()
{
	int n , m;
	cin >> m;//硬币的种类数 
	for(int i = 0 ; i < m ; i++)
		cin >> vj[i];
	cin >> n;
	d[0] = 0;
	for(int i = 1 ; i <= n ; i++)
	{
		d[i] = N;
		for(int j = 0 ; j < m ; j++)
		{
			if((i-vj[j])>=0)
				d[i] = min(d[i] , d[i-vj[j]]+1);
		}
		if(d[i]==N)
			d[i] = 0;
	}
	cout << d[n] << endl;
	return 0;
 } 

贪心
何为贪心?看这两个字猜都猜得到,贪!在解决大问题的时候,无法一眼看出最优解,于是跟dp类似,也是从初始状态开始,每一步都只考虑当前最优解,一直这样贪到终止状态,也就得到了大问题的最优解。
你可能有疑问了,那跟dp有什么区别呢?当然是有的啊。想想刚才讲的dp,虽说也是从初始状态开始,按照状态转移方程一步步算到终止状态,但是,注意了,它的每一步都是依赖于前一步所得到的结果,在解出所有相关子问题之后才做出选择。而贪心呢,是仅仅考虑在当前状态下如何达到最优,再去贪心解做出这个选择之后的子问题。
贪心的解题思路:
贪就完了!怎么贪心怎么来,先从最优的开始贪,贪完了再从次优的开始贪…就这样一直贪到满足题意。
eg:分硬币
有1元、5元、10元硬币各10个、5个、2个,问用这些硬币支付36元,最少需要多少个硬币?
按照我们的解题思路,就从最优的开始贪,也就是10元开始贪,最多也就贪两个;
再从次优的5元开始贪,最多贪3个;最后贪1元的,1个。

#include<stdio.h>
#include<iostream>
using namespace std;
const int N = 100010;
int a[N];//硬币的面值 
int b[N];//不同面值硬币的数量

int main()
{
	int n , m;
	cin >> m;//硬币的种类数
	for(int i = 0 ; i < m ; i++)
		cin >> a[i];
	for(int i = 0 ; i < m ; i++)
		cin >> b[i];
	cin >> n;//要用硬币支付多少元 
	int res = 0;
	for(int i = m-1 ; i >= 0 ; i--)
	{
		if(n == 0)
			break;
		int k;
		k = n/a[i];
		if(k<=b[i])
		{
			res+=k;
			n-=k*a[i];
		}
		else
		{
			res+=b[i];
			n-=b[i]*a[i];
		}
	}
	cout << res << endl;
	return 0;
 } 

回溯
何为回溯?一猜就知道,就是走不通了就往回走,知错就改知难而退。
在遇到一个问题的时候,你脑袋里的第一想法肯定是直接暴力,毕竟常言道:暴力出奇迹。可是你的暴力实在是太暴力了,拿不到满分,可是你又不会更优秀的算法,那怎么办呢?回溯就是个介于两者之间的不错的选择。
也就是说,在你解决问题的时候,你直接暴力解决,所有能走的路你全都走一遍。但是,你又不是简单的暴力,你是有头脑的暴力。啥意思呢?就是你虽然想走所有能走的路,但是你不傻傻的走,当你走一半发现这条路肯定是走不通的,你就不走剩下的了,你往回走,走到前一个岔路口,换条路走;在走新的路的时候你要是又发现它走不通,就再往回走,在换条路,一直到成功。
回溯的解题步骤:
1.试着走所有的路,每个岔路口中所有的路都走一遍
2.尝试着走,当发现走不通的时候回退一步,换条路走;没发现走不通的时候,就试探着走向下个岔路口。
3.已退到头则结束
eg:分硬币
设你有面值为1到n的硬币各一枚,问若要付款m元,一共有多少种不同的付款方式。
按照我们先前所说的思路,我们暴力的试探着走所有的路,走不通或走到底则回退一步换个路走。
在此题中,假设n=5,m=6,那么我们的思路就是:拿1,拿2,拿3,现在等于6了,是结果之一,也就是走到底了;然后回退,再拿4,那现在手中就是1,2,4,大于6,此路不通,回退;再拿5,现在手中是1,2,5,大于6,此路不通,回退;这时你发现2之后的都无路可走了,那就继续回退,退到手中只有1,然后拿3,再拿4…

#include<stdio.h>
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
vector<int> a;

void dfs(int index , int count , int n)
{
	if(count == 0)
	{
		for(int i = 0 ; i < a.size() ; i++)
		{
			cout << a[i] << " ";
		}
		cout << endl;
	}
	else
	{
		for(int i = index ; i <= count&&i <= n ; i++)
		{
			a.push_back(i);
			dfs(i+1,count-i,n);
			a.pop_back();
		}
	}	
}

int main()
{
	int n , m;
	cin >> n >> m;
	dfs(1,m,n);
	return 0;
 } 

分支限界法
何为分支限界法?这个看名字应该是看不出来。其实就是广度优先搜索,先找每个分支,再依次从每个分支起找它的所有分支。与上一个回溯法恰恰相反,回溯其实就是深搜,分支限界就是广搜。
分支限界的解题思路
首先要确定一个合理明确的限界函数,也就是说超过这个限界的值都是需要丢弃的,不需要继续分支下去的。
明确了之后就开始按照广搜的思想,依次探索每个分支,放到队列中,然后再依次从对头开始,对每个元素再探索分支,直到得到最优解。
eg:走格子
给你一个Wx行Wy列的迷宫,里面有黑白两色的格子,他站在黑色的格子上,可以朝上下左右四个方向走,但是他只能走黑色的格子,不能走白色格子。问他最多能走多少个黑格子。其中’.'表示黑格子,‘#’表示红格子,‘@’是起点,起点必是黑格子)。
按照我们的思路,首先要确定一个限界函数。此题中限界函数就是:不能走红格子,不能走出地图范围,如遇两者则找下个格子。确定完限界函数,就可以从起点开始四个方向广搜了。

#include<bits/stdc++.h>
using namespace std;
const double N = 1e6+10;
const double pi = acos(-1.0);
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
#define ll long long
#define CL(a,b) memset(a,b,sizeof(a))
#define MAXN 200010
int a[MAXN]; 
int b[MAXN];
char room[23][23];
int dir[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
int Wx , Hy , num;//Wx行,Hy列,num统计可走的位置有多少 
#define CHECK(x,y)(x<Wx&&x>=0&&y>=0&&y<Hy)
struct node
{
   int x;
   int y;
};
void BFS(int dx , int dy)
{
   num = 1;
   queue<node> q;
   node start , next;
   start.x = dx;
   start.y = dy;
   q.push(start);
   while(!q.empty())
   {
   	start = q.front();
   	q.pop();
   	for(int i = 0 ; i < 4 ; i++)
   	{
   		next.x = start.x + dir[i][0];
   		next.y = start.y + dir[i][1];
   		if(CHECK(next.x,next.y)&&room[next.x][next.y]=='.')
   		{
   			room[next.x][next.y] = '#';
   			num++;
   			q.push(next);
   		}
   	}
   }
}
int main()
{
   int x , y , dx , dy;
   while(cin >> Wx >> Hy)
   {
   	if(Wx==0&&Hy==0)
   		break;
   	for(y = 0 ; y < Hy ; y++)
   	{
   		for(x = 0 ; x < Wx ; x++)
   		{
   			cin >> room[x][y];
   			if(room[x][y] == '@')
   			{
   				dx = x;
   				dy = y;
   			}
   		}
   	}
   	num = 0;
   	BFS(dx , dy);
   	cout << num << endl;
   }
   return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值