蓝桥杯算法合集: 蓝桥杯算法合集(终极完结版)
思想:
DFS的主要的思想就是一条路走到黑。例如:有N个阶段,第N个阶段走不通,就回退到第N-1个阶段尝试其他的可能。
而BFS维护的是一种层次关系,按照状态的层次扩展,先搜完走一步能到达的所有点,每次离根节点越近的越先扩展。
应用:
当要求最短路时,可以考虑用BFS(当然DFS在数据范围比较小的时候也OK),要求所有符合条件的情况时,考虑用DFS
BFS,跟DFS差别不太大,还是由三个部分组成——出口、标记、枚举。
状态保存: DFS递归传参存储状态,而BFS利用结构体如自定义Node类
标记: 常见字符串、数组、 HashMap标记
bfs的常规操作:
建立标志数据(记录某点是否被访问过)
起点入队
一个判断队列是否为空的循环:{
取出队首元素 pop队首元素
判断是否已经到达终点 判断各个分支是否满足入队要求.
}
模板:
void bfs(起始点){
将起始点放入队列中;
标记起始点;
while(队列非空){
删除队首元素x;
for(x所有相邻点){
if(该点未被访问过){
加标记;
将该点加入队列末尾;}
}
}
队列为空,广搜结束;
}
常见错误:
1、三个if是可能都会进 而if else if else 是只进一个
2、分支标记访问过要在进队列前
3、java中对象A=对象B,是引用。A=B,改变A的值会引起B的改变,这点要注意,不然bfs会出问题。
所以通常要new带参方法传参B的值或者克隆
public static Nod clo(Nod a) {//传值不影响本身
Nod b = new Nod();
b.num[0] = a.num[0];
b.num[1] = a.num[1];
b.num[2] = a.num[2];
b.num[3] = a.num[3];
b.step = a.step;
return b;
}
常见以下四种
1)将A对象的值分别通过set方法加入B对象中;
(2)通过重写java.lang.Object类中的方法clone();
(3)通过org.apache.commons中的工具类BeanUtils和PropertyUtils进行对象复制;
(4)通过序列化实现对象的复制。
给一个n行m列的迷宫,'S'表示迷宫的起点,'T'表示迷宫的终点,
'*'表示不能通过的点,'.'表示可以通过的点。现在要求从'S'出发走到'T',
每次只能上下左右走动,并且只能进入能通过的点,每个点只能通过一次,
问是否能够到达终点可以的话求出最短路,如果不可以输出-1。
测试数据:
5 6
....S*
.***..
.*..*.
*.***.
.T....
答案:9
5 6
....S*
.**...
.*..*.
*..**.
.T....
答案:7
//用ret数组存放步数
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class bfs求迷宫最短路 {
static int n,m;
static char[][]maze;
static boolean[][]vis;
static int[][] dir= {{-1,0},{0,-1},{1,0},{0,1}};//上 左 下 右
static int[][] ret;
static Queue<Node> q=new LinkedList<Node>();
static class Node {
int x,y;
Node(int _x,int _y){
x=_x;
y=_y;
}
}
static int bfs(int sx,int sy) {
q.offer(new Node(sx,sy));//初始时先把起始点放在队列中
vis[sx][sy]=true;//并标记为访问过
ret[sx][sy]=0;
while(!q.isEmpty()) {
Node now=q.peek();//用一个结点指向队首
q.poll();//弹出队首元素 此时队列的第一个元素为原先队首的下一个
int u=now.x;
int v=now.y;
for(int i=0;i<4;i++) {//与队首相连的所有没有被访问过的点v入队并且标记为访问过(加的同时就标记为已经访问过了)
int tx=u+dir[i][0];
int ty=v+dir[i][1];
if(tx>=0&&tx<n&&ty>=0&&ty<m&&maze[tx][ty]!='*'&&!vis[tx][ty]) {
if(maze[tx][ty]=='T') {
return ret[u][v]+1;//上一个状态是ret[u][v]
//所以应该返回ret[u][v]+1而不是ret[tx][ty]+1
}else {
vis[tx][ty]=true;//要在加入时标记 不然就一直 绕圈
ret[tx][ty]=ret[u][v]+1;
//System.out.println(ret[tx][ty]);
q.offer(new Node(tx,ty));//按步数大小顺序(层次)入队列
}
}
}
}
return -1;
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
n=reader.nextInt();
m=reader.nextInt();
int x = 0,y = 0;//局部变量需初始化
maze=new char[n][m];
vis=new boolean[n][m];
ret=new int[n][m];
for (int i = 0; i <n; i++) {
String s=reader.next();
for (int j = 0; j < m; j++) {
if(s.charAt(j)=='S') {
x=i;
y=j;
}
maze[i][j]=s.charAt(j);
}
}
System.out.println(bfs(x,y));
}
}
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class bfs求迷宫最短路3 {
static char[][]map;
static boolean[][]vis;
static int[][]dir= {{-1,0},{0,-1},{1,0},{0,1}};
static class Node{
int x,y,step;
Node(int x,int y,int step){
this.x=x;
this.y=y;
this.step=step;
}
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
int n=reader.nextInt();
int m=reader.nextInt();
map=new char[n][m];
vis=new boolean[n][m];
int sx=0;
int sy=0;
for(int i=0;i<n;i++) {
String s=reader.next();
for(int j=0;j<m;j++) {
map[i][j]=s.charAt(j);
if(s.charAt(j)=='S') {
sx=i;
sy=j;
}
}
}
Queue<Node> q = new LinkedList<Node>();
q.offer(new Node (sx,sy,0));
vis[sx][sy]=true;
while(!q.isEmpty()) {
Node now=q.peek();
q.poll();
for(int i=0;i<4;i++) {
int nx=now.x+dir[i][0];
int ny=now.y+dir[i][1];
if(nx>=0&&nx<n&&ny>=0&&ny<m&&map[nx][ny]!='*'&&!vis[nx][ny]) {
if(map[nx][ny]=='T') {
System.out.println(now.step+1);
System.exit(0);
}
vis[nx][ny]=true;//每入栈一个点先标记为访问过
q.offer(new Node(nx,ny,now.step+1));
}
}
}
System.out.println(-1);
}
}
一维坐标的移动
在一个长度为n的坐标轴上,蒜头君想从A点 移动到B点。他的移动规则如下:
向前一步,坐标增加1。
向后一步,坐标减少1。
跳跃一步,使得坐标乘2。
蒜头君不能移动到坐标小于0或大于n的位置。蒜头想知道从A点移动到B点的最少步数是多少,你能帮他计算出来么?
输入格式
第一行输入三个整数n,A,B,分别代表坐标轴长度,起始点坐标,终点坐标。(0≤A,B≤n≤50000)
输出格式
输出一个整数占一行,代表蒜头要走的最少步数。
样例输入
10 2 7
样例输出
3
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
//步数是固定增加的用bfs
public class 一维坐标的移动2 {
static class Node{
int x,y,d;
Node(int xx,int dd){
x=xx;
d=dd;
}
}
static boolean[] vis;
static int A,B,n,now,step;
static Queue<Node> q=new LinkedList<Node>();;
static void bfs(int a,int b ) {
q.offer(new Node(a,b));//起点入队
vis[A]=true;//标记为访问过
while(!q.isEmpty()) {//判断队列是否为空循环
Node node=q.peek();//取队首
now=node.x;
step=node.d;
q.poll();//pop队首元素
if(now==B) {//有出口
System.out.println(step);
break;
}
//三种走法
//注意标记
if(now+1<=n&&!vis[now+1]) {
q.offer(new Node(now+1,step+1));
vis[now+1]=true;
}
if(now-1>=0&&!vis[now-1]) {
q.offer(new Node(now-1,step+1));
vis[now-1]=true;
}
if(now*2<=n&&!vis[now*2]) {
q.offer(new Node(now*2,step+1));
vis[now*2]=true;
}
}
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
n=reader.nextInt();
A=reader.nextInt();
B=reader.nextInt();
vis=new boolean[n+1];
bfs(A,0);
}
}
Farmer John一直努力让他的草地充满鲜美多汁的而又健康的牧草。
可惜天不从人愿,他在植物大战人类中败下阵来。
邪恶的乳草已经在他的农场的西北部份佔领了一片立足之地。
草地像往常一样,被分割成一个高度為Y(1 < = y < = 100),
宽度為X(1 < = x < = 100)的直角网格。(1,1)是左下角的格
(也就是说坐标排布跟一般的X,Y坐标相同)。乳草一开始占领了格(Mx,My)。
每个星期,乳草传播到已被乳草佔领的格子四面八方的每一个没有很多石头的格
(包括垂直与水平相邻的和对角线上相邻的格)。1周之后,
这些新占领的格又可以把乳草传播到更多的格裡面了。
Bessie想要在草地被乳草完全佔领之前尽可能的享用所有的牧草。
她很好奇到底乳草要多久才能佔领整个草地。如果乳草在0时刻处於格(Mx,My),
那麼还在那个时刻它们可以完全佔领入侵整片草地呢(对给定的数据总是会发生)?
草地由一个图片表示。" ." 表示草,而" *" 表示大石。比如这个X=4, Y=3的例子。
....
..*.
.**.
如果乳草一开始在左下角(第1排,第1列),那麼草地的地图将会以如下态势发展:
.... .... MMM. MMMM MMMM
..*. MM*. MM*. MM*M MM*M
M**. M**. M**. M**. M**M
星期数 0 1 2 3 4
乳草会在4星期后占领整片土地。
输入格式:
第一行: 四个由空格隔开的整数: X, Y, Mx, My
第2到第Y+1行: 数据的第y+1行由X个字符(" ." 表示草地," *" 表示大石),描述草地的第(Y+2-y)行。
输出格式:
一个单独的整数表示最后一个不是大石块的格子被乳草佔领的星期数。
测试数据:
4 3 1 1
....
..*.
.**.
答案:4
package 广度优先搜索;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class 乳草的侵占 {
static class Node{
int x,y,ans;
Node(int _x,int _y,int _ans) {//结点存储当前格子的状态
x=_x;
y=_y;
ans=_ans;
}
Node(){
}
}
static int X,Y,sx,sy,an;
static char[][] grass;
static boolean[][] vis;
//上 左上 左 左下 下 逆时针
static int dir[][]= {{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}};
static Queue<Node> q=new LinkedList<Node>();
static boolean in(int x,int y) {
return x>=0&&x<Y&&y>=0&&y<X;
}
static int bfs() {
Node first=new Node(sx,sy,0);
q.offer(first);//起点以结点形式入队列
vis[sx][sy]=true;//标记为访问过
while(!q.isEmpty()) {
Node now=q.peek();//取队首
an=now.ans;
q.poll();//出队首
for(int i=0;i<8;i++) {//枚举所有入队列分支
int tx=now.x+dir[i][0];
int ty=now.y+dir[i][1];
if(in(tx,ty)&&!vis[tx][ty]&&grass[tx][ty]!='*') {
vis[tx][ty]=true;
q.offer(new Node(tx,ty,now.ans+1));
}
}
}
return an;//当队列为空 地图上所有点都遍历过了 ans为最远那个点 即为最大值
}
public static void main(String[] args) throws IOException {
Scanner reader=new Scanner(System.in);
X=reader.nextInt();
Y=reader.nextInt();
sx=reader.nextInt();
sy=reader.nextInt();
grass=new char[Y][X];
vis=new boolean[Y][X];
//System.out.println(X+" "+Y+" "+sx+" "+sy);
for(int i=0;i<Y;i++) {
String s=reader.next();
for(int j=0;j<X;j++) {
grass[i][j]=s.charAt(j);
//if(s.charAt(j)=='*')
}
}
//输入的X,Y对应原矩阵的位置
sx=Y-sx;
sy=sy-1;
System.out.println(bfs());
}
}
蒜头君要回家,但是他家的钥匙在他的朋友花椰妹手里,
他要先从花椰妹手里取得钥匙才能回到家。
花椰妹告诉他:“你家的钥匙被我复制了很多个,分别放在不同的地方。”
蒜头君希望能尽快回到家中,他需要首先取得任意一把钥匙,
请你帮他计算出回家所需要的最短路程。
蒜头君生活的城市可以看做是一个 n×m 的网格,其中有道路有障碍,
钥匙和家所在的地方可以看做是道路,可以通过。
蒜头君可以在城市中沿着上下左右 4 个方向移动,移动一个格子算做走一步。
输入格式
第一行有两个整数 n,m。城市的地图是 n 行 m 列。(1≤n,m≤2000)
接下来的 n 行,每行 m 个字符,代表城市的地图。
’.’ 代表道路,’#’ 代表障碍物,’S’ 代表蒜头君所在的位置,
’T’ 代表蒜头家的位置,’P’代表钥匙的位置。除了障碍物以外,
别的地方都可以通过。(题目保证蒜头君至少有一条路径可以顺利拿到钥匙并且回家)
输出格式
输出蒜头回家要走的最少步数,占一行。
测试数据:
8 10
P.##.#P#
..#..#...#
..#T#.#.#
..........
..#.###
..........
###...#
##....S#
答案:21
思路:做两次bfs
* ret数组记录到从搜索起点到每一个点的步数
* 从起点往终点搜一次再从终点往起点搜
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class 蒜头君回家 {
static class Node{
int x,y;
Node(int _x,int _y){
x=_x;
y=_y;
}
}
static int n,m;
static boolean vis[][];
static char mp[][];
static int[][] dir= {{-1,0},{0,-1},{1,0},{0,1}};
//三维数组 一个维度用来表示是否取到钥匙
static int[][][] ret;
static boolean in(int x,int y) {
return x>=0&&x<n&&y>=0&&y<m;
}
static void bfs(int sx,int sy,int d) {
Queue<Node> q=new LinkedList<Node>();
vis[sx][sy]=true;
ret[d][sx][sy]=0;//设置起点步数为0 用ret数组记录到每一个点的步数
//d=0为从S出发到每一个点的步数 d=1为从T出发到每一个点的步数
q.offer(new Node(sx,sy));
while(!q.isEmpty()) {
Node now=q.peek();
q.poll();
int u=now.x;
int v=now.y;
for(int i=0;i<4;i++) {
int tx=u+dir[i][0];
int ty=v+dir[i][1];
if(in(tx,ty)&&!vis[tx][ty]&&mp[tx][ty]!='#') {
ret[d][tx][ty]=ret[d][u][v]+1;//下一个点为上一个点步数加1
vis[tx][ty]=true;//标记为走过
q.offer(new Node(tx,ty));
}
}
}
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
Queue<Node> q=new LinkedList<Node>();
n=reader.nextInt();
m=reader.nextInt();
vis=new boolean[n][m];
mp=new char[n][m];
ret=new int[2][n][m];
int sx=0,tx=0;
int sy=0,ty=0;
for(int i=0;i<n;i++) {
String s=reader.next();
for(int j=0;j<m;j++) {
mp[i][j]=s.charAt(j);
if(mp[i][j]=='P') {//有多把钥匙
q.offer(new Node(i,j));//所有钥匙坐标都压进队列中
}
else if(mp[i][j]=='S')
{
sx=i;
sy=j;
}
else if(mp[i][j]=='T')
{
tx=i;
ty=j;
}
}
}
bfs(sx,sy,0);//d为0表示从起点S出发到各个点钥匙的距离 因为bfs不像dfs 它是没有回溯取消标记的
for(int i=0;i<n;i++) {//重置 一次bfs后vis已经全部被标记过了
for(int j=0;j<m;j++) {
System.out.print(vis[i][j]+" ");
}
System.out.println();
}
// for(int i=0;i
// for(int j=0;j
// vis[i][j]=false;
// }
// }
bfs(tx,ty,1);//1表示从终点点T出发到各个点钥匙的距离
// for(int i=0;i
// for(int j=0;j
// System.out.print(ret[0][i][j]+" ");
// }
// System.out.println();
// }
// System.out.println();
// for(int i=0;i
// for(int j=0;j
// System.out.print(ret[1][i][j]+" ");
// }
// System.out.println();
// }
int ans=100000000;
//钥匙点可以看成中转站 不论是从起点出发还是从终点出发ret数组都存储了各自到这个点的最短距离
while(!q.isEmpty()) {
//依次取出钥匙坐标
int x=q.peek().x;
int y=q.peek().y;
q.poll();
ans=Math.min(ans, ret[0][x][y]+ret[1][x][y]);
}
System.out.println(ans);
}
}
next=a; //next 指向a next的四位数数组转动 a也跟着转动
密码锁
现在一个紧急的任务是打开一个密码锁。密码由四位数字组成,每个数字从 到 进行编号。每次
可以对任何一位数字加 或减 。当将 9 加 时,数字将变为 1 ,当 1 减 的时,数字将变
为 9 。你也可以交换相邻数字,每一个行动记做一步。现在你的任务是使用最小的步骤来打开锁。
注意:最左边的数字不与最右边的数字相邻。
输入格式
第一行输入四位数字,表示密码锁的初始状态。
第二行输入四位数字,表示开锁的密码。
输出格式
输出一个整数,表示最小步骤。
测试数据:
1234
2144
答案:2
1234
5687
答案:13
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class 密码锁 {
static class Node{
int []num=new int [4];
int step;
Node() {
}
Node (int a,int b,int c,int d){
num[0]=a;
num[1]=b;
num[2]=c;
num[3]=d;
}
}
static Node first=new Node();
static Node last=new Node();
static int[][][][] vis=new int[11][11][11][11];
static void bfs() {
int i;
Node a=new Node();
Queue<Node> q=new LinkedList<Node>();
a=first;//a指向first a改变first也要跟着改变
a.step=0; //起始步数要清为0
q.offer(a);
//vis数组表示搜没搜到这个状态
vis[a.num[0]][a.num[1]][a.num[2]][a.num[3]]=1;
while(!q.isEmpty()) {
a=q.peek();//当前的状态
q.poll();
//bfs出口
if(a.num[0]==last.num[0]&&a.num[1]==last.num[1]&&a.num[2]==last.num[2]&&a.num[3]==last.num[3]){
System.out.println(a.step);
return;
}
//下一步状态
for(i=0;i<4;i++) {//+1
Node next=new Node(a.num[0],a.num[1],a.num[2],a.num[3]);
next.num[i]++;
if(next.num[i]==10) {
next.num[i]=1;
}
if(vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]!=1) {
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=1;
next.step=a.step+1;
q.offer(next);
}
}
for(i=0;i<4;i++) {//-1
System.out.println(a.num[0]+""+a.num[1]+""+a.num[2]+""+a.num[3]);
Node next=new Node(a.num[0],a.num[1],a.num[2],a.num[3]);
next.num[i]--;
if(next.num[i]==0) {
next.num[i]=9;
}
System.out.println(next.num[0]+""+next.num[1]+""+next.num[2]+""+next.num[3]);
if(vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]!=1) {
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=1;
next.step=a.step+1;
q.offer(next);
}
}
for(i=0;i<3;i++) {
Node next=new Node(a.num[0],a.num[1],a.num[2],a.num[3]);
int temp=next.num[i];
next.num[i]=next.num[i+1];
next.num[i+1]=temp;
if(vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]!=1) {
vis[next.num[0]][next.num[1]][next.num[2]][next.num[3]]=1;
next.step=a.step+1;
q.offer(next);
}
}
}
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
String init=reader.next();
String password=reader.next();
for(int i=0;i<4;i++) {
first.num[i]=init.charAt(i)-'0';
last.num[i]=password.charAt(i)-'0';
}
bfs();
}
}
package 广度优先搜索;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class 打水滴 {
static class Node{//记录格子的状态
int x,y,pos,tm;
Node(int _x,int _y,int p,int t){
x=_x;
y=_y;
tm=t;
pos=t;
}
Node(){
}
}
static int n,m,L,q;
static boolean[][] vis;
static int[][] sum;
static int [][]tm; //记录时间
//上 左 下 右
static int[][] dir= {{-1,0},{0,-1},{1,0},{0,1}};
static void bfs(int x,int y) {
Queue<Node> q=new LinkedList<Node>();
Node cur=new Node();
//该位置加上水滴
sum[x][y]++;
if(sum[x][y]>L) {//x,y这个网格水滴数大于L
for(int i=0;i<4;i++) {
//结点记录网格位置 水滴破裂的时间和飞出方向
cur.x=x;
cur.y=y;
cur.pos=i;
cur.tm=0;
q.offer(cur);//压入队列
}
//该网格水滴置为0 时间为0
sum[x][y]=0;
tm[x][y]=0;
}
//搜索
while(!q.isEmpty()) {
cur=q.poll();
int xx=cur.x+dir[cur.pos][0];
int yy=cur.y+dir[cur.pos][1];
int ttm=cur.tm;
if(xx<0||xx>=n||yy<0||yy>=m) {
continue;
}
//再ttm+1秒破灭过 处理同一秒多个水滴进入一个格子
if(tm[xx][yy]==ttm+1) {
continue;
}else if(sum[xx][yy]>0){
sum[xx][yy]++;
if(sum[xx][yy]>L) {
sum[xx][yy]=0;
tm[xx][yy]=ttm+1;
for(int i=0;i<4;i++) {
Node temp=new Node();
temp.x=xx;
temp.y=yy;
temp.pos=i;
temp.tm=ttm+1;
q.offer(temp);
}
}
}else {
//该网格水滴为0 记录时间和要传递的方向
Node temp=new Node();
temp.x=xx;
temp.y=yy;
temp.pos=cur.pos;
temp.tm=ttm+1;
q.offer(temp);
}
}
}
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
n=reader.nextInt();
m=reader.nextInt();
L=reader.nextInt();
vis=new boolean[n][m];
sum=new int[n][m];
tm=new int[n][m];
//网格
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
sum[i][j]=reader.nextInt();
}
}
q=reader.nextInt();//打水滴的次数
//每次放一个水滴 bfs 去看一下有哪些水滴破裂
for(int i=0; i<q;i++) {
int x=reader.nextInt()-1;
int y=reader.nextInt()-1;
bfs(x,y);
}
int ss=0;
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
ss+=sum[i][j];
}
}
if(ss==0) {
System.out.println("Yes");
}
else
System.out.println("NO");
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
System.out.print(sum[i][j]+" ");
}
System.out.println();
}
}
}