当前位置: 首页 > news >正文

搜索与回溯算法(基础算法)

目录

1.前言

                                                          正片开始

2.演练

1.素数环

2.拆分

3.N皇后问题(八皇后plus)

4.最高效益

3.课后练习

1.LETTERS

2.迷宫

4.小结


1.前言

啦啦啦啦我又来啦hhh

咳咳这次还是一笔带过,顺便说一下,接下来每篇文章的最后一行有快捷跳转至本专栏的其他算法,至于前几个已经改好啦~~~

                                                          正片开始

好,我们这次呢,讲的是搜索与回溯,其实了解的都知道,递归就是搜索嘛,那这么简单别搞了。

咳咳,开个玩笑不会有人真信了吧,这可是高质量文章,怎么可能不结束......额不,结束了呢。

好,不扯了,来看回搜索与回溯。这东西呢,也是重要的基础算法之一。众所周知,有些题目你根本推不出答案的关系对吧(想必有很多人被坑过),比如1,5,19,234,21241,143315...你看似这个数列有些像递推,但是吧,这是我随便在键盘上打的hh。

既然如此,搜索与回溯就派出了用场。当然,还是声明一下,回溯是搜索的plus版(简化版),∴回溯就是递归的plus版,又∵递归是搜索的一种,∴回溯是搜索的plusplus版......

等等,停下,赶紧回正题,不然都没兴趣了,毕竟是来学的,不是来看相声的。

不方,我们继续看回溯(由于搜索不是本篇重点,所以不看),回溯呢,就是一种位于搜索算法中间的控制策略,它基本是深度优先搜索,只要遇到了死路,就退回去一步,直到找到了出口,也就是找到了答案;或者发现没路,也就是没答案时,便停止运行。

同样的,与递归算法相同,这种算法的时间问题值得研究。当然,这种方法适用于找最优解或找一个解的情况,而找多个解嘛,深度不大好,时间很large,广度优先搜索最好,下章讲。

好,我们看一看回溯算法的基础框架:(未减枝)

int dfs(int x){for(int i = 1;i <= 算符种数;i++){if(判断条件){保存这个结果(令它为①)if(满足终止条件) 输出解(+结束相当于找最优解)else search(x+1);恢复保存①之前的状态,也就是回溯一步}}
}
​​

上面这个框架我不常用,甚至好像没用过...下面这个我比较常用,但看个人喜好哈

int dfs(int x){if(满足终止条件) 输出解(+结束相当于找最优解)else{for(int i = 1;i <= 算符种数;i++){if(判断条件){保存这个结果search(x+1);恢复状态}}}
}

非常看不懂简单,上手来练一练。

2.演练

好,第一题

1.素数环

题目为将1~20这20个数摆成一个圆环,要求任意两个相邻的数的和都是质数。

这题乍一看非常简单,毕竟就一个个相加再判断质数嘛(质数函数没背下来的给我背!!!),so这时又有一个重要的问题:当遍历到第20个的时候如何跟第1个比?

切,特判。

好,再简单理解一下题意:我们可以把这圆环上看作20个空(毕竟给了20个数),也就是说,每个位置都有20种可能,那么这样的话如果用递归...20^{20} \approx 10^{26},这long比long还多一亿倍啊,疯了。

所以呢这时就要用回溯。当然,要一边判断一遍填。

来吧。

#include <bits/stdc++.h>
using namespace std;int type;//第x种情况
int a[25];//保险起见+5
int b[25];//判断所选数字是否被选中过bool isprime(int x){    //不会的给我背!!if(x <= 1) return false;for(int i = 2;i * i <= x;i++){if(x % i == 0) return false;}return true;
}void dfs(int step){for(int i = 1;i <= 20;i++){if(isprime(a[step-1]+i) && (!b[i])){       //判断选中数字是否能选中且与前面一位数构成质数a[step] = i;b[i] = 1;//变更为选中if(step == 20){if(isprime(a[20]+a[1])){type++;cout << type << ":";for(int j = 1;j <= 20;j++){cout << a[j] << " ";}cout << endl;}}else dfs(step+1);b[i] = 0;}}
}int main(){dfs(1);cout << type << endl;return 0;
}//电脑性能不好的别运行!!!

我为什么要提醒不要去试图运行呢?因为光是以“2 1 4”开头的素数环就已经达到了恐怖的116084种排列方法,因此千万不要去挑战自己电脑的CPU!!!

(以下是输出部分)

可以看到,这仅仅才列举了一点点,总数估算了一下大概要几亿种吧(也就几亿种...)

没事,好歹这题也算是撒花了。

下一题。

2.拆分

任何一个大于1的自然数都能被拆分成若干个小于它的自然数之和,先给出一个自然数n,求它所能被拆分的所有方法数。

如n=3时:

3=1+1+1

3=2+1

即总数sum=2

还是套回溯,所以我们可以用遍历一层层筛选出可供选择的数,从而得出结果,所以直接看代码:

#include <bits/stdc++.h>
using namespace std;int n,a[25],ans;void dfs(int step,int sum){if(sum > n) return;            //当总和过大时就可以直接结束(剪枝)if(sum == n){                  //当总和刚好相等时就可以加一种方法ans++;return;}for(int i = a[step - 1];i <= n-1;i++){  //小于用n-1a[step] = i;dfs(step + 1,sum + i);     //这里主要回溯在sum+i中,这时sum并未改变,因此再用就算一次回溯,相当于15~17行
//        sum += i;
//        dfs(step+1,sum);
//        sum -= i;}
}int main(){cin >> n;a[0] = 1;          //注意赋初值dfs(1,0);          //初始的函数步数为1,总和为0cout << ans;return 0;
}

ok十分简单对吧,接下来上难度:

3.N皇后问题(八皇后plus)

八皇后想必学过的大佬们都不陌生,毕竟这可是回溯中极其重要的题目之一,不会有人做回溯不做这题吧?

但是呢,这里本蒟蒻手动加强一下这道题:

————————————————————————————————————

题目描述

对一个如下的8×8的国际象棋棋盘,有八个皇后被放置在棋盘上,使得每行、每列和每条斜线上都至多只有一个皇后,

这就是著名的八皇后问题,下图是其中一个解.

类似地我们可以定义n皇后问题:n×n(1 \leq n \leq 10)的国际象棋棋盘,有n个皇后被放置在棋盘上,使得每行、每列和每条斜线上都至多只有一个皇后.

你需要根据n,输出n皇后问题的第一个解. 容易发现一个解中,每行一定会恰好有一个皇后,我们规定两个解中如果前k−1行的皇后位置相同,则第k行的皇后更靠前(靠左)的排在前面.(或者说,以皇后的每行位置,按字典序排序)

————————————————————————————————————

这题乍一看用递归嘛,可是n别说10了,哪怕是本来的8都要2^64了诶,人家2^31的小身板怎么能撑得住呢。

so,为了电脑的安全,我们用回溯来优化一下,让电脑逍遥地活着更好的撑住。

好,分析哈,首先行和列检测是很容易的,但斜线如何检测呢?

我们发现当填完后每个皇后的位置中的行和列相加的值都是不同的(看上图,从第一列开始每个皇后的行+列的值分别是:2,9,8,12,7,10,13,11,互不相同),那么就好办了。

我们可以推出公式:i+a[i] != j+a[j]

于是,可以编出如下代码:

#include <bits/stdc++.h>
using namespace std;int n,a[25],vis[25];bool check(){for(int i = 1;i <= n;i++){for(int j = i+1;j <= n;j++){if(a[i] == a[j] || i-a[i] == j-a[j] || i+a[i] == i+a[j])//判断行列斜线的值是否相等,若相等则不成立return false;}}return true;
}void dfs(int step){if(step == n+1){if(check()){for(int i = 1;i <= n;i++){cout << a[i] << " ";}exit(0);//因为要求给出字典序最前的一种,所以结束后立即跳出dfs函数,不再继续}}for(int i = 1;i <= n;i++){if(!vis[i]){a[step] = i;  //指皇后位置在第step列第i行vis[i] = 1;   //选中时dfs(step+1);vis[i] = 0;   //回溯}}
}int main(){cin >> n;dfs(1);return 0;
}

也是十分easy,接下来继续(本文内容稍多,请见谅)

4.最高效益

设有A、B、C、D、E五人分别从事1、2、3、4、5五项工作,每人仅能从事一项,他们工作所得的效益为:

效益
12345
A13111047
B13101085
C59774
D151210115
E1011884

(打表给我手打废了)

现在每个人都需要选择一项工作且不能重复选一项,在所有组合中求效益最高的一种组合。

这道题似乎很简单,但如何判断是否选过呢?

设一个bool型a数组就完sir,只要判断a[i]是否被用过(0为没用过,1为已用过),若没用过就继续就可以啦,呵呵。

当然,还要有一个sum来统计效益总和,其实也不复杂。

来代码:(打表最累哈)

#include <bits/stdc++.h>
using namespace std;//打表,这里本蒟蒻喜欢遍历1~5,so外面多了一行0,当然习惯0~4的也可以用d[5][5]
int d[6][6] = {         {0,0,0,0,0,0},{0,13,11,10,4,7},{0,13,10,10,8,5},{0,5,9,7,7,4},{0,15,12,10,11,5},{0,10,11,8,8,4}
};
int ans,f[10],h[10];
//这里ans是最高效益,f[10]存储当前组合方式,h[10]储存最优方案
int p[6];  //判断是否用过的数组(回溯时先1后0)void dfs(int step,int sum){if(step > 5){                      //5人已全被安排工作if(sum > ans){                 //判断效率是否更高//全部更改为最优方案ans = sum;   for(int i = 1;i <= 5;i++){h[i] = f[i];}}return;                        //void要注意返回空值来结束,否则在部分情况下会超时(这里虽不会,但养成好习惯)}for(int i = 1;i <= 5;i++){         //遍历每一项工作if(!p[i]){                     //判断工作是否已被占用f[step] = i;               //若未使用,则第step人选第i项工作p[i] = 1;                  //标记为已被占用dfs(step+1,sum+d[step][i]);//往下搜(这里sum的值只在下一层中使用,不用这一层并未改变,不需回溯)p[i] = 0;                  //回溯}}
}int main(){//本题无输入,直接搜dfs(1,0);for(int i = 1;i <= 5;i++){char t = 64+i;                 //这里使用了ASCll码,t值分别对应A、B、C、D、Ecout << t;    printf(":第%d项\n",h[i]);}printf("total:%d",ans);return 0;
}

so easy。

啥,你想直接要答案?自己去运行。

3.课后练习

经过这一节几乎没用干货满满的小课,想必你肯定有些收获,下面来手动练习一下:

1.LETTERS

给出一个R*C的大写字母矩阵,你一开始位于左上角,并且可以通过上下左右走动来收集字母,但每个字母只能收集一次,而往后就不能再经过收集过的字母的格子。求你最多能收集多少个字母呢。

样例输入:

3  6

HFDFFB

AJHGDH

DGAGEH

样例输出:

6

样例解释:

收集H(1,1),A(1,2),D(1,3),G(2,3),J(2,2),F(2,1)六个字母

2.迷宫

今天你在丛林中探险时不幸进入了一个迷宫,迷宫由n*n的矩阵组成,其中“.”为路,是可以走的;“#”为墙,是不能走的(你不能瞬移和挖穿墙壁)。而你每次只能移动到上下左右的相邻格子上。

现在经过你一顿勇猛的乱冲后,你到了(hb,lb)的位置,出口则在(he,le)的位置,这时,你获得了一个地图,但身为程序员的你看不懂这诡异的地图,于是将其导入了你随身携带的电脑中(电脑里没地图,想得美),先请你写出一段程序,帮你自己看看有没有出去的路,如果出口的路被封死了也就只能够等待救援,而如果未被封死,就可以继续试探来寻找出路。

输入:

第一行是需解决的迷宫数k。

后面是k组输入,每组输入的第1行是n,表示迷宫大小为n*n,后2~n+1行是一个n*n的矩阵,矩阵中为“.”或者“#”,代表这个格子是路还是墙,n+2行则是四个数hb,lb,he,le,代表你的位置和出口的位置。

输出:

输出k行,每行输出对应一个输入。如果可以逃出,则输出“You can find the way.”;否则输出“Please wait for support.”

输入样例:

2

3

. # #

. . #

# . .

0 0 2 2

5

. . . . .

# # # . #

. . # . .

# # # . .

. . . # .

0 0 4 0

样例输出:

You can find the way.

Please wait for support.

4.小结

这次我们学习了搜索与回溯的算法,可以说你应对考试的方法又多了一种,如果学会了,你可以去一些平台上(不打推销)来练练手,我们《广度优先搜索算法(基础算法)》见,86!

上一章:递归算法                                                        下一章:广度优先搜索算法(未完工)

http://www.dtcms.com/a/263846.html

相关文章:

  • 华为交换机堆叠与集群技术深度解析附带脚本
  • Golang的并发编程实践总结
  • 【pathlib 】Python pathlib 库教程
  • 成都芯谷金融中心文化科技园:打造区域科技活力
  • nginx配置websocket
  • 用java,把12.25.pdf从最后一个点分割,得到pdf
  • Elastic 构建 Elastic Cloud Serverless 的历程
  • CertiK《Hack3d:2025年第二季度及上半年Web3.0安全报告》(附报告全文链接)
  • 61、【OS】【Nuttx】【构建】向量表
  • Redis-7.4.3-Windows-x64下载安装使用
  • 浅谈Docker Kicks in的应用
  • ‌Webpack打包流程
  • 为什么时序数据库IoTDB选择Java作为开发语言
  • Milvus docker-compose 部署
  • t检验​、​z检验、χ²检验中的P值
  • Vue3 使用 i18n 实现国际化完整指南
  • 浏览器F12开发者工具的使用
  • 大模型MCP技术之一句话安装Hadoop
  • DML-2-更新和删除
  • Python 数据分析:numpy,抽提,整数数组索引与基本索引扩展(元组传参)。听故事学知识点怎么这么容易?
  • JavaWeb笔记02
  • hello算法_C++_ 最差、最佳、平均时间复杂度
  • Spring事务传播行为?失效情况?(详解)
  • 设计模式精讲 Day 20:状态模式(State Pattern)
  • imx6ull芯片中断机制6.24-6.25
  • Python中字符串isalpha()函数详解
  • 设计模式-责任链, 责任链+ 模板方法模式相结合
  • 抽奖概率-数值练习题
  • AR衍射光波导设计遇瓶颈,OAS 光学软件来破局
  • 【Golang面试题】Go结构体的特点,与其它语言的区别