在开始正文之前,先讨论一下数据结构是什么?数据结构就是程序运行时数据存放的结构,在万物皆对象的java里面,对象就是存放在堆内存里面。
而有一些对象很特别:
比如数组,是有一片连续的内存组成的,可以通过数组的下标来访问到数组里面的每一个对象。
再比如链表,在自己的内存空间中不仅存储值内容,还有一个指向下一个元素指针/引用,一般都叫个next或者pre之类的名字。
再比如二叉树,有两个引用,比如left和right。
照着这个逻辑一个对象可以有三个引用吗?当然可以比如二叉树再维护一下父元素的引用,left,right和parent。
那四个呢,也可以up down left right。
五个六个七八个呢,个人觉得也是可以的,大不了next1,next2,next3,next4,next5,next6但是代码如果写到这个程度,多少有点不优雅。
一个引用叫链表,两个可以维护一颗二叉树,多个拼起来也是一种数据结构叫做图。图里面的每一个元素叫做顶点。
个人理解觉得链表和二叉树都是图的范畴。都是用引用跟另外的元素连接起来。那么如果一个元素跟另外的100个元素都能连接我是不是要维护100个引用呢。
前辈告诉我们不需要。图有专门的记录方式,最经典的就是两种:
一种是矩阵,比如我有100个元素(顶点),那么维护一个长度是100的数组来记录顶点。然后在维护一个边长是100的矩阵(正方形),用一种标志标记两个顶点是否相连比如1,另外的标志标记连个顶点不相连,比如无穷大或者0。
一种是链表,比如还是100个顶点,还是长度100的数组记录顶点,然后维护一个长度100的链表桶,就跟java里面HashMap的存储结构一样,每个链表记录与该顶点相连的其他顶点。
好了终于进入今天的正文了,深度/广度优先搜索就是为遍历图数据结构的算法。
首先是深度优先,我不关心这个顶点有几个相连元素,我每次只取一个,并标记已访问的元素,直到没有下一个或者所有的都访问到。
然后是广度优先,我把一个定点所有的元素全部访问到(第一层),然后再去访问第二层,第三层直到所有的元素访问完毕。
深度优先搜索不好理解的地方在于回溯。就是如果一条路走不通,需要重新回溯回来重新搜索。
而广度优先搜索需要借助一种数据结构队列,才能完成。大体的逻辑就是先把第一个顶点元素入队列,然后搜索这个顶点所有的节点入队之后第一个定点出队,在队列中再取出另外的元素继续这个过程直到队列元素为空,利用的队列先进先出的特性。
一个算法例子:
宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N)的格子地图上,
宝宝和妈妈抽签决定各自的位置,地图上每个格子有不同的糖果数量,部分格子有障碍物。
游戏规则是妈妈必须在最短的时间(每个单位时间只能走一步)到达宝宝的位置,
路上的所有糖果都可以拿走,不能走障碍物的格子,只能上下左右走。
请问妈妈在最短到达宝宝位置的时间内最多拿到多少糖果(优先考虑最短时间到达的情况下尽可能多拿糖果)。
表数如下:
// -3:妈妈
// -2:宝宝
// -1:障碍
// ≥0:糖果数(0表示没有糖果,但是可以走)
// 4
// 3 2 1 -3
// 1 -1 1 1
// 1 1 -1 2
// -2 1 2 3
这个题目要求最短时间到达,第一反应是使用广度优先搜索,一层一层的搜索,直到为妈妈找到一条最短的路径。
代码说明,首先看Pos对象注意其visit属性,这个是必须要维护的,网上你凡是能搜索到的免费答案,几乎都是错的,付费的咱不知道。
维护一个全局的是否访问属性,能叫广度搜索吗?稍微给一个复杂点的例子都跑不通。
import java.util.*;
/**
* 糖果游戏 广度优先搜索
*/
public class CandyGameBfs {
static int mi = 0,mj = 0,si = 0,sj = 0;
static Queue<Pos> queue = null; //记录已访问节点
static List<Pos> posList = null; //记录所有的路线
static Pos end = null;//宝宝的位置
static int[][] arr;
public static void main(String[] args) {
//处理输入
Scanner scanner = new Scanner(System.in);
final int n = scanner.nextInt();
arr = new int[n][n];
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
arr[i][j] = scanner.nextInt();
if(arr[i][j] == -3){
mi = i;
mj = j;
}else if(arr[i][j] == -2){
si = i;
sj = j;
}
}
}
//初始化数据
queue = new LinkedList<>(); //记录已访问节点
posList = new LinkedList<>(); //记录所有的路线
Pos start = new Pos(mi,mj,0,new boolean[n][n]); //开始位置
end = new Pos(si,sj,0);
queue.offer(start);
bfs(queue.poll());
//打印结果
System.out.println(posList.size());
for(Pos pos : posList){
int size = 0;
int sum = 0;
while (pos != null){
System.out.println(pos.x + " " + pos.y + " "+ pos.count);
pos = pos.pre;
if(pos != null && pos.count > 0){
sum+=pos.count;
size++;
}
}
System.out.println(size + 1 + " "+ sum);
System.out.println("===");
}
}
/**
* //1,将第一个元素加入队列
* //2,搜索这个元素相邻的所有元素加入队列。之后第一个元素可以出队列
* //3,拿出队首的一个元素重复1,2
* //4,如果找到了孩子,记录当前路径,
* //5,队列为空,搜索结束
* 广度搜索不需要回溯
* @param start
*/
public static void bfs(Pos start){
if(start == null){ //队列空了 结束方法
return;
}
if(start.equals(end)){ //找到了,加入结果集
posList.add(start);
}
start.visit[start.x][start.y] = true; //已访问 这个地方还是值得考虑一下的。
//向上
if(start.x > 0 && !start.visit[start.x - 1][start.y] && arr[start.x - 1][start.y] != -1){
Pos tmp = new Pos(start.x-1,start.y,arr[start.x-1][start.y], start.visit);
tmp.pre = start; //维护路径链表
queue.offer(tmp);
}
//向下
if(start.x < arr.length-1 && !start.visit[start.x + 1][start.y] && arr[start.x + 1][start.y] != -1){
Pos tmp = new Pos(start.x+1,start.y,arr[start.x+1][start.y], start.visit);
tmp.pre = start;
queue.offer(tmp);
}
//向左
if(start.y > 0 && !start.visit[start.x][start.y - 1] && arr[start.x][start.y-1] != -1){
Pos tmp = new Pos(start.x,start.y - 1,arr[start.x][start.y-1], start.visit);
tmp.pre = start;
queue.offer(tmp);
}
//向右
if(start.y < arr.length - 1 && !start.visit[start.x][start.y+1] && arr[start.x][start.y+1] != -1){
Pos tmp = new Pos(start.x,start.y+1,arr[start.x][start.y+1], start.visit);
tmp.pre = start;
queue.offer(tmp);
}
//到这里 搜有的相邻元素都加入队列了。
bfs(queue.poll());
}
static class Pos{
int x;
int y; //当前位置
int count; //糖果数量
Pos pre; //路径链表
boolean[][] visit; //标记是否已访问
int size; //路径链表长度,因为要寻找最短路径,记录该字段可判断是否大于该字段,大于即可结束搜索,该示例未维护。
public Pos(int x,int y,int count){
this.x = x;
this.y = y;
this.count = count;
}
public Pos(int x,int y,int count,boolean[][] visited){
this.x = x;
this.y = y;
this.count = count;
boolean[][] visit = new boolean[visited.length][visited[0].length];
for(int i = 0; i < visited.length; i++){
System.arraycopy(visited[i], 0, visit[i], 0, visited[0].length);
}
this.visit = visit;
}
/**
* 重写该方法 只要x,y相等该对象就相等
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pos pos = (Pos) o;
return x == pos.x && y == pos.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
}
以上欢迎大佬批评指正。