深度优先搜索(Depth First Search,DFS)
主要思想
深度优先搜索是从起点开始,按照某种权重规则,选择优先级最高的路径,从而到达下一个结点,而后在上一步选择的基础上进行下一步优先级最高的选择,反复进行类似探索。
最终会遇到两种情况:
- 就是找到了终点(目标),即结束搜索
- 碰到了“绝路”,也就是前面没有路了。此时“回头”选择离此次最近的另外一条路(相当于我开始是一个方向走到头,发现没有路了,则返回到最近的分叉路口处选择另一条路。),再继续上述探索,如果直至最后回到起点,发现我已经没有未走过的路了,即把所有的路都走了一遍还没有找到终点(目标),则没有搜索结果
例子
如何从以下的S点到达E点,只能进行上下左右的移动且每次只能移动一格,并且不能跨域障碍,同时需要达到一个不错的效果(路途要尽量短)。
对于这个例子,前提准备数据类型与规则制定:
1.先从S出发,因为E在S的右下方,所以选择右,下移动的优先级大于左,上移动。
2.其次还需要两个容器去装有已经查找过的结点,以及将要查找的结点。而我选择了用栈充当存放结点的容器,由于栈的数据结构特点是:后进行先出,刚好满足当探索碰到了“死路”的时候,会弹出最近的存入了将要探索的结点,即返回到最近分叉路口选择另一条路。
3.而对于结点:需要有坐标,同时要标记是否是阻碍结点,而且还需要记入父亲结点来记录路径
java代码实现(包含数据存储的栈,结点类)
import java.util.ArrayList;
public class Dfs {
/**
* dfs算法搜索:它是通过栈的数据结构来进行充分的进行搜索,先初始化开始节点与路(棋盘),
* 起始点的父节点为NULL。它通过判断周围的节点是否被标记过或则是BLOCKED,
* 将下一步探索,没被标记(已查)且不是BLOCKED转态的结点的压入将要搜索的(frontier)栈内,
* 并将原结点压入已经检查过的栈内(explored)。不断对frontier栈顶元素进行判断是否到达目标位置
* 如果未到,继续探索下一步可以走的位置,有则压入frontier中,没有则继续抛出frontier栈顶元素,
* 直至frontier元素为0,则没有下一步可以走了,若此时还是没到目标位置,则不能到达目标位置
*/
// 将要检查的结点
Stack frontier;
// 检查过的结点
Stack explored;
// 当前结点
Node currentNode;
// 表格(路况)的初始化
RoadCondition roadCondition;
/**
*初始化两个栈和棋盘,同时将起始点压入frontier栈中
*/
public Dfs(Node start) {
frontier=new Stack();
explored=new Stack();
roadCondition=new RoadCondition();
this.frontier.push(start);
}
/**
* DFS算法
* @param goal 目标结点
* @return 如果找到目标结点则返回该结点,否则返回null
*/
public Node myDfs(Node goal){
// 如果有还可以进行检查(探索)元素,即还可以有下步走的地方
while (frontier.size()!=0){
// 弹出frontier栈顶元素进行匹配
currentNode=frontier.pop();
if(currentNode.equals(goal)){
// 返回值当前结点
return currentNode;
}else{
// 通过successor方法,将currentNode的下一步可以走的结点压入frontier栈中
successor(currentNode);
// 将当前结点设置为已检查的结点
explored.push(currentNode);
}
}
// 没找到,返回null
return null;
}
/**
* 将currentNode的下步可以走的结点压入frontier栈中
* @param node 结点元素
*/
public void successor(Node node){
// 首先判断下一步有没有越界,同时也不是有阻碍的结点,以前也没有检查过。
// 由于dfs算法是通过栈的数据结构存储,所以先进后出,故判断下一步的走法,
// 先压入:上,左,下,右。所以优先就是刚好反过来的:右,下,左,上
// 将符合条件的结点,压入将要探索的frontier栈中,并且记入父节点,便于画路径
if(node.state.x-1>=0&&
!roadCondition.road[node.state.x-1][node.state.y].block&&
!explored.contains(roadCondition.road[node.state.x-1[node.state.y])&&
!frontier.contains(roadCondition.road[node.state.x-1][node.state.y]))
{
// 将该结点压入frontier栈内,表示下一步可以探索的结点
frontier.push(roadCondition.road[node.state.x-1][node.state.y]);
// 记入下一步可以探索结点的父节点,便于回溯路径
roadCondition.road[node.state.x-1][node.state.y].parent=node;
}
// 判断向是否可以向左走
if(node.state.y-1>=0&&
!roadCondition.road[node.state.x][node.state.y-1].block&&
!explored.contains(roadCondition.road[node.state.x][node.state.y-1])&&
!frontier.contains(roadCondition.road[node.state.x][node.state.y-1]))
{
// 将该结点压入frontier栈内,表示下一步可以探索的结点
frontier.push(roadCondition.road[node.state.x][node.state.y-1]);
// 记入下一步可以探索结点的父节点,便于回溯路径
roadCondition.road[node.state.x][node.state.y-1].parent=node;
}
// 判断是否可以向下走
if(node.state.x+1<=8&&
!roadCondition.road[node.state.x+1][node.state.y].block&&
!explored.contains(roadCondition.road[node.state.x+1][node.state.y])&&
!frontier.contains(roadCondition.road[node.state.x+1][node.state.y]))
{
// 将该结点压入frontier栈内,表示下一步可以探索的结点
frontier.push(roadCondition.road[node.state.x+1][node.state.y]);
// 记入下一步可以探索结点的父节点,便于回溯路径
roadCondition.road[node.state.x+1][node.state.y].parent=node;
}
// 判断是否可以向右走
if(node.state.y+1<=8&&
!roadCondition.road[node.state.x][node.state.y+1].block&&
!explored.contains(roadCondition.road[node.state.x][node.state.y+1])&&
!frontier.contains(roadCondition.road[node.state.x][node.state.y+1]))
{
// 将该结点压入frontier栈内,表示下一步可以探索的结点
frontier.push(roadCondition.road[node.state.x][node.state.y+1]);
// 记入下一步可以探索结点的父节点,便于回溯路径
roadCondition.road[node.state.x][node.state.y+1].parent=node;
}
}
/**
* 如果可以到达目标位置,对通过的路径进行描绘
* @param node 寻找到的目标结点
*/
public void show(Node node){
ArrayList<Node> path=new ArrayList<>();
// 如果结点是null,则没有找到目标
if(node==null){
System.out.println("抱歉没有路径通往目的地!");
return;
}else {
// 记录通往目标结点所经过的所有的结点
Node current_tag=node;
while (current_tag!=null){
path.add(current_tag);
current_tag=current_tag.parent;
}
// 对棋盘(路况信息)以及通往目标结点路径的描绘
for (Node[] nodes : roadCondition.road) {
System.out.print("|");
for (Node n : nodes) {
if(n.block){
System.out.print("X |");
}else if(path.contains(n)){
System.out.print("->|");
}else {
System.out.print(" |");
}
}
System.out.println();
}
}
}
}
/**
* 数据结构:栈
*(底层使用的是LinkedList存储元素)
*/
class Stack {
LinkedList<Node> list;
public Stack(LinkedList<Node> list) {
this.list = list;
}
public Stack() {
list=new LinkedList<>();
}
// 判断是否包含node结点
public boolean contains(Node node){
return list.contains(node);
}
// 压栈操作
public void push(Node node){
list.push(node);
}
// 弹出栈顶元素
public Node pop(){
return list.removeFirst();
}
// 返回栈里元素的个数
public int size(){
return list.size();
}
@Override
public String toString() {
return "Stack{" +
"list=" + list +
'}';
}
}
/**
* 节点元素:Node
*/
class Node{
// 坐标类
MyState state;
// 父类结点
Node parent;
// 是否是障碍结点
boolean block;
public Node() {
}
public Node(MyState state, Node parent, boolean block) {
this.state = state;
this.parent = parent;
this.block = block;
}
@Override
public String toString() {
return "Node{" +
"state=" + state +
", parent=" + parent +
", block=" + block +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
return Objects.equals(state, node.state);
}
@Override
public int hashCode() {
return Objects.hash(state, parent, block);
}
}
/**
* 下标类
*/
class MyState{
// 横坐标
int x;
// 纵坐标
int y;
public MyState(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyState myState = (MyState) o;
return x == myState.x &&
y == myState.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "MyState{" +
"x=" + x +
", y=" + y +
'}';
}
}
/**
*棋盘(路况)类(包括9X9棋盘生成,障碍的生成)
*/
class RoadCondition {
Node[][] road=new Node[9][9];
public RoadCondition() {
Random random=new Random();
// 生成9X9的棋盘
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
// 设置生成障碍的概率,这里是设置1/5
int tmp=random.nextInt(5);
road[i][j]=new Node(new MyState(i,j),null,tmp==1);
}
}
// 手动设置,默认起点(0,0),终点(8,8)不是障碍结点
road[0][0].block=false;
road[8][8].block=false;
}
@Override
public String toString() {
return "RoadCondition{" +
"road=" + Arrays.toString(road) +
'}';
}
public void show(){
for (Node[] nodes : road) {
System.out.print("|");
for (Node node : nodes) {
if(node.block){
System.out.print("X |");
}else {
System.out.print(" |");
}
}
System.out.println();
}
}
}
运行结果展示
结果1
结果2
总结
注意:深度优先搜索算法找到的路径并不一定是最短(优)路径,这里就比与之对应的广度优先搜索稍微“弱”点。
而对于广度优先搜索来说所找到的路径是最短(优)路径。而广度优先搜索算法与深度优先搜索算法是一模一样的,唯一不一样的是广度优先搜索算法将栈换成了队列来存储结点元素!
谢谢观看,欢迎一起讨论!