基础算法合集:
https://blog.csdn.net/GD_ONE/article/details/104061907
摘要
本文主要介绍 深度优先搜索和广度优先搜索,下文皆称为DFS和BFS。
DFS和BFS是两种搜索树和图的基本策略,见名知其义, 深搜和广搜,一种往深处搜,一种往边上搜。 DFS常用于暴力搜索所有状态,BFS常用于搜索到达某一状态的最短路径。
状态
我们将DFS和BFS搜索的东西称为状态, 状态就是一个具体问题的一个解,而将问题的所有状态关联在一起, 就能得到一个状态图,DFS和BFS就是遍历状态图的两种方式。
下面以几个具体问题来加以解释:
-
输出123的全排列
对于此题,将123数列的三个位置看作三个状态,当3个位置都填满数字时,就是其中一种排列,显然,答案就是所有当搜索完所有位置的所有状态。那么我们就可以使用DFS来找出所有状态。
部分状态图:
以上状态图中, 蓝色箭头是DFS的搜索顺序,对以上状态图的解释:对于第一位,有3种状态,1 2 3, 先选1, 然后看第二位, 1已经选过了,只能选2,3,选择2, 然后第三位就只剩下了3, 得到123,然后再将第二位选择为3, 则第三位为2, 得到132.
可以看到,状态图是树形结构。一个状态总能从根节点(起始状态)转移得到。
-
用一个3*3的二维矩阵代表一个迷宫,由0,1组成,1代表可以通过,0代表不能通过。要求找出从入口(1,1)到出口(n,n)的最短路径
对于此题,每个可以从入口到达的点,都是一个状态,那么从一层一层的搜,到达出口时的层数就是最短路径的长度了。
部分状态图:
对于以上状态图, X代表当前位置, 1 2 3代表BFS搜索的层数, 当搜索完第 1 层,搜索第 2 层,第 2 层搜索完才会搜索第 3 层。
DFS
我们已经知道DFS是沿着当前路径一直搜索下去,直到无路可走,就会返回,这被称为 回溯
我们可以利用递归的方式来实现这一回溯效果。
public class Main{
public static int f(int n){
if(n == 0){
return n;
}
System.out.print(n+"\n");
f(n-1);
System.out.print(n+"\n");
return n;
}
public static void main(String[] args) throws IOException {
f(3);
}
}
输出:
3
2
1
1
2
3
函数执行顺序:
- main中调用f()函数
- n刚开始等于 3, 先输出3 然后调用f(n-1); // 第一层函数
- n等于2, 输出2, 然后调用f(n-1) ; // 第二层函数
- n等于1, 输出1, 然后调用f(n-1); //第三层函数
- n等于0, 返回到上一次函数, 即第三层函数。然后输出1
- 然后再返回到第二层, 输出2
- 最后返回第一次, 输出1.
- f()函数结束。
这就是回溯的实质了。
看一道例题:
Red and Black
题目大意就是从起始位置@开始走,只能走在 . (黑砖)上面, 不能走在#(红砖)上面,问从@开始最多可以走几块 .(黑砖)。
将每个可以到达的点当作一个状态,搜索所有的状态,就可以得到答案啦。
怎么搜索呢, 只要我们得到下个点的坐标就可以了, 可以朝四个方向走,
所以,当前坐标 + (0,1) 就是向右走, + (1,0)就是向下走, + (0,-1)就是向左走, +(-1, 0)就是向上走。
然后判断一下个点是否超出边界是红砖还是黑砖,如果是黑砖,就搜索下一点,也就是DFS下一个点。
本题也可BFS。
本题坑点: @点也算一个答案。
具体看代码
C++:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int x,y,sum=1;
int n,m; // 行 列
int next[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
char map[20][20];
int book[20][20];
void input(int n,int m){ // 读入数据,并记录@坐标
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
cin>>map[i][j];
if(map[i][j]=='@')
x=i,y=j;
}
}
}
void dfs(int x,int y){
for(int i=0;i<4;i++){
int gx=x+next[i][0];
int gy=y+next[i][1];
if(gx>=0&&gx<m&&gy>=0&&gy<n&&book[gx][gy]!=1&&map[gx][gy]=='.'){
sum++;
book[gx][gy]=1; // 标记已经走过。则下次不再走这个点
dfs(gx,gy);
}
}
return;
}
int main()
{
while(cin>>n>>m&&n||m)
{
memset(map,0,sizeof map);
memset(book,0,sizeof book);
input(n,m);
dfs(x,y);
cout<<sum<<endl;
sum=1;
}
return 0;
}
JAVA:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
public class Main{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
static char[][] map = new char[30][30];
static int[][] vis = new int[30][30];
static int[][] ne = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
static int n, m, res = 0;
public static void dfs(int x, int y){
vis[x][y] = 1;
for(int i = 0; i < 4; i++){ //四个方向
int nx = x + ne[i][0];
int ny = y + ne[i][1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n || map[nx][ny] == '#' || vis[nx][ny] == 1){
continue;
}
res++;
dfs(nx,ny);
}
}
public static void main(String[] args) throws IOException{
while(true){
String s[] = in.readLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
if(n==0||m==0) break;
int x = 0, y = 0;
res = 0;
for(int i = 0; i < vis.length; i++)
Arrays.fill(vis[i], 0);
for(int i = 0; i < m; i++){
String s1 = in.readLine();
if(s1.indexOf('@') != -1){
x = i;
y = s1.indexOf('@');
}
map[i] = s1.toCharArray();
}
dfs(x, y);
out.write((res+1)+"\n");
out.flush();
}
}
}
BFS
BFS要先搜索当前状态可以直接到达的所有状态。因为一次只能处理一个状态,所以我们需要按照先后顺序,先将可以到达的所有状态点全部存起来,因为需要满足先后的顺序,所以正好可以利用队列先进先出的特性来存储。
以下是一般的BFS模板
public static void bfs(int x, int y){
q.add(x, y); //将起点入队
vis[x][y] = 1; // 标记已经走过
res[x][y] = 0; // 标记起点的步数
while(!q.isEmpty()){ // 当队列不空
x = q.peek().x; //取出队头元素
y = q.peek().y;
q.poll(); //删除队头元素
if(x == n-1 && n == m-1) return; // 到达终点
for(int i = 0; i < 4; i++){ // 当前点可以到达的下四个方向
int nx = x + dx[i]; // 下个点的坐标
int ny = y + dy[i];
if(nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny] == 1 ) continue; // 下个点不合法
else{
res[nx][ny] = res[x][y] + 1; // 到达下一个点的步数为此父结点的步数加1
q.add(new pair(nx,ny)); //下个点合法,入队存储
vis[nx][ny] = 1; // 标记该点已经走过
}
}
}
}
用走迷宫来解释, 就是先把一个点可以直接到达的所有点存到一个队列里,
然后从这些点里每次出队一个,进行上述操作,直到所有可到达的点都被搜索过。
例题:
走迷宫
JAVA代码:
import java.io.*;
import java.util.*;
class pair{
public int x,y;
pair(int a, int b){
x = a;
y = b;
}
}
public class Main{
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
static final int N = 1000;
static String[][] arr = new String[N][N];
static int[][] pren = new int[N][N];
static int[][] vis = new int[N][N];
static int[][] res = new int[N][N];
static int[] dx = {-1, 0, 1, 0};
static int[] dy = {0, 1, 0, -1};
static int n, m = 0;
static Queue<pair> q = new LinkedList<>();
public static int Int(String s){ return Integer.parseInt(s);}
public static void read() throws IOException{
for(int i = 0; i < n; i++){
arr[i] = in.readLine().split(" ");
}
}
public static void bfs(int x, int y)throws Exception{
q.add(new pair(x, y));
vis[x][y] = 1;
res[x][y] = 0;
while(!q.isEmpty()){
x = q.peek().x;
y = q.peek().y;
q.poll();
for(int i = 0; i < 4; i++){
int nx = x + dx[i];
int ny = y + dy[i];
if(nx < 0 || nx >= n || ny < 0 || ny >= m || vis[nx][ny] == 1 || arr[nx][ny].compareTo("1") == 0) continue;
else{
res[nx][ny] = res[x][y] + 1;
q.add(new pair(nx,ny));
vis[nx][ny] = 1;
if(nx == n-1 && ny == m-1) return;
}
}
}
}
public static void main(String[] args) throws Exception{
String s[] = in.readLine().split(" ");
n = Int(s[0]);
m = Int(s[1]);
read();
bfs(0,0);
out.write(res[n-1][m-1]+"\n");
out.flush();
}
}
刷题题目集:
POJ 1321 棋盘问题
POJ 2251 Dungeon Master
POJ 3278 Catch That Cow
POJ 3279 Fliptile
POJ 1426 Find The Multiple
POJ 3126 Prime Path
POJ 3087 Shuffle’m Up
POJ 3414 Pots
FZU 2150 Fire Game
UVA 11624 Fire!
POJ 3984 迷宫问题
HDU 1241 Oil Deposits
HDU 1495 非常可乐
HDU 2612 Find a way
题目链接
题目集题解:https://blog.csdn.net/GD_ONE/article/details/100833175
开始刷题吧!