一、基本思路
1、DFS深度搜索——执着
- 数据结构:
-
- stack
- 空间复杂度:
-
- O(n)—> 只需要记录下路径上的点
- 特点:
-
-
- 不具有最短性
-
-
-
- 每一个节点会一条路走到黑,到达叶节点之后,然后进行回溯处理
-
- 重要概念:
-
- 剪枝:通过一定的判断条件,实现尚未到达叶节点的时候就返回上一层进行处理。
-
- 回溯:走到叶节点或者经过剪枝判断,返回上一层重新进行判断与分支,与此同质,最重的是需要恢复现场,即将当前节点dfs之前的状态恢复,重新进行下一步处理。
-
- DFS本质上就是递归。
- DFS本质上就是递归。
2、BFS——稳重
- 数据结构:
-
- queue
- 空间复杂度:
- O(2^n) —> 需要把整个一层的节点存下来
- 特点:
-
-
- 最短路问题求解,因为是按层来进行搜索的(前提是路径上的权值为1)
-
-
-
- 每层距离根节点的距离相等,按照层数进行遍历处理,即为不同距离
-
- 模板:
quene<> // 初始化
while(queue 不为空){
temp = queue[hh++]; //首节点处理
拓展t的操作
}
二、例题题解
排列数字(DFS)
// java题解实现DFS
import java.util.*;
public class Main{
static int N = 10; // 此处是确定开多少空间
static int n; // 此处代表的是有多少个数1~n
static int[] path = new int[N];
// 此处存储的是遍历中使用到的数,也就是在当前路径中,我们用到了哪些了数
// 每一条路径在输出之后都被更新了
static boolean[] str = new boolean[N]; // 此处代表的是1~n的数有没有被用过,str[i] = ture就代表第i个数已经被用过了
static void dfs(int u){ // u 表示要当前在第几步
if(u == n){ // 已经到达根节点了,也就是最后一层了
for(int i = 0;i < n; i++){
System.out.print(path[i] + " "); // 输出当前遍历路径上的存储值
}
System.out.println();
}else{
for(int i = 1; i <= n;i++){ // 此处之所以从1开始,是因为其中的str【i】代表的就是数字i是否被使用
if(str[i] == false){
path[u] = i; // 存下本次遍历的值
str[i] = true; // 已经用过了
dfs(u + 1); // 递归进行下一层的遍历
str[i] = false; // 在从最后一层回溯过程中,需要恢复现场,重新进行选择
}
}
}
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
n = in.nextInt();
dfs(0); // 因为一开始一个数也没有存,所以从第0步开始进行搜索
}
}
n皇后问题(DFS)
//方法一:全排列的办法,按行进行处理
import java.util.*;
public class Main{
static int N = 20;
static int n; // 行列数
static char[][] g = new char[N][N]; // 存储棋盘内容
static boolean[] col = new boolean[N]; // 列标志位,因为在遍历行
static boolean[] dg = new boolean[N]; // 正对角线(左到右,y = -x)
static boolean[] udg = new boolean[N]; // 反对角线
static void dfs(int u){
if(u == n){ // 判断是否到最后一层
for(int i = 0; i < n; i++){ // 行
for(int j = 0; j < n; j++){ // 列
System.out.print(g[i][j]);
}
System.out.println();
}
System.out.println();
}else{
for(int i = 0; i < n; i++){
if(col[i] == false && dg[i + u] == false && udg[n + i - u] == false){
// 标志位没有用过,则表示可以插入
// 其中的对角线 i + u 代表的是i为y,u为x,代表偏移量b = y + x,即为标志斜线的编号
// 其中的反对角线 n - u + i 代表的是i为y,u为x,代表偏移量编号b = y - x,
// 但是会存在负值,加一个偏移n
g[u][i] = 'Q';
col[i] = true;
dg[i + u] = true;
udg[n - u + i] = true;
dfs(u + 1);
// 恢复现场
col[i] = false;
dg[i + u] = false;
udg[n - u + i] = false;
g[u][i] = '.';
}
}
}
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
n = in.nextInt();
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
g[i][j] = '.';
}
}
dfs(0);
}
}
// 方法二:按棋盘点进行深搜,逐点进行判断处理
import java.util.*;
public class Main{
static int N = 20;
static int n;
static char[][] g = new char[N][N];
static boolean[] row = new boolean[N];
static boolean[] col = new boolean[N];
static boolean[] dg = new boolean[N];
static boolean[] udg = new boolean[N];
static void dfs(int x, int y, int u){ // x,y代表点的位置,u代表当前操作到每行最后了嘛
if(y == n){ // 这一行遍历完了,回到开始,开始下一行
y = 0;
x++;
}
if(x == n){
if(u == n){
for (int i = 0; i < n;i ++){
for(int j = 0; j < n;j ++){
System.out.print(g[i][j]);
}
System.out.println();
}
System.out.println();
}
return;
}
// 不放皇后,因此u不变
dfs(x, y + 1, u);
// 放皇后
if(row[x] == false && col[y] == false && dg[x + y] == false && udg[x - y + n] == false){
g[x][y] = 'Q';
row[x] = true;
col[y] = true;
dg[x + y] = true;
udg[x - y + n] = true;
dfs(x, y + 1, u + 1);
// 恢复现场
row[x] = false;
col[y] = false;
dg[x + y] = false;
udg[x - y + n] = false;
g[x][y] = '.';
}
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
n = in.nextInt();
for (int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
g[i][j] = '.';
}
}
dfs(0, 0, 0);
}
}
走迷宫(BFS)
// 走迷宫问题
import java.util.*;
import java.io.*;
public class Main{
static int N = 110;
static int[][] g = new int[N][N]; // 地图存储
static int[][] d = new int[N][N]; // 可以走的路径上,搜索的每一层的距离大小,初始化为-1,则表示没走过
static Pair[] quene = new Pair[N * N];
// 存储需要搜索的节点或位置,如果是搜索完毕则出队列,搜索到的新的节点加在队列后面
// 用队列的原因,是为了一次存储的都是同层结构,也都是每个节点搜索到的同一层加在后面
// 节点存储用的Pair自定义,N*N是因为最多的情况就是N*M都添加一遍
static int hh = 0; // 队列首指针
static int tt = -1; // 队列尾指针,
static int n; // 行
static int m; // 列
// 宽度优先搜索算法,返回值是距离最小
// 保证距离最小的关键在于[][]的使用,作用有两个
// 一个是存储距离长度,一个是不走已经添加过的路,也就不会转圈圈
static int bfs(){
d[0][0] = 0; // 定义左上角加入路径,起始点的距离为0
quene[++tt] = new Pair(0,0); // 将第一个点加入搜索初始节点,为后面进行搜索
int[] dx = {-1, 0, 1, 0}; // 使用向量的形式进行方向的表示
int[] dy = {0, 1, 0, -1};
// 分别是 上,右,下,左
// 下面是对队列中的节点进行搜索处理
while(hh <= tt){ // 队列不为空
Pair temp = quene[hh++]; // 取出队首节点,作为搜索节点
for(int i = 0; i < 4; i++){ // 进行四个方向的确定,看是否能走下去
int nex = temp.first + dx[i]; // 节点搜索变化
int ney = temp.second + dy[i];
// 判断xy是否在规定范围内,注意xy的范围不一致,并不一定是方阵
// g = 0 表示可以走
// d = -1表示没有走过,那么这个点就算是可以加入队列的新的搜索节点
if(nex >= 0 && nex < n && ney >= 0 && ney < m && d[nex][ney] == -1 && g[nex][ney] == 0){
d[nex][ney] = d[temp.first][temp.second] + 1; // 记录下新的搜索节点的路径距离值,也就是所在层数
quene[++tt] = new Pair(nex,ney); // 将新搜索到的节点,加入队列,后面还会继续拓展
}
}
// 因为肯定有一条路径会到达终点,所以说不用关心最终节点,队列空了就表示搜索完了
}
return d[n - 1][m - 1]; // 返回右下角节点的距离值,就是最短路径
}
public static void main(String[] args) throws IOException{
StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
in.nextToken();
n = (int) in.nval;
in.nextToken();
m = (int) in.nval;
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
in.nextToken();
g[i][j] = (int) in.nval;
d[i][j] = -1; //初始化距离矩阵d
}
}
System.out.print(bfs());
}
}
class Pair{
int first;
int second;
Pair(int x, int y){
this.first = x;
this.second = y;
}
}
八数码问题(BFS)
import java.util.*;
public class Main{
// 进行数组交换
static void swap(char[] arr, int x, int y){
// 此处是进行字符数组内部的交换,也就相当于完成了其中移动操作
// 值传递,但是因为才字符数组传递的是地址,故此,实际值也发生了改变
char temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}
static int bfs(String start,String end){
Map<String,Integer> dis = new HashMap<>(); // 存储距离,key是其中的队列内容
Queue<String> q = new LinkedList<>(); // 存储后面进行搜索的空间
q.offer(start); // 将初始值插入队列
dis.put(start,0); // 初始化距离,代表这是第一步
// 方向搜索
int[] dx = {-1, 0, 1, 0};
int[] dy = {0, 1, 0, -1};
// 判断队列是否为空,也就是还能不能继续搜索
while(!q.isEmpty()){
// 返回队首值,然后进行删除
String temp = q.poll();
// 找到x在当前维护string中的索引
int k = temp.indexOf('x');
int x = k / 3; // 坐标转换,转换为三行三列,记住这个技巧
int y = k % 3;
// 如果已经存入了最后结果,说明已经成功了,map中存了其中的距离值
if(temp.equals(end)){
return dis.get(temp); // 距离存储值
}
// 四个方向搜索
for(int i = 0; i < 4; i++){
int nex = x + dx[i] ; // 需要搜索的下一个位置在3*3中的位置
int ney = y + dy[i];
// 判断下一个点是否在我们要移动的数组内3*3
if(nex >= 0 && nex < 3 && ney >= 0 && ney < 3){
char[] arr = temp.toCharArray();
swap(arr,k, nex * 3 + ney); // x的移动
// 此处很重要
// nex * 3 + ney是为了将交换节点————映射回将在原来string中的位置索引
String process = new String(arr);
if(dis.get(process) == null){
dis.put(process, dis.get(temp) + 1); // 存储并更新新的值,记录这是第几步到达这个样子
q.offer(process); // 将新的图加入搜索队列
}
}
}
}
return -1;
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
String start = "";
for(int i = 0;i < 9; i++){
String s = in.next();
start += s;
}
String end = "12345678x";
System.out.print(bfs(start,end));
}
}