B F S BFS BFS 和 D F S DFS DFS 的思路
B F S BFS BFS(广度优先搜索)
思路: B F S BFS BFS 类似于树的层次遍历过程,从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。舍去空间换时间(每一个的状态都需要用一个对象存储起来,放到一个队列中去)。
实现:
1.创建一个空队列 q u e u e queue queue(用来存放节点)和一个空列表 v i s i t visit visit(用来存放已访问的节点)
2.依次将起始点及邻接点加入 q u e u e queue queue 和 v i s i t visit visit 中
3. p o l l poll poll 出队列中最先进入的节点,从图中获取该节点的邻接点
4.如果邻接点不在 v i s i t visit visit 中(这个情况还没有判断),则将该邻接点加入 q u e u e queue queue 和 v i s i t visit visit 中
5.输出 p o l l poll poll 出的节点
6.重复 3 3 3、 4 4 4、 5 5 5,直至队列为空
D F S DFS DFS(深度优先搜索)
思路:沿着树的深度遍历树的节点,选一条路一直走到底,回到之前有路的点,换一个方向继续走,知道遍历所有的子节点(所有情况全部判断完成),进而达到全局搜索的目的。
全排列
D F S DFS DFS 解法
思路:将此过程看做一棵树,每一个结点下都会有 n n n 个结点表示下一个数,首先先将全部 n n n^n nn 个结果全部得出,然后剪枝,减去有重复数字出现的情况。
public static void dfs(int depth,String ans,int n){//当前深搜的层数,目前的结果,目标层数
if(depth==n){//当前深搜层数=目标层数
System.out.println(ans);
return;
}
for(int i=1;i<=n;i++){
if(!ans.contains(i+""))//只有当还没有用过i的时候,才会在现在的基础上继续往下拓展
dfs(depth+1,ans+i;n);//进入下一层,ans记录为进入下一层的值,n不变
}
}
B F S BFS BFS 解法
思路:先将有重复数字的结果得出,每一个数后都可以跟 n n n 中可能,那么将这 n n n 中可能存入队列中,然后重复此过程,直到字符串的长度为 n n n 时,得到结果;剪枝,如果这个数字已经用过了,就直接只用下一个数字。
public static void bfs(int n){
Queue<String> queue = new LinkedList<>();
for(int i=1;i<=n;i++)
queue.offer(i+"");
while(!queue.isEmpty()){
String now = queue.poll();
for(int i=1;i<=n;i++){//每个结点都向下产生n个结果
if(now.contains(i+""))//i已经使用过了
continue;
String son = now + i;
if(son.length()==n)
System.out.println(son);
else
queue.offer(son);
}
}
}
整数划分
思路:对 n n n 进行划分后, n n n 可以被不超过 n n n 个数累加得到,进行累加的每一个数,也可以被不超过它本身个数累加得到。
public static void dfs(int n,int nowget,int max,String ans){//要划分的数,现在已经得到的值,目前划分已经用到的最大值,具体拆分方法
if(nowget==n){
ans = ans.substring(0,ans.length()-1);
System.out.println(n+"="+ans);
return;
}
for(int i=1;i<=n-nowget;i++){//从nowget累加到n
if(i>=max)//只有当下一个数不小于我之前用过的最大值时,才能保证整个结果为非递减
dfs(n,nowget+i,i,ans+i+"+");
}
}
例题
例题:路径之谜
思路:
1.从入口点开始,到达每一个点都将对应位置北墙和西墙的箭靶数减一,每一个点,都可以继续向四个方向继续前进(前提是这个点没有走过,在城堡范围内,且这个点对应的两个箭靶的数字不为 0 0 0 )。
2.如果已经到了终点,就要判断现在每一个箭靶上的数字是否都已经变为 0 0 0 ,如果是,那么此时走的路径就是正确解,否则就需要回溯,考虑其他的行走路线。
3.回溯:因为要从已经走过的点退回来,所以在已经走过的点上射的箭要收回,箭靶数加一,并且标记此点为还没有走过。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class 路径之谜 {
static int[] path;//记录最终路径,因为底面为n*n,所以走出需要2*n步
static int n;
static int[] cntx;//存储北墙箭靶数字
static int[] cnty;//存储西墙箭靶数字
static boolean[][] visited;//判断此点有没有走过
static int dx[] = {1, 0, -1, 0};//到下一个点x坐标的变化量
static int dy[] = {0, 1, 0, -1};//到下一个点y坐标的变化量
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
n = Integer.parseInt(in.readLine());
cntx = new int[n];
cnty = new int[n];
path = new int[n * n];
visited = new boolean[n][n];
String[] s = in.readLine().split(" ");
for (int i = 0; i < n; i++) {
cntx[i] = Integer.parseInt(s[i]);
}
s = in.readLine().split(" ");
for (int i = 0; i < n; i++) {
cnty[i] = Integer.parseInt(s[i]);
}
dfs(0, 0, 0);//从0,0位置开始走,目前走了0步
}
private static void dfs(int x, int y, int step) {
path[step] = y * n + x; //将该点编号记录到路径中
visited[x][y] = true;//将该点标记为已经走过的状态
cntx[x]--;//拔掉对应北墙的箭
cnty[y]--;//拔掉对应西墙的箭
if (x == n - 1 && y == n - 1 && check()){//判断是否到达终点
for (int i = 0; i <= step; i++){//输出答案
System.out.print(path[i]+" ");
}
return;
}
for (int i = 0; i < 4; i++){//上下左右四个方向搜索下一步
int xx = x + dx[i], yy = y + dy[i];
//下一步(xx,yy)未走过且在地图范围内
if (0 <= xx && xx <= n-1 && yy >= 0 && yy <= n-1&& !visited[xx][yy] ){
if (cntx[xx] > 0 && cnty[yy] > 0){//该点对应箭靶上有箭,说明该点可以走
dfs(xx, yy, step + 1);//搜索下一步
//要从xx,yy点回来,在xx,yy点射的箭要复原,并重新标记xx,yy点没有走过
visited[xx][yy] = false;
cntx[xx]++;
cnty[yy]++;
}
}
}
}
private static boolean check() {//判断到达终点时,是否箭靶数都已经归零
for (int i = 0; i < n; i++) {
if (cntx[i] != 0 || cnty[i] != 0)
return false;
}
return true;
}
}
例题:迷宫
思路:从起点开始,将从此点能到达的点存储到队列中,每次获取并删除队列中的第一个元素,并将其能到达且还未到达过的点(若此点已经到达过,则表示当前处理的这条路径不是最短路径)存储到队列中,若已经到达终点,则此路径为最短路径。如果队列中已经没有元素,但仍未到达迷宫终点,则表示此迷宫无解
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Queue;
public class 迷宫 {
static int num;//存储迷宫最短路径所需要的步数
static int xsize = 30;//迷宫大小30行50列
static int ysize = 50;
static char[][] arr = new char[xsize][ysize];//存储迷宫:0表示路,1表示墙
static boolean[][] help = new boolean[xsize][ysize];//判断此点是否已经做过
static int[][] dir = {{1,0},{0,-1},{0,1},{-1,0}};//四个方向横纵坐标的变化量
static char[] sign = {'D','L','R','U'};//表示四个方向
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
for(int i=0;i<xsize;i++){
arr[i] = in.readLine().toCharArray();
}
out.println(bfs());
out.print(num);//额外输出最短路径需要多少步
out.flush();
}
private static String bfs() {
Queue<Node> list = new LinkedList<>();//队列
int x = 0;
int y = 0;
int runnum = 0;
list.add(new Node(x,y,"",runnum));//将起点存储到队列中
while(!list.isEmpty()){//判断队列是否为空,若为空,则此迷宫没有通路
Node now = list.poll();//获取队列中的第一个元素并删除
help[now.x][now.y] = true;//将此点标记为已经走过
for(int i=0;i<4;i++){//循环四次,对四个方向进行处理
int xx = now.x + dir[i][0];//移动后的x坐标
int yy = now.y + dir[i][1];//移动后的y坐标
//此点在迷宫范围内,未走过,不是墙
if(check(xx,yy) && help[xx][yy]==false && arr[xx][yy]=='0'){
list.add(new Node(xx,yy,now.num + sign[i],now.runnum + 1));//将此点存入队列中
if(xx==xsize-1 && yy==ysize-1){//如果已经到了迷宫终点
num = now.runnum + 1;//所需步数+1(now.runnum是到达迷宫终点前一步所需要的步数)
return now.num + sign[i];//返回通过迷宫的方式
}
}
}
}
return "";//空字符串,表示此迷宫无通路
}
private static boolean check(int xx, int yy) {//判断此点是否在迷宫范围内
return xx>=0 && yy>=0 && xx<xsize && yy<ysize;
}
static class Node{
int x;//x坐标
int y;//y坐标
int runnum;//到达此点最短步数
String num;//到达此点的方式
public Node(int x, int y,String num ,int runnum) {
super();
this.x = x;
this.y = y;
this.num = num;
this.runnum = runnum;
}
}
}