深度优先搜索(DFS)
(1)概念
从最开始的状态开始一条路走到底,如果不能寻找到最优解,就从上一个点,再走到底,直到找到最优解。
(2)算法模板
(3)适用的题型范围
给定一个初始状态和一个目标状态,要求判断这个初始状态到目标状态是否有解。
a 迷宫
思考思路
完整代码示例:
package com.grm.algorithm.dfs.migong;
/**
* dfs深搜-迷宫最短路径
*
* @author: gaorimao
* @since: 2021-8-2
*/
public class DFSMaze {
// 障碍物
private static final int STONE = 1;
// 定义一个二维数组来实现移动
public static int[][] direct = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
// 定义变量记录走出迷宫最少的步数
private static int stepMin = Integer.MAX_VALUE;
// 已经走过
private static int[][] walked = null;
// 最大行列索引
private static int xMax;
private static int yMax;
public static void main(String[] args) {
// 迷宫数据
int[][] mazeData = {
{ 0, 0, 1, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 1, 0 },
{ 0, 1, 0, 0 },
{ 0, 0, 0, 1 } };
queryMazeMinPath(mazeData, 0, 0, 3, 2);
}
/**
* desc:
*
* @param datas 迷宫
* @param startX 开始行索引
* @param startY 开始列索引
* @param endX 结束行索引
* @param endY 结束列索引
*/
public static void queryMazeMinPath(int[][] datas, int startX, int startY, int endX, int endY) {
//求x,y边界
yMax = datas[0].length;
xMax = datas.length;
//初始化walked数组,初始位置记为2
walked = new int[xMax][yMax];
walked[startX][startY] = 2;
// 只要调用一次dfs就相对于走了一步,所以一开始传入1
dfs(datas, startX, startY, endX, endY, 1);
System.out.println("最小路径是:" + stepMin + "步");
}
private static void dfs(int[][] datas, int xCur, int yCur, int outX, int outY, int step) {
for (int i = 0; i < direct.length; i++) {
//下一步的坐标位置
int xNew = xCur + direct[i][0];
int yNew = yCur + direct[i][1];
// 防止走出迷宫界限之外
if (xNew >= xMax || yNew >= yMax || xNew < 0 || yNew < 0) {
continue;
}
// 确定是否已经走过
if (walked[xNew][yNew] == 2) {
continue;
}
// 是否为障碍物
if (datas[xNew][yNew] == STONE) {
continue;
}
// 出口
if (xNew == outX && yNew == outY) {
if (step < stepMin) {
stepMin = step;
}
System.out.println("########### 找到出口坐标:" + xNew + "," + yNew + "走了:" + step + "步");
//回溯
return;
}
System.out.println("移动到了 " + xNew + "," + yNew + "此位置:" + datas[xNew][yNew]);
walked[xNew][yNew] = 2;
dfs(datas, xNew, yNew, outX, outY, step + 1);
}
}
}
运行结果
移动到了 0,1此位置:0
移动到了 0,2此位置:0
移动到了 0,3此位置:0
移动到了 0,4此位置:0
移动到了 1,4此位置:0
移动到了 2,4此位置:0
移动到了 2,3此位置:0
移动到了 3,3此位置:0
########### 找到出口坐标:3,2走了:9步
移动到了 1,2此位置:0
移动到了 1,1此位置:0
移动到了 2,1此位置:0
移动到了 3,1此位置:0
########### 找到出口坐标:3,2走了:7步
移动到了 1,0此位置:0
最小路径是:7步
b 求排列组合数
输出1~n的全排列
代码
package com.grm.algorithm.dfs.quanpailie;
import java.util.Scanner;
/**
* dfs实现1~n的全排列
*
* @author gaorimao
* @date 2021/08/03
*/
public class QuanPaiLie {
static int n;
static int[] arr;//用来记录已经在排列中的数字
static int[] brr;//分别标记数字是否放置在了位置上,如已经放置值用1表示,没放置用0。
/**
* dfs
*
* @param step 步伐
*/
static void dfs(int step) {
if (step == n) {//边界输出,位置是从0开始的,如果将卡片放完一遍,则退出
for (int i = 0; i < n; i++) { //打印每个位置上所放置的卡片号
System.out.print(arr[i] + " ");
}
System.out.println();
} else if (step < n) {
for (int i = 1; i <= n; i++) {//每一次循环都将i作为第一个数进行排列
if (brr[i] == 0) {//判断是否重复,相当于check方法是否满足条件
brr[i] = 1;//标记已经使用
arr[step] = i;
dfs(step + 1);//进行下一步
brr[i] = 0;//清空标记
}
}
} else {
return;
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
arr = new int[n + 1];
brr = new int[n + 1];
dfs(0);
}
}
结果
3
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
广度优先搜索(BFS)
(1)概念
类似于树的按层次遍历的过程,则是以广度为第一关键词,当碰到岔道口时,总是先依次访问从该岔道口能直接到达的所有结点,然后再按这些结点被访问的顺序去依次访问它们能直接到达的所有结点,以此类推,直到所有结点都被访问为止。
(2)算法模板
public void bfs(int s){
Queue<Integer> queue=new LinkedList<Integer>();
queue.offer(s);
while(!queue.isEmpty()){
//取出队首元素top
//访问队首元素top
//将队首元素出队
//将top的下一层结点中未曾入队的结点全部入队,并设置为已入队
}
}
①定义队列q,并将起点s入队。
②写一个 while循环,循环条件是队列q非空。
③在while循环中,先取出队首元素top,然后访问它(访问可以是任何事情,例如将其输出)。访问完后将其出队。
④将top的下一层结点中所有未曾入队的结点入队,并标记它们的层号为now的层号加1,同时设置这些入队的结点已入过队。
⑤返回②继续循环。
(3)适用的题型范围
因为BFS问题是一层一层的搜索,所以它非常适合搜索最短路,找到的第一个合法解就是最优解,即层数
a 迷宫问题
代码实现
package com.grm.algorithm.bfs;
import java.util.LinkedList;
/**
* bfs迷宫
*
* @author gaorimao
* @since 2021/08/03
*/
public class BfsMaze {
public static void main(String[] args) {
int[][] array={
{0,0,1,0},
{0,0,0,0},
{0,0,1,0},
{0,1,0,0},
{0,0,0,1}
};
new BfsMaze().findMinRold(array,0,0,3,2);
}
public void findMinRold(int[][] arr,int startX,int startY,int endX,int endY) {
//求边界
int xMax = arr.length;
int yMax = arr[0].length;
//定义 上下左右四个方向
int[][] direction = {{1,0},{-1,0},{0,1},{0,-1}};
//创建队列
LinkedList<Node> queue = new LinkedList<>();
//创建开始节点
Node start = new Node(startX,startY,0,null);
//把开始节点放入队列中,并做下标记,在原来数组中做标记
queue.offer(start);
arr[startX][startY] = 1;
//循环操作队列,进行广度遍历
Node temp = null;
ok:
while(!queue.isEmpty()) {
temp = queue.poll();
//依次遍历这个节点的四个方向,查找还没有遍历的相连节点
for(int i = 0 ; i < 4 ; i++) {
int newX = temp.x + direction[i][0];
int newY = temp.y + direction[i][1];
//是否越界,坐标
if(newX < 0 || newX >= xMax || newY < 0 || newY >= yMax) {
continue;
}
//判断是否该节点可以通过
if(arr[newX][newY] == 1) {
continue;
}
//构造节点
Node next = new Node(newX , newY , temp.dis+1 , temp);
//该节点可以通过,判断该节点是否是最终节点
if(newX == endX && newY == endY) {
queue.clear();
//相当于头插法,转置
queue.offerFirst(next);
Node preNode = next.pre;
while(preNode != null) {
queue.offerFirst(preNode);
preNode = preNode.pre;
}
int len = queue.size();
System.out.println("最短路径长度为:"+(len-1));
for(Node it:queue) {
System.out.println("("+it.x+","+it.y+") ");
}
break ok;
}
arr[newX][newY] = 1;
queue.offer(next);
}
}
}
//定义图的节点信息
class Node{
//定义坐标和距离第一个节点的距离
int x;
int y;
int dis;
//定义节点的前缀,用于绘制整个最短路径的 线路图
Node pre;
public Node(int x , int y , int dis , Node pre) {
this.x = x;
this.y = y;
this.dis = dis;
this.pre = pre;
}
}
}
输出结果
最短路径长度为:7
(0,0)
(1,0)
(2,0)
(3,0)
(4,0)
(4,1)
(4,2)
(3,2)