BFS-广度优先搜索-解决最短路径的算法之一。
- 什么是BFS
- BFS可以解决什么问题
- 使用BFS模拟最短路线
- 在游戏中使用
BFS是一种简便的图的搜索方法之一,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
例如我从(0,0)点到(3,3)点,沿着A路线需要6步,沿着B需要8步。目标简单,最短路径可一眼看出,假如路线复杂,添加障碍物,而且路线的选择交给机器(没有思想),如果有一个固定的算法规律来指引,就可以给出路线来参考。
BFS搜寻的大致思路:
最先找到原则,举一个钓鱼的例子,假如鱼漂是路径的目的地,这时你向河水投入一枚石子,假如石子的位置为路径的起点。石子到鱼漂的最短路径就是石子荡起的波纹触碰到鱼漂的位置。BFS图搜寻的思路大致类似,以起点为开始向四处扩散,直到找到目标。
BFS可以解决两类问题:
- 从A出发有前往B的路径吗?(波纹扩散到边界没有触碰到鱼漂,则没有)
- 从A出发前往B的那条路径最短?(最先触碰到鱼漂的波纹路径最短)
使用BFS算法模拟最短路径。
需要用到最短路径的场景很多,
比如:我想找到一名微信好友,经过哪些好友的推荐可以快速找到?
- 我发现我有这个好友, 那就0步找到。
- 我发现通过我直接好友的推荐可以找到,那就一步可以找到。
- 我发现通过我直接好友的直接好友可以找到,那就两步找到。
- ... ...
- 找到后,再通过一步一步回溯就可以找到该最短路径。
如下,我通过好友的好友的好友找到了D1好友,然后通过D1 --> A的回溯找到最近。(A - B1 - C2 - D1)
在游戏中使用
吃到食物的最短路径:
部分代码如下:
private void autoV3(){
/**
* TODO 未完成
*
* 1.可以找到吃食物的路线
* 1.吃到食物后,可以找到自己的尾巴
* 移动一步
* 2.吃到食物后,找不到自己的尾巴
* 沿最远路径想尾巴移动一步
* 2. 找不到食物的路线
* 1. 可以找到尾巴
* 沿着最远向尾部移动一步
* 2. 找不到尾巴
* 按存活时间最长移动一步
*/
// 将snake位置放到list中,用于调用bfs
snakeList.addFirst(new Point(snakeX[0],snakeY[0]));
for(int i = 1;i< len;i++){
snakeList.addLast(new Point(snakeX[i],snakeY[i]));
}
// 路线
LinkedList dirList = sdfMinStep.bfs(snakeList,new Point(pointX,pointY));
System.out.println("计算路线:"+dirList);
if(dirList == null){
// setGameStat("AI AM LOST");
// return;
dirList = sdfMaxStep.bfs(snakeList,snakeList.getLast());
if(dirList == null){
setGameStat("AI AM LOST");
return;
}
}
System.out.println("计算步数:"+dirList.size());
// 先按照路线模拟走一遍,
// 模拟snake数据
LinkedList tempSnakeList = new LinkedList<>(snakeList);
// 计算模拟之后的snake位置
int stepNum = dirList.size();
for(int i = 0;i < stepNum;i++){
tempSnakeList.addFirst(dirList.get(i).getPoint());
if(i != stepNum - 1){
tempSnakeList.removeLast();
}
}
System.out.println("模拟吃到食物后的snake位置:"+tempSnakeList);
// 计算模拟之后的snake是可以走到snake尾
LinkedList tempDirList = sdfMinStep.bfs(tempSnakeList,tempSnakeList.getLast());
System.out.println("模拟的snake到snake尾的路径:"+tempDirList);
if(tempDirList == null){
// 向snake尾走一步 TODO 按最远走
// 更新路线
dirList = sdfMaxStep.bfs(snakeList,snakeList.getLast());
System.out.println("向snake尾走一步:"+dirList);
if(dirList == null){
setGameStat("AI AM LOST");
return;
}
}
System.out.println("模拟的snake到snake尾的路径:"+tempDirList.size());
snakeList.clear();
System.out.println("准备路线:"+dirList);
System.out.println("执行步数:"+dirList.size());
if(dirList.size() > 0){
System.out.println("执行方向:"+dirList.getFirst());
move(dirList.getFirst().getDir());
}
}
// 由于不想修改snake.move方法
// 预返回一条带方向的list,便于调用
public LinkedList bfs(LinkedList snakeList, Point targetPoint){
if(snakeList.size() < 1){
return null;
}
// snakeList.removeLast();
Point startPoint = snakeList.getFirst();
if(isTarget(startPoint,targetPoint)){
return null;
}
// 用于存储到达目标时的信息
PointDto tempTargetPoint = null;
int step = 0;
LinkedList allList = new LinkedList<>();
LinkedList parentList = new LinkedList<>();
LinkedList childList = new LinkedList<>();
PointDto pointDto = new PointDto(startPoint,null,0);
allList.add(pointDto);
parentList.add(pointDto);
childList.add(pointDto);
while (!parentList.isEmpty()){
step ++;
parentList.clear();
parentList.addAll(childList);
childList.clear();
A:for (PointDto p:parentList) {
// 1. 边界,终点,
// 2. 已存在
Point thisPoint = p.getPoint();
// 上 下 左 右
PointDto pointU = new PointDto(thisPoint.getX(),thisPoint.getY() - 1,step,"U");
PointDto pointD = new PointDto(thisPoint.getX(),thisPoint.getY() + 1,step,"D");
PointDto pointL = new PointDto(thisPoint.getX() - 1,thisPoint.getY(),step,"L");
PointDto pointR = new PointDto(thisPoint.getX() + 1,thisPoint.getY(),step,"R");
List udlrPointList = new LinkedList<>();
udlrPointList.add(pointD);
udlrPointList.add(pointL);
udlrPointList.add(pointR);
udlrPointList.add(pointU);
for (PointDto pointUDLR:udlrPointList) {
if(isSuitPoint(pointUDLR,allList,snakeList)){
pointUDLR.setParent(p);
childList.add(pointUDLR);
allList.add(pointUDLR);
if(isTarget(pointUDLR.getPoint(),targetPoint)){
tempTargetPoint = pointUDLR;
allList.removeLast();
childList.removeLast();
}
}
}
}
}
System.out.println("长度:"+snakeList.size());
System.out.println("位置:"+snakeList);
if(allList.size() < 1){
return null;
}
if(tempTargetPoint == null){
return null;
}
System.out.println("计算所有位置:"+allList.getLast());
System.out.println(tempTargetPoint);
return getDirList(tempTargetPoint);
}
/**
* 方向路线
*/
private LinkedList getDirList(PointDto pointDto){
if(pointDto.getParent() == null){
return null;
}
LinkedList dirList = new LinkedList<>();
while(pointDto.getParent() != null){
dirList.addFirst(new StepInfo(pointDto.getDir(),pointDto.getPoint()));
pointDto = pointDto.getParent();
}
return dirList;
}
/**
* 是否终点
* @return
*/
private boolean isTarget(Point point,Point targetPoint){
return point.equals(targetPoint);
}
/**
* 判断是否节点合适
* @param list
* @return
*/
private boolean isSuitPoint(PointDto PointUDLR,List list,LinkedList snakeList) {
// 界限内
Point point = PointUDLR.getPoint();
if(0 <= point.getX() && point.getX() < mapWidth && 0 <= point.getY() && point.getY() < mapHeight){
// 阻碍物
for(Point snakePoint : snakeList){
if(snakePoint.equals(point)){
return false;
}
}
// 没有被统计过
for(PointDto pointDto:list){
if(pointDto.getPoint().equals(point)){
return false;
}
}
return true;
}
return false;
}
效果图: