搜索算法例题讲解(Good)
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
很小,暴力搜索可行!
算法分析
题目给出了苹果和盘子全相同,所以说我们 这里为了让题目变的简单,假设让ai<=ai+1, 正如上面的例子所看到的一样。 搜索时我们需要设置一个条件,就是当放置 苹果到第i个盘子的时候,必须要求第i-1个盘 子中的苹果小于第i个盘子,如果大于我们继 续加1,直到第i个盘子放置的苹果大于等于 第i-1个盘子中的苹果。如果剩下的苹果已经 小于前一个盘子中的苹果,我们停止这个分 支的搜索。
搜索算法例题讲解
史宁 西安电子科技大学ACM基地
例1:放苹果(POJ1664)
Description 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不 放,问共有多少种不同的分法?(5,1,1和1,1,5是同一种 方法) Input 以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10 Output 对输入的每组数据M和N,用一行输出相应的K。
例4:滑雪(POJ1088)
Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可 是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡 底,你不得不再次走上坡或者等待升降机来载你。Michael想 知道载一个区域中最长底滑坡。区域由一个二维数组给出。 数组的每个数字代表点的高度。下面是一个例子 12345 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 一个人可以从某个点滑向上下左右相邻四个点之一,当且仅 当高度减小。在上面的例子中,一条可滑行的滑坡为24-1716-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一 条。
搜索小结二
搜索题目无外乎上面所说的几类,接下来我们来一一分析下 它们的特点。 求最终状态的个数
这个问题我们有时可以转化为一个组合问题 这个问题是最麻烦,必须对状态空间进行全遍历 这个相对简单,有时候可以用随机化或者当搜索到其中一个解的时候 停止搜索 这个问题可以在问题2的基础上再对状态空间解进行一次扫描。也可 以转化成动态规划问题
7. 8. 9. 10. 11.
main() { int i,u; res=0; intputdata(); //数据输入 for (i=1;i<=n;i++) for (u=1;u<=m;u++) if (s[i][u]==‘W’) //当点为w进行种子填充 bfs(i,u); cout<<res<<endl; //输出结果 }
队首和队尾之间的元素为BFS扩展的状态
普通队列与优先队列
有些题目中,我们需要在整个状态空间中找 到一个最值怎么办? 普通队列?
显然达不到要求,因为它的插入是无序的
二叉堆!
二叉堆支持在O(logn)下对堆进行维护并且在O(1) 下找出最值。
题目分析
回到例2题目中,我们通过分析,显然用BFS 比较容易实现,而且很直观。下面我们来针 对题目进行分析。 因为有三种操作,我们可以像刚刚所看到的 状态空间树一样去扩展。 直到扩展到目标节点为止。
BFS代码分析
while (1) { t++; while (f!=r1) { w=q[++f]; if (w+1==k||w-1==k||w*2==k){ printf("%d\n",t); return 0; } if (2*w<100000&&p[2*w]==0){ q[++r]=2*w; p[w*2]=1; } if (w-1<100000&&p[w-1]==0){ q[++r]=w-1; p[w-1]=1; } if (w+1<100000&&p[w+1]==0){ q[++r]=w+1; p[w+1]=1; } } r1=r; //当前的r赋值给下一层次执行结束时r1的标志 }
算法分析
初始化
我们将n加入队列中作为第一个元素q[1]=n,让 队首f=0,队尾r=1;再设一变量t记录拓展的层次 首先我们先让一个元素出队,对这个元素进行扩 展。 将扩展出来的节点加入队尾,直到上一层的队尾 r1。 按以上操作,直到找到目标状态
BFS操作
BFS剪枝讲解
判重
当我们扩展状态时,总会出现重复现象
算法的部分优化
void dfs(long k, long w) { long i,u; if (k==m) { if (n-w>=s[k-1]) { s[k]=n-w; z=z+1; } return; } for (i=0;i<=n;i++) if ((i>=s[k-1])&&((n-w)/(m-k)>=s[k-1])) { w=w+i; s[k]=i; k=k+1; dfs(k,w); w=w-i; k=k-1; } }
样例分析
题目要求用3中操作对其进行处理。下面我们 来看一下状态树的生成。 5 部分状态空间树 10 9 18 11 22 20 19
BFS vs DFS 4 3 2 15 16 17 8 14 7 6 12 13
算法描述
BFS算法
找到当前节点扩展出来的子节点
当前状态已经不能满足解的条件则剪枝 为了减少搜索节点的状态空间,我们从起始节点 和目标节点共同出发进行搜索
小结
我们通过以上的例子给大家讲解了基本的BFS 搜索,它的重点在于层次上的队列元素的出 队和入队。再者就是判重,防止状态空间树 无穷大而导致RE或者TLE。 注意理解DFS中堆栈和BFS中队列的应用,这 点很重要。 BFS中优先队列的使用,当题目要求在队中找 到最值时,我们应当使用。
例3:Lake Counting(POJ2386)
算法效率的分析
此算法的效率比较低,为指数级算法。效率 相当之低下,不过应对10以内的数据完全能 达到要求。 下面我们看一个剪枝。当剩下的苹果平均放 到剩余的盘子中的时候,每个盘子分的的苹 果数目小于前一个盘子放置的苹果数目,这 样就不满足我们给的要求ai-1<ai,所以我们对 它进行剪枝。下面是剪枝优化后的程序。
扩散现象!
如果我们利用这种思路能否想出本题的算法呢?
种子填充法!
种子填充法
种子填充算法又称为边界填充算法。其基本 思想是:从多边形区域的一个内点开始,由 内向外用给定的颜色画点直到边界为止。如 果边界是以一种颜色指定的,则种子填充算 法可逐个像素地处理直到遇到边界颜色为止
题目分析
回到题目,由题目我给大家讲解一下种子填 充算法。 首先题目中给定的矩阵很简单,只有两种元 素,一种是”w”表示连通块,一种是”.”表 示分隔个连通块 题目中说到时八个方向任意一个方向有w均可 连通,所以说这个是要区分四向连通的
算法分析二
临界条件的设置:
搜索不是无穷的搜索下去,它必须有个条件来约 束它,这个题目中,当m个盘子都放满苹果时我 们结束搜索。
当程序开始时,我们已经将0个苹果放置到1号盘 子,递归运算的时候,我们将循环值从0到n,这 样子就可以让每个盘子都能放置0到n个苹果,即 便它不符合要求。
初始赋值与递归运算:
代码分析二
1. 2. 3. 4. 5. 6. 7.
if (s[a][b+1]=='W') { s[a][b+1]='.'; r=r+1; d[r][1]=a; d[r][2]=b+1; }
小结
这里所讲到的种子填充法,我们既可以用BFS 实现,也可以用DFS实现,这个根据各自的喜 好决定。 种子填充法一般分为4向和8向,实际上也就 是它的扩展规则不同,方法都一样。 种子填充时应当注意处理被填充过的节点, 否则会出现状态无限增大的情况。
当k<=n时,直接n-k 如果结果超出了题目中给定的数据规模,我们剪 枝,这个也是为了防止越界
k<=n特殊操作
范围处理
初始化代码分析
1. 2. 3. 4. 5. 6. 7.
if (k<=n) { printf("%d\n",n-k); return 0; } f=0; r=1; q[1]=n; r1=1; t=0; memset(p,0,sizeof(p)); 注意第7行代码,它的作用是判重 1-5行是一个特殊处理,他的作用是当k<=n时, 只能通过n=n-1的操作来达到最优解,所以说我们 直接输出n-k
双向BFS算法
双向BFS算法是将起始状态和目标状态共同进 行BFS搜索,直到他们两个的状态(中间状态 )相同时停止。 这样做的好处是可以减少状态空间的容量。 也可以减少时间的开销,可以时程序达到一 个较优的状态。
DFS与BFS状态操作分析
栈首和栈尾之间的元素为DFS扩展的状态
栈顶
队首
队尾
栈底
枚举矩阵中的每一个元素,当元素为w时对 它进行种子填充(BFS) 种子填充
1. 2.
3.
对八个方向分别扩展加入队列 再对w进行修改,把原有的“w”改为“.”,这样 我们就可以不必再考虑这个点了 用BFS搜索的办法把相邻的点均加入队列,直到 无节点可以扩展
代码分析
1. 2. 3. 4. 5.
6.
代码分析
void dfs(long k, long w) //初始k=1,w=0; k表示现在已用几个盘子,w表示已经放了几个苹果 { long i,u; if (k==m) //当最后一个盘子被放置苹果的时候我们进行判断 { if (n-w>=s[k-1]) { s[k]=n-w; z=z+1; } return; } for (i=0;i<=n;i++) if (i>=s[k-1]) //如果当前放置的苹果个数大于前一个盘子继续放置 { w=w+i; s[k]=i; k=k+1; dfs(k,w); w=w-i; k=k-1; } }
题目描述:给定一个矩阵,求它的连通块个 数 样例数据:
样例分析
题目中要求的数据范围长宽不超过100 大家可以看到,题目中说的是八个方向只要 有任意一个方向有块,则它联通。 样例中给的数据显然只有3个块 分析完样例,大家思考一下该怎么做?
算法引入
如果有一滴墨水滴入水中会出现什么现象?
ห้องสมุดไป่ตู้
BFS+剪枝
双向BFS
BFS与DFS算法
我们将当前节点的扩展节点存放到队列之中 ,这里跟DFS不同的是,在DFS搜索中我们将 状态节点存放在堆栈中,这里需要大家注意 的是区分这两种搜索的不同点。DFS是当不能 在扩展节点时对当前节点进行回溯操作,直 到堆栈为空。而BFS节点则是当队列中的状态 为空时停止操作。下面我们来分析下栈操作 在DFS和队列操作在BFS中的应用。
//优化剪枝部分
优化前后的对比
搜索题目小结
暴力搜索只能在很小的数据范围内使用,一 般情况下,所谓的暴力也就是枚举了所有的 状态,在这些状态中找到最终的解。在很多 情况下,搜索题目有以下几类:1.求所有状 态的个数,例如放苹果。2.找出所有解的状 态,例如把放苹果的每种方式求出来。3.求 其中一个可行解。4.求出所有可行解的最优 解。
算法描述
初始化
本题初始化很简单,我们只需要将存放结果的 变量设为0,并且把矩阵中其中一个点加入BFS 队列之中 r=1; //设置队尾 f=0; //设置队首 res=res+1; //结果加1 d[r][1]=x; //其中一个w点加入队列 d[r][2]=y;
1. 2. 3.
4.
5.
算法描述二
Sample Input 73 Sample Output 8
数据分析
当n=7,m=3时,有以下几类分法: 007 016 025 034 115 124 133 223
题目分析
这里需要注意两个同样,盘子是相同的,而 且苹果也相同。所以说随意调换盘子和苹果 的位置是可以的。 题目抽象出的数学模型就是把一个数n拆成m 份,使得a1+a2……am=n,0<=ai<=n 接下来我们看下数据规模, 1<=m,n<=10,
求出所有最终状态
求出一种可行状态
求出最优可行状态
当然以上都是些常规的解题思想,并不能代表所有问题都是 按照以上的方式进行求解。
例2:Catch That Cow(POJ3278)
题目描述:给定一个n,k(0 <=n,k <= 100,000);对n有三种操作,分别为 n=n+1,n=n-1,n=2*n。现在要求用最少的操 作次数使得n变为k。 样例输入: 5 17 样例输出: 4 Hint:5-10-9-18-17
算法分析
题目给出了苹果和盘子全相同,所以说我们 这里为了让题目变的简单,假设让ai<=ai+1, 正如上面的例子所看到的一样。 搜索时我们需要设置一个条件,就是当放置 苹果到第i个盘子的时候,必须要求第i-1个盘 子中的苹果小于第i个盘子,如果大于我们继 续加1,直到第i个盘子放置的苹果大于等于 第i-1个盘子中的苹果。如果剩下的苹果已经 小于前一个盘子中的苹果,我们停止这个分 支的搜索。
搜索算法例题讲解
史宁 西安电子科技大学ACM基地
例1:放苹果(POJ1664)
Description 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不 放,问共有多少种不同的分法?(5,1,1和1,1,5是同一种 方法) Input 以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10 Output 对输入的每组数据M和N,用一行输出相应的K。
例4:滑雪(POJ1088)
Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可 是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡 底,你不得不再次走上坡或者等待升降机来载你。Michael想 知道载一个区域中最长底滑坡。区域由一个二维数组给出。 数组的每个数字代表点的高度。下面是一个例子 12345 16 17 18 19 6 15 24 25 20 7 14 23 22 21 8 13 12 11 10 9 一个人可以从某个点滑向上下左右相邻四个点之一,当且仅 当高度减小。在上面的例子中,一条可滑行的滑坡为24-1716-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一 条。
搜索小结二
搜索题目无外乎上面所说的几类,接下来我们来一一分析下 它们的特点。 求最终状态的个数
这个问题我们有时可以转化为一个组合问题 这个问题是最麻烦,必须对状态空间进行全遍历 这个相对简单,有时候可以用随机化或者当搜索到其中一个解的时候 停止搜索 这个问题可以在问题2的基础上再对状态空间解进行一次扫描。也可 以转化成动态规划问题
7. 8. 9. 10. 11.
main() { int i,u; res=0; intputdata(); //数据输入 for (i=1;i<=n;i++) for (u=1;u<=m;u++) if (s[i][u]==‘W’) //当点为w进行种子填充 bfs(i,u); cout<<res<<endl; //输出结果 }
队首和队尾之间的元素为BFS扩展的状态
普通队列与优先队列
有些题目中,我们需要在整个状态空间中找 到一个最值怎么办? 普通队列?
显然达不到要求,因为它的插入是无序的
二叉堆!
二叉堆支持在O(logn)下对堆进行维护并且在O(1) 下找出最值。
题目分析
回到例2题目中,我们通过分析,显然用BFS 比较容易实现,而且很直观。下面我们来针 对题目进行分析。 因为有三种操作,我们可以像刚刚所看到的 状态空间树一样去扩展。 直到扩展到目标节点为止。
BFS代码分析
while (1) { t++; while (f!=r1) { w=q[++f]; if (w+1==k||w-1==k||w*2==k){ printf("%d\n",t); return 0; } if (2*w<100000&&p[2*w]==0){ q[++r]=2*w; p[w*2]=1; } if (w-1<100000&&p[w-1]==0){ q[++r]=w-1; p[w-1]=1; } if (w+1<100000&&p[w+1]==0){ q[++r]=w+1; p[w+1]=1; } } r1=r; //当前的r赋值给下一层次执行结束时r1的标志 }
算法分析
初始化
我们将n加入队列中作为第一个元素q[1]=n,让 队首f=0,队尾r=1;再设一变量t记录拓展的层次 首先我们先让一个元素出队,对这个元素进行扩 展。 将扩展出来的节点加入队尾,直到上一层的队尾 r1。 按以上操作,直到找到目标状态
BFS操作
BFS剪枝讲解
判重
当我们扩展状态时,总会出现重复现象
算法的部分优化
void dfs(long k, long w) { long i,u; if (k==m) { if (n-w>=s[k-1]) { s[k]=n-w; z=z+1; } return; } for (i=0;i<=n;i++) if ((i>=s[k-1])&&((n-w)/(m-k)>=s[k-1])) { w=w+i; s[k]=i; k=k+1; dfs(k,w); w=w-i; k=k-1; } }
样例分析
题目要求用3中操作对其进行处理。下面我们 来看一下状态树的生成。 5 部分状态空间树 10 9 18 11 22 20 19
BFS vs DFS 4 3 2 15 16 17 8 14 7 6 12 13
算法描述
BFS算法
找到当前节点扩展出来的子节点
当前状态已经不能满足解的条件则剪枝 为了减少搜索节点的状态空间,我们从起始节点 和目标节点共同出发进行搜索
小结
我们通过以上的例子给大家讲解了基本的BFS 搜索,它的重点在于层次上的队列元素的出 队和入队。再者就是判重,防止状态空间树 无穷大而导致RE或者TLE。 注意理解DFS中堆栈和BFS中队列的应用,这 点很重要。 BFS中优先队列的使用,当题目要求在队中找 到最值时,我们应当使用。
例3:Lake Counting(POJ2386)
算法效率的分析
此算法的效率比较低,为指数级算法。效率 相当之低下,不过应对10以内的数据完全能 达到要求。 下面我们看一个剪枝。当剩下的苹果平均放 到剩余的盘子中的时候,每个盘子分的的苹 果数目小于前一个盘子放置的苹果数目,这 样就不满足我们给的要求ai-1<ai,所以我们对 它进行剪枝。下面是剪枝优化后的程序。
扩散现象!
如果我们利用这种思路能否想出本题的算法呢?
种子填充法!
种子填充法
种子填充算法又称为边界填充算法。其基本 思想是:从多边形区域的一个内点开始,由 内向外用给定的颜色画点直到边界为止。如 果边界是以一种颜色指定的,则种子填充算 法可逐个像素地处理直到遇到边界颜色为止
题目分析
回到题目,由题目我给大家讲解一下种子填 充算法。 首先题目中给定的矩阵很简单,只有两种元 素,一种是”w”表示连通块,一种是”.”表 示分隔个连通块 题目中说到时八个方向任意一个方向有w均可 连通,所以说这个是要区分四向连通的
算法分析二
临界条件的设置:
搜索不是无穷的搜索下去,它必须有个条件来约 束它,这个题目中,当m个盘子都放满苹果时我 们结束搜索。
当程序开始时,我们已经将0个苹果放置到1号盘 子,递归运算的时候,我们将循环值从0到n,这 样子就可以让每个盘子都能放置0到n个苹果,即 便它不符合要求。
初始赋值与递归运算:
代码分析二
1. 2. 3. 4. 5. 6. 7.
if (s[a][b+1]=='W') { s[a][b+1]='.'; r=r+1; d[r][1]=a; d[r][2]=b+1; }
小结
这里所讲到的种子填充法,我们既可以用BFS 实现,也可以用DFS实现,这个根据各自的喜 好决定。 种子填充法一般分为4向和8向,实际上也就 是它的扩展规则不同,方法都一样。 种子填充时应当注意处理被填充过的节点, 否则会出现状态无限增大的情况。
当k<=n时,直接n-k 如果结果超出了题目中给定的数据规模,我们剪 枝,这个也是为了防止越界
k<=n特殊操作
范围处理
初始化代码分析
1. 2. 3. 4. 5. 6. 7.
if (k<=n) { printf("%d\n",n-k); return 0; } f=0; r=1; q[1]=n; r1=1; t=0; memset(p,0,sizeof(p)); 注意第7行代码,它的作用是判重 1-5行是一个特殊处理,他的作用是当k<=n时, 只能通过n=n-1的操作来达到最优解,所以说我们 直接输出n-k
双向BFS算法
双向BFS算法是将起始状态和目标状态共同进 行BFS搜索,直到他们两个的状态(中间状态 )相同时停止。 这样做的好处是可以减少状态空间的容量。 也可以减少时间的开销,可以时程序达到一 个较优的状态。
DFS与BFS状态操作分析
栈首和栈尾之间的元素为DFS扩展的状态
栈顶
队首
队尾
栈底
枚举矩阵中的每一个元素,当元素为w时对 它进行种子填充(BFS) 种子填充
1. 2.
3.
对八个方向分别扩展加入队列 再对w进行修改,把原有的“w”改为“.”,这样 我们就可以不必再考虑这个点了 用BFS搜索的办法把相邻的点均加入队列,直到 无节点可以扩展
代码分析
1. 2. 3. 4. 5.
6.
代码分析
void dfs(long k, long w) //初始k=1,w=0; k表示现在已用几个盘子,w表示已经放了几个苹果 { long i,u; if (k==m) //当最后一个盘子被放置苹果的时候我们进行判断 { if (n-w>=s[k-1]) { s[k]=n-w; z=z+1; } return; } for (i=0;i<=n;i++) if (i>=s[k-1]) //如果当前放置的苹果个数大于前一个盘子继续放置 { w=w+i; s[k]=i; k=k+1; dfs(k,w); w=w-i; k=k-1; } }
题目描述:给定一个矩阵,求它的连通块个 数 样例数据:
样例分析
题目中要求的数据范围长宽不超过100 大家可以看到,题目中说的是八个方向只要 有任意一个方向有块,则它联通。 样例中给的数据显然只有3个块 分析完样例,大家思考一下该怎么做?
算法引入
如果有一滴墨水滴入水中会出现什么现象?
ห้องสมุดไป่ตู้
BFS+剪枝
双向BFS
BFS与DFS算法
我们将当前节点的扩展节点存放到队列之中 ,这里跟DFS不同的是,在DFS搜索中我们将 状态节点存放在堆栈中,这里需要大家注意 的是区分这两种搜索的不同点。DFS是当不能 在扩展节点时对当前节点进行回溯操作,直 到堆栈为空。而BFS节点则是当队列中的状态 为空时停止操作。下面我们来分析下栈操作 在DFS和队列操作在BFS中的应用。
//优化剪枝部分
优化前后的对比
搜索题目小结
暴力搜索只能在很小的数据范围内使用,一 般情况下,所谓的暴力也就是枚举了所有的 状态,在这些状态中找到最终的解。在很多 情况下,搜索题目有以下几类:1.求所有状 态的个数,例如放苹果。2.找出所有解的状 态,例如把放苹果的每种方式求出来。3.求 其中一个可行解。4.求出所有可行解的最优 解。
算法描述
初始化
本题初始化很简单,我们只需要将存放结果的 变量设为0,并且把矩阵中其中一个点加入BFS 队列之中 r=1; //设置队尾 f=0; //设置队首 res=res+1; //结果加1 d[r][1]=x; //其中一个w点加入队列 d[r][2]=y;
1. 2. 3.
4.
5.
算法描述二
Sample Input 73 Sample Output 8
数据分析
当n=7,m=3时,有以下几类分法: 007 016 025 034 115 124 133 223
题目分析
这里需要注意两个同样,盘子是相同的,而 且苹果也相同。所以说随意调换盘子和苹果 的位置是可以的。 题目抽象出的数学模型就是把一个数n拆成m 份,使得a1+a2……am=n,0<=ai<=n 接下来我们看下数据规模, 1<=m,n<=10,
求出所有最终状态
求出一种可行状态
求出最优可行状态
当然以上都是些常规的解题思想,并不能代表所有问题都是 按照以上的方式进行求解。
例2:Catch That Cow(POJ3278)
题目描述:给定一个n,k(0 <=n,k <= 100,000);对n有三种操作,分别为 n=n+1,n=n-1,n=2*n。现在要求用最少的操 作次数使得n变为k。 样例输入: 5 17 样例输出: 4 Hint:5-10-9-18-17