深度/广度优先搜索

在开始正文之前,先讨论一下数据结构是什么?数据结构就是程序运行时数据存放的结构,在万物皆对象的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);
        }
    }
}



以上欢迎大佬批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lx18854869896

和谐社会靠你了,老铁...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值