贪心,深度优先(9)

一、贪心算法

1.1 简介

贪心本质:一个贪心算法总是做出当前最好的选择,也就是说,它期望通过局部优先选择从而得到全局最优解决方案。

就如《算法导论》里所说的:“人要活在当下” “看清楚眼前”……贪心算法正是“活在当下,看清楚眼前”的办法。从问题的初始解开始,一步一歩地做出当前最好的选择,逐步逼近问题的目标,尽可能地得到最优解,即使达不到最优解,也可以得到最优解的近似解
贪心算法在解决问题的策略上“目光短浅”,只根据当前已有的信息就做出选择, 一旦做出了选择,不管有什么样的结果,这个选择都不会改变。因此,贪心算法在实际中得到大量的应用。在贪心算法中,我们需要注意以下几个问题。

(1)没有后悔药。一旦做出选择,不可以反悔。
(2)有可能得到的不是最优解,而是最优解的近似解。
(3)选择什么样的贪心策略,直接决定算法的好坏。

有木有觉得贪心算法有点像 冒泡排序?

其实,这不是 贪心算法像冒泡排序 ,而是冒泡排序使用了贪心算法,它的贪心策略就是每一次从剩下的序列中选一个最大的数,把这些选出来的数放 在一起,就得到了从大到小的排序结果,如图所示:
贪心,深度优先(9)_第1张图片

1.2 例题&代码

例1

在N行M列的正整数矩阵中,要求从每行中选出一个数,使得选出的N个数的和最大。

例2

在一个N*M的方格阵中,每一格子赋予一个数(即权值),规定每次移动时只能向上或向右。现找出一条路径,使其从左下角至右上角所经过的权值之和最大

分析1

本题可以用贪心算法求解。选N次,每次选出相应行中的最大值即可。

分析2

我们以2*3的矩阵为例:

3  4  6
1  2  10

若按贪心算法求解:所得路径为1->3->4->6
若按动态规划求解,所得路径为1->2->10->6
所以这里贪心算法求解,得到的并不是问题的最优解,而是近似解

例题1
人民币找零

在50元和100元面值的人民币出现之前,人民币仅由10元、5元、2元、1元、5角、2角、1角和5分、2分、1分面值的钱币组成。现给定一个10000元以内,精确到1分的人民币数值,请你用最少的钱币张数,找出相应的钱数。

样例输入

17.32

样例输出

6

分析

给定钱币数为17元3角2分,则输入数据为17.32,则相应的输出为6(钱币总张数,此数应最小)
10 1 (以下为各种面值钱币的张数,不需要使用的钱币不必列出)
5 1
2 1
0.2 1
0.1 1
0.02 1

例题2
均分纸牌

有 n 堆纸牌,编号分别为 1,2,…, n。每堆上有若干张,但纸牌总数必为 n 的倍数可以在任一堆上取若干张纸牌,然后移动。移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 n 的堆上取的纸牌,只能移到编号为 n-1 的堆上;其他堆上取纸牌,可以移到相邻左边或右边的堆上。现在要求找出一种移动方法,用最少的移动次数使每堆纸牌数都一样多。

输入格式

n(n 堆纸牌,1 <= n <= 100)

输出格式

所有堆均达到相等时的最少移动次数。

样例输入

4

样例输出

9 8 17 6

不给分析

代码
#include
using namespace std;
int n,ave=0,step=0,a[100];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
{
	cin>>a[i];
	ave+=a[i];
}
ave/=n;
for(int i=1;i<=n;i++)
	a[i]-=ave;
int i=1;
for(;i<n;i++,step++)
{
	while(i<n&&a[i]==0) i++;
	if(i>=n) break;
	a[i+1]+=a[i];
	a[i]=0;
}
cout<<step<<endl;
return 0;
}
例题3
删数问题

键盘输入一个高精度的正整数n(≤240位),去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小。

输入格式

n 代表一个高精度正整数
s 去掉数字的个数

输出格式

最后剩下的最小数

样例输入

178543
4

样例输出

13

分析

由于正整数n的数位为240位,所以很自然地采用字符串类型存贮n。
是不是删除最大的那s个数字呢? 当然不是,大家很容易举出一些反例。比如:178593

代码
#include
using namespace std;
string n;
int s;
int main()
{
	cin>>n>>s;
	while(s>0)
	{
		int i=0;
		while(i<n.length()-1&& n[i]<=n[i+1]) i=i+1;
		n.erase(i,1);
		s=s-1;
	}
    int i=0; 
	while(n[i]=='0') i++;
	while(i<=n.length()) 
	{
	cout<<n[i];
	i++;
	}
	return 0;
}

选择区间不相交问题

给定n个开区间(ai,bi),选择尽量多个区间,使得这些区间两两没有公共点。
策略:首先,按照结束时间b1<=b2<=….<=bn的顺序排序,依次考虑各个活动 ,如果没有和已经选择的活动冲突就选;否则,就不选。

【正确性】
假设bj

例题4
活动安排

设有n个活动集合E={1,2,3……n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动 i 都有一个使用该资源的起始时间si和一个结束时间fi,且si

输入格式

第一行一个整数n(n<=1000);接下来的n行,每行两个整数s i和f i

输出格式

输出尽可能多的互相兼容的活动个数。

样例输入

4
1 3
4 6
2 5
1 7

样例输出

2

代码
#include
using namespace std;
struct node
{
	int st,ed;
}a[1005];
bool cmp(node x,node y)
{
	return x.ed<y.ed;
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	 cin>>a[i].st>>a[i].ed;
	sort(a+1,a+n+1,cmp); 
	int t=a[1].ed;
	int ans=1;
	for(int i=2;i<=n;i++)
	if(a[i].st>=t)
	{
		ans++;
		t=a[i].ed;
	}
	cout<<ans;
	return 0;
}

二、深度优先搜索

2.1 简介

搜索是什么?

1.复杂问题的直观想法
2.非常朴素的写题思路
3.暴力骗分的不二法宝
4.按照问题决策发展顺序,遍历问题所有状态空间的求解方法

搜索的类型

1.枚举
2.递归
3.回溯
4.深度优先
5.广度优先
6.记忆化

搜索回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。

回溯是搜索算法中的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。

在我们平常的 NOIP (CSP-J/S) 赛中,如果我们真有不会做的题时,我们就可以用搜索来做;**做不到AC (Accepted) ,就把暴力分得到。

2.2 例题

跳马问题:

有一只中国象棋中的“马”,在半张棋盘的左下角出发,向右上角跳去。规定只许向右跳(可上,可下,但不允许向左跳),如下图就是一种跳法。请编程求从起点A到终点B共有多少种不同跳法。

2.3 知识点

深度优先搜索算法是搜索中的一种控制策略,但与枚举法不同的是,它是从初始状态出发,运用题目给出的条件、规则,按照深度优先的顺序扩展所有可能情况,当所有可能情况都探索过且都无法到达目标的时候,再回退到上一个出发点,继续探索另一个可能情况,这种“不撞南墙不回头”寻找目标的方法也称为“回溯法”,所以,深度优先搜索也是一种“盲目”搜索。

2.4 例题&代码

拯救瑞恩

有一天,大兵瑞恩在穿越亚马逊丛林时迷路了,他的好友凯西得知后准备去拯救瑞恩,凯西搞到了亚马逊丛林的地图,决定快速去拯救瑞恩……


亚马逊丛林由n行m列的单元格组成(n和m都小于等于50),每个单元格要么是空地,要么是大树,你的任务是帮助凯西找到一条从起点到瑞恩所在位置的最短路径,注意大树是不能通过的,当然凯西也不能走到丛林外边。


首先,我们可以用一个二维数组来存储这个丛林,刚开始的时候,凯西处于丛林的入口处(1,1),瑞恩在(p,q)。其实,就是找一条从(1,1)到(p,q)的最短路径。如果你是凯西,你该怎么办呢?凯西最开始在(1,1),他只能往右走或者往下走,但是凯西究竟是该往下走还是往右走呢?他只能一个一个地去尝试。我们可以先让凯西往右走,直到走不通的时候再回到这里,再去尝试另外一个方向。我们这里规定一个顺序,按照顺时针方向来尝试(即按照右、下、左、上的顺序去尝试)。


我们先来看看凯西一步之内可以到达的点有哪些?只有(1,2)和(2,1)。根据刚才的策略,我们先往右边走,凯西来到了(1,2)这个点,来到(1,2)这个点之后凯西又能到达哪些新的点呢?只有(2,2)这个点,因为(1,3)这个大树是无法到达的,(1,1)是刚才来的路径已经走过的点,也不能走,所以只能到(2,2)这个点,但是瑞恩并不在这个点上,所以凯西还得继续往下走,直至无路可走或者找到瑞恩为止。请注意,此处不是一找到瑞恩就结束了,因为刚才只尝试了一条路,而这条路并不一定是最短的。刚才很多地方在选择方向的时候都有多种选择,因此我们需要返回到这些地方继续尝试往别的方向走,直到把所有可能都尝试一遍,最后输出最短的一条路径。

图片在我上传的文件里
代码
#include
using namespace std;
int n,m,p,q,min=99999999;
Int a[51][51],book[51][51];
void dfs(int x,int y,int step)
{
	int next[4][2]={{0,1},    //向右走
	          		   {1,0}, //向下走
	          		   {0,-1}, //向左走
	          		   {-1,0}} //向上走
    int tx,ty,k;
    if(x==p&&y==q)
	{
		if(step<min)    min=step;
	    return;
	}
    for(int k=0;k<=3;k++)
	{        //计算下一个点的坐标
       	tx=x+next[k][0];
       	ty=y+next[k][1];
      	if(tx<1||tx>n||ty<1||ty>m) continue;
      	if(a[tx][ty]==0&&book[tx][ty]==0)
		{
	    	book[tx][ty]=1;  //标记这个点已经走过
	        dfs(tx,ty,step+1);   //开始尝试下一个点
	        book[tx][ty]=0;     尝试结束,取消这个点的标记
		}
	}
    return;
}
Int main()
{
	int I,j,startx,starty;
	cin>>n>>m;
	for(i=1;i<=n;i++)
     for(j=1;j<=m;j++)
	  cin>>a[i][j];
	cin>>startx>>starty>>p>>q;
	book[startx][starty]=1;
	dfs(startx,starty,0);
	cout<<min;
	return 0;
}
	

2.5 深度优先搜索的算法框架

深度优先搜索(Depth First Search,DFS),简称深搜,其状态“退回一步”的顺序符合“后进先出”的特点,所以采用“栈”存储状态。深搜适用于要求所有解方案的题目。

框架
void dfs(int dep, 参数表 )
{
	自定义参数 ;
	if( 当前是目标状态 )
	{
		输出解或者作计数、评价处理 ;
	}
	else
	 for(i = 1; i <= 状态的拓展可能数 ; i++)
	  if( 第 i 种状态拓展可行 )
	  {
				维护自定义参数 ;
				dfs(dep+1, 参数表 );
	  }
}

*更多例题可以在我上传的资源中找到

THE END

好了,今天就说到这儿吧(毕竟后面的我还没有学呢~)
这是我第3次写博客自我感觉良好。
*题目均来自JZOJ网站(39.98.198.136),照片均来自老师PPT,部分文字来自老师PPT

你可能感兴趣的:(C++,贪心,深度优先搜索)