搜索DFS、BFS
题目:N皇后问题
剪枝技巧在这个程序中体现在 PD
函数和 DFS
函数中。
-
在
PD
函数中的剪枝: 在PD
函数中,每次放置一个皇后时,会检查该皇后与之前已放置的皇后是否存在冲突。如果存在冲突,则返回 0,表示不能放置在当前位置,从而减少了不必要的递归。这样,就避免了在已经冲突的情况下继续递归搜索,提高了程序的效率。 -
在
DFS
函数中的剪枝: 在DFS
函数中,首先会调用check
函数检查是否已经放置了 n 个皇后,如果是,则符合条件,直接返回。这样可以避免在已经放置了 n 个皇后之后继续递归搜索,节省了不必要的计算时间。
此外,在 DFS
函数中,还会在放置皇后之前进行判断,如果当前位置不符合条件(即调用 PD
函数返回 0),则直接跳过当前列的放置,继续尝试下一列,从而减少了搜索空间,提高了效率。
这些剪枝技巧使得程序能够更加高效地搜索出符合条件的解,避免了不必要的计算和搜索,从而提高了程序的运行效率。
#include<bits/stdc++.h>
using namespace std;
int x[15]={0};//x[i] 表示第 i 个皇后所在的列号
int sum,n;
//n皇后冲突的条件是:
//在同一条对角线上(行号之差的绝对值等于列号之差的绝对值)
//在同一列上
int PD(int k){
for(int i=1;i<k;i++){
if(abs(k-i)==abs(x[k]-x[i]))
return 0;
else if(x[k]==x[i])//在同一列上
return 0;
}
return 1;
}
bool check (int a){
if(a>n) { //满足条件 ,可以退回main输出结果
sum++; //检查是否已经放置了 n 个皇后
return 1;
}
else
return 0;
}
void DFS(int a){
if(check(a))
return ;
else
for(int i=1;i<=n;i++){
x[a]=i;//第a个皇后放的列数
if(PD(a))//判断是否能放这步
DFS(a+1);//能就进行下一个
else continue; //不能就下一列
}
}
int main(){
cin>>n;//n个皇后
DFS(1);//每次都从第一个皇后开始
cout<<sum<<endl;
return 0;
}
题目:最大数字
#include<bits/stdc++.h> // 包含 C++ 标准库的所有头文件
using namespace std; // 使用标准命名空间
string n; // 输入的数字序列
long long ans; // 最终的最大数
int a,b; // 操作1和操作2的剩余次数
// 深度优先搜索函数,寻找最大数
void dfs(int x, long long an){
int t = n[x] - '0'; // 将字符转换为数字
// 如果当前处理的数字位存在
if(n[x]){
// 处理操作1
int c = min(a, 9 - t); // 计算当前位置可以进行的操作1的最大增量
a -= c; // 更新操作1的剩余次数
dfs(x + 1, an * 10 + t + c); // 递归处理下一位数字
a += c; // 恢复操作1的剩余次数
// 如果剩余的操作2次数大于当前数字
if(b > t){
b = b - t - 1; // 更新操作2的剩余次数
dfs(x + 1, an * 10 + 9); // 递归处理下一位数字
b = b + t + 1; // 恢复操作2的剩余次数
}
} else {
ans = max(ans, an); // 更新最大数
}
}
int main(){
cin >> n >> a >> b; // 输入数字序列和操作次数
dfs(0, 0); // 从数字的第一位开始进行深度优先搜索
cout << ans; // 输出最大数
return 0;
}
在此之前,我还有一种写法,但是只通过了60%的测试数据,我先做个记录
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char c[20];
ll ans=0;
int n,m;//n:1号操作剩余次数 m:2号操作剩余次数
void dfs(int i,ll v){
int x=c[i]-'0';//将字符 c[i] 转换为对应的数字
if(c[i]){//判断用于检查当前处理的数字位是否存在,则不为结束符 \0 时
int t=min(n,9-x);//计算了当前位置可以进行操作1的最大增量,确保当前数字加上增量后不会超过 9
n=n-t;//更新剩余操作次数 n
dfs(i+1,v*10+x+t);//进行操作1,递归调用 dfs 函数,处理下一位数字,并将得到的新数字加入当前数字的十位
n=n+t;//在递归调用 dfs 函数之后,恢复剩余操作次数 n,因为递归调用结束后,需要回溯到当前状态。
//判断用于检查当前位置是否可以进行操作2,当前数字大于 0 时
if(m>x){
m=m-x+1;//更新剩余操作次数 m
dfs(i+1,v*10+9);//进行操作2,将当前数字减去尽可能大的数(即减到 9),并递归调用 dfs 函数,处理下一位数字。
m=m+x+1;//
}
}
else{//已经处理完所有数字,不需要再进行递归
ans=max(ans,v);//更新最大值并返回
}
}
int main(){
scanf("%s%d%d",c,&n,&m);
dfs(0,0);
printf("%lld",ans);
return 0;
}
题目:路径之谜
此题我写的时候,测试用例7个只通过了6个,通过率85.7%,但是我觉得这份代码是思路比较好理解dfs,时间也比较紧迫,所以坚持不改了。有需要的可以上蓝桥杯官网上看100%通过的题解。
#include<bits/stdc++.h>
using namespace std;
//#define ll long long
struct PII{
int first;
int second;
};
const int N = 30;//最大的网格大小
int rol[N];//行
int col[N];//列
int n;//格子数 长款从1到n
bool flag[N][N];//标记网格中的位置是否已经走过
vector<PII> res; //存储路径的容器,每个节点表示一个格子的位置
//------------------------------图的路径搜索常用方向移动表示
int dx[4]={0,1,-1,0};// 定义四个方向的横坐标增量
int dy[4]={1,0,0,-1};// 定义四个方向的纵坐标增量
//两两组合形成上下左右四个方向
// 1-----------------x
// |
// |
// |
// |
// y
//
//dx[0]=0 dy[0]=1//向下
//dx[0]=0 dy[0]=-1//向上
//dx[0]=-1 dy[0]=0//向左
//dx[0]=1 dy[0]=0//向右
//-----------------------------------------------------------
// 检查当前位置是否为终点
bool check(int x,int y){
if(x==n&y==n) {// 如果当前位置为右下角
for (int i=1;i<=n;i++){
if(rol[i]!=0)// 如果有任何一行的靶子数不为0,说明不是有效路径
return false;
}
for(int i=0;i<res.size();i++){
int x=res[i].first;//获取当前节点的x轴坐标
int y=res[i].second;//获取当前节点的y轴坐标
int sum=n*(x-1)+y-1;//计算当前节点在一维数组中的位置
cout<<sum<<" ";
}
cout<<endl;
return false;//到达右下角,成功终止
}
return true;//继续搜索
}
bool pd(int x2,int y2){//边界判断
//已经被走过,不能再走,超出边界
if(flag[x2][y2]==1)
return 0;
//从左侧走出方格
else if(x2<1)
return 0;
//从右侧走出方格
else if(x2>n)
return 0;
//从上侧走出方格
else if(y2<1)
return 0;
//从下侧走出方格
else if(y2>n)
return 0;
//没走到右下角,箭用完了
else if(col[x2]<=0)
return 0;
else if(rol[y2]<=0)
return 0;
else return 1;//符合边界条件,可以继续执行搜索
}
void dfs(int x,int y){
if(!check(x,y)){// 如果当前位置不满足条件
return ;//回溯,用于剪枝
}
else {
for(int i=0;i<4;i++){ // 遍历四个方向
int xt=dx[i]+x;// 计算下一个节点的横坐标
int yt=dy[i]+y;// 计算下一个节点的纵坐标
if(!pd(xt,yt)){// 如果下一个位置不满足边界条件
continue;// 继续尝试下一个方向
}
else{
flag[xt][yt]=true;// 标记当前位置已经访问过
col[xt]--; //更新列靶子数
rol[yt]--;//更新行靶子数
res.push_back({xt,yt});// 将当前位置加入路径中
dfs(xt,yt);// 递归搜索下一个节点
res.pop_back();// 回溯,撤销当前位置的访问
flag[xt][yt]=false;// 回溯,恢复当前位置的未访问状态
col[xt]++;//回溯,恢复列靶子数
rol[yt]++;//回溯,恢复行靶子数
}
}
}
}
int main(){
cin>>n;// 读取格子数
for(int i=1;i<=n;i++)
cin>>rol[i];//行的靶子数
for(int i=1;i<=n;i++)
cin>>col[i];//列的靶子数
flag[1][1]==true;// 标记起点已经访问过
col[1]--;//更新靶子数
rol[1]--;
res.push_back({1,1});// 将起点加入路径中
dfs(1,1);// 开始深度优先搜索
return 0;
}
题目:长草
#include <bits/stdc++.h>
using namespace std;
const int M = 1005;
struct PII
{
int first;//x
int second;//y
};
// C++ 有个数据类型叫 pair 上面的就可以定义为 pair<int,int> 用起来比较方便。
PII tempPair;//临时结点
char Map[M][M];// 地图,存储地块信息
//---------图的路径搜索常用方向移动表示-------
int dx[4]= {0,1,-1,0};
int dy[4]= {1,0,0,-1};
// 两两组合形成上下左右四个方向
// 1------------------> x
// |
// |
// |
// |
// |
// |
// |
// ↓
// y
// dx[0]=0 dy[0]=1 那么代表向下的方向
// dx[1]=1 dy[1]=0 那么代表向右的方向
// dx[2]=-1 dy[2]=0 那么代表向左的方向
// dx[3]=0 dy[3]=-1 那么代表向上的方向
int n;// n 行
int m;// m 列
int k;// k 次/个月
queue<PII > q; //广度优先搜索所用的队列
int len;//记录节点数量方便后续k的计算
bool pd(int x, int y)
{
if(x<1)
return 0;
// /x 轴坐标 左侧越界
else if(x>n)
return 0;
//x 轴坐标 右侧越界
else if(y<1)
return 0;
//y 轴坐标 上侧越界
else if(y>m)
return 0;
//y 轴坐标 下侧越界
else if(Map[x][y]=='g')
return 0;
//已经长草了
else return 1;
// 在范围内,且没长草
}
void BFS()
{
//BFS
while(!q.empty()&&k>0)
{
tempPair = q.front();// 获取队首节点
q.pop();// 弹出队首节点
//这两步是取出队首的节点
int x = tempPair.first;//获取横坐标
int y = tempPair.second;//获取纵坐标
// 遍历四个方向,上下左右
for(int i=0; i<4; i++)
{
int nowx = x+dx[i]; //扩展后的横坐标
int nowy = y+dy[i]; //扩展后的纵坐标
if(pd(nowx,nowy))
{
q.push({nowx,nowy});// 将满足条件的坐标加入队列
Map[nowx][nowy]='g';// 标记为已长草
}
//符合要求执行扩展,不符合要求,忽略即可。
}
len--; //每取出一个节点 len-1
if(len==0)
{
//当len =0 时,代表当前层扩展完了,那么就代表第一个月扩展完了
k--; // 所以k--
len = q.size(); // 当前层扩展完了,那就该扩展下一层了,所以len又被赋值为下一层的节点数目的值
}
}
}
int main()
{
cin>>n>>m;// 输入行数和列数
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>Map[i][j];// 输入地块信息
if(Map[i][j]=='g')
{
tempPair.first=i;
tempPair.second=j;
q.push(tempPair);//将初始有树的结点加入队列
}
}
}
len = q.size();//记录第一层的节点数量方便后续k的计算
cin>>k;//输入月数/次数
BFS();
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cout<<Map[i][j];// 输出结果地图
}
cout<<endl;
}
return 0;
}
题目:走迷宫
#include<bits/stdc++.h> // 包含常用的标准库头文件
using namespace std; // 使用标准命名空间
int vis[150][150]; // 用于存储是否访问过,并且存储长度
char G[150][150]; // 用于存储题目给出的地图
int n,m,ans=0; // n、m分别表示地图的行数和列数,ans用于存储最终结果
int dx[4]={0,0,-1,1}; // 方向数组,表示四个方向的x坐标变化
int dy[4]={1,-1,0,0}; // 方向数组,表示四个方向的y坐标变化
struct node{ // 定义结构体node,表示坐标点
int x;
int y;
};
node Start,End; // 分别表示起点和终点的坐标
bool pd(int x,int y){ // 判断坐标(x,y)是否符合条件
if(x<1)
return 0;
else if(x>n)
return 0;
else if(y<1)
return 0;
else if(y>m)
return 0;
else if(vis[x][y]!=0) // 已经访问过了
return 0;
else if(G[x][y]!='1') // 不是路不能走
return 0;
else
return 1;
}
bool check(int x,int y){ // 判断是否到达终点
if(x==End.x&&y==End.y){ // 找到终点,把距离给他
ans=vis[x][y];
return 1;
}
else
return 0;
}
void bfs(){ // 广度优先搜索
queue<node>q; // 定义队列q,用于存储待访问的节点
node now,next; // 定义当前节点和下一个节点
q.push(Start); // 将起点压入队列中
vis[Start.x][Start.y]=1; // 标记起点已经访问
while(!q.empty()){ // 当队列不为空时循环
now = q.front();
if(check(now.x,now.y)) // 判断是否到达终点
return ;
q.pop(); // 将队列最前面的弹出
for(int i=0;i<4;i++){ // 遍历四个方向
int nextx = now.x+dx[i];
int nexty = now.y+dy[i];
if(pd(nextx,nexty)){ // 判断下一个节点是否符合条件
next.x=nextx;
next.y=nexty;
q.push(next); // 将下一个节点压入队列中
vis[nextx][nexty] = vis[now.x][now.y]+1; // 步数+1
}
}
}
}
int main(){
cin>>n>>m; // 输入地图的行数和列数
for(int i=1;i<=n;i++){ // 输入地图信息
for(int j=1;j<=m;j++){
cin>>G[i][j];
}
}
cin>>Start.x>>Start.y>>End.x>>End.y; // 输入起点和终点坐标
ans=0;
bfs(); // 调用广度优先搜索算法
cout<<ans-1<<endl; // 输出结果
return 0;
}
题目:迷宫
此题用BFS搜索记录路径,用DFS打印路径
#include<bits/stdc++.h>
using namespace std;
#define maxn 2000// 定义迷宫大小
string maze[maxn]={// 迷宫地图,0代表可通行,1代表墙壁
"01010101001011001001010110010110100100001000101010",
"00001000100000101010010000100000001001100110100101",
"01111011010010001000001101001011100011000000010000",
"01000000001010100011010000101000001010101011001011",
"00011111000000101000010010100010100000101100000000",
"11001000110101000010101100011010011010101011110111",
"00011011010101001001001010000001000101001110000000",
"10100000101000100110101010111110011000010000111010",
"00111000001010100001100010000001000101001100001001",
"11000110100001110010001001010101010101010001101000",
"00010000100100000101001010101110100010101010000101",
"11100100101001001000010000010101010100100100010100",
"00000010000000101011001111010001100000101010100011",
"10101010011100001000011000010110011110110100001000",
"10101010100001101010100101000010100000111011101001",
"10000000101100010000101100101101001011100000000100",
"10101001000000010100100001000100000100011110101001",
"00101001010101101001010100011010101101110000110101",
"11001010000100001100000010100101000001000111000010",
"00001000110000110101101000000100101001001000011101",
"10100101000101000000001110110010110101101010100001",
"00101000010000110101010000100010001001000100010101",
"10100001000110010001000010101001010101011111010010",
"00000100101000000110010100101001000001000000000010",
"11010000001001110111001001000011101001011011101000",
"00000110100010001000100000001000011101000000110011",
"10101000101000100010001111100010101001010000001000",
"10000010100101001010110000000100101010001011101000",
"00111100001000010000000110111000000001000000001011",
"10000001100111010111010001000110111010101101111000"
};
bool vis[maxn][maxn];// 标记数组,标记是否访问过某个位置
int dir[4][2]={{1,0},{0,-1},{0,1},{-1,0}};//D L R U
bool in(int x,int y){// 判断坐标是否在迷宫范围内
return x<30&&x>=0&&y>=0&&y<50;
}
struct node{
int x,y,d;// x,y表示坐标,d表示步数
char pos;//存储路径信息:D L R U
};
node father[maxn][maxn];//当前节点的父节点
node now,nex;//指向当前和下一个位置
void dfs(int x,int y){ //递归打印
if(x==0&&y==0) //找到起点 开始正向打印路径
return;
else
dfs(father[x][y].x,father[x][y].y);// 递归打印父节点
cout<<father [x][y].pos;// 打印当前节点的移动方向
}
void bfs(int x,int y){
queue<node> q;
now.x=x;
now.y=y;
now.d=0;
q.push(now);// 将起点加入队列
vis[x][y]=true;// 标记起点已访问
while(!q.empty()){
now=q.front(); // 取出队首节点
q.pop();
for(int i=0;i<4;i++){
int tx=now.x+dir[i][0];// 计算下一个位置的坐标
int ty=now.y+dir[i][1];
if(in(tx,ty)&&!vis[tx][ty]&&maze[tx][ty]!='1'){// 判断下一个位置是否合法
vis[tx][ty]=true;// 标记下一个位置已访问
nex.x=tx;
nex.y=ty;
nex.d=now.d+1;// 步数加一
q.push(nex);//将下一个节点压入队列
father[tx][ty].x=now.x;
father[tx][ty].y=now.y;
if(i==0)//存储路径
father[tx][ty].pos='D';
else if(i==1)
father[tx][ty].pos='L';
else if(i==2)
father[tx][ty].pos='R';
else if(i==3)
father[tx][ty].pos='U';
}
}
}
}
int main(){
bfs(0,0);
dfs(29,49);//30行50列,从0开始
return 0;
}