java算法day25

news2025/4/13 21:49:32

java算法day25

  • 广度优先搜索
  • 岛屿数量深搜
  • 岛屿数量广搜

广度优先搜索

核心:从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。搜索的方式是上下左右
在这里插入图片描述


一张图说明白模拟过程:
在这里插入图片描述
每一层,每个点不停的往上下左右的方向扩。
在面对有障碍的情况下也同样如此:
在这里插入图片描述

所以可以得出一个结论:
因为bfs这种一圈一圈层层往外搜索的性质,决定了bfs处理得到的路径一定是一条最短路径。

那这种一圈一圈的搜索过程是怎么做到的,用了什么容器才能实现这样的遍历。
回答是:用队列,栈,数组都可以。但是这里习惯用队列。
用队列那就是保证每一圈都是一个方向去转,例如统一顺时针或者统一逆时针。
因为队列是先进先出,加入元素和弹出元素的顺序没有发生改变。
而且顺时针和逆时针转都是可以的,并不用做什么特殊处理。

接下来是一个队列的模板。看看用队列怎么完成bfs。

import java.util.*;  

class Solution {  
    // 表示四个方向:右、下、上、左  
    private static final int[][] dir = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};  
    
    // grid 是地图,是一个二维字符数组  
    // visited 标记访问过的节点,避免重复访问  
    // x, y 表示开始搜索节点的坐标  
    public void bfs(char[][] grid, boolean[][] visited, int x, int y) {  
    //定义队列,该队列用于BFS,其中存的都是点
        Queue<int[]> queue = new LinkedList<>();  
        queue.offer(new int[]{x, y}); // 起始节点加入队列  
        visited[x][y] = true; // 标记起始节点为已访问  
        //BFS主循环,当队列不空时,继续搜索
        while (!queue.isEmpty()) {
        	//从队列中把要处理的点取出来,x坐标是cur[0],y坐标是cur[1]。这里把要处理的点的坐标拿出来是为了方便等下做上下左右运算。  
            int[] cur = queue.poll();  
            int curx = cur[0];  
            int cury = cur[1];  
            
            // 遍历四个方向:右、下、上、左  
            //这里相当于处理当前节点,
            for (int[] d : dir) {
            //为了方便还是计算该节点下一步要走的坐标。分别计算横坐标和纵坐标  
                int nextx = curx + d[0];  
                int nexty = cury + d[1];  
                
                //这个点算出来了 检查是否越界,看看这个点是否合法  
                if (nextx < 0 || nextx >= grid.length || nexty < 0 || nexty >= grid[0].length) {
                //如果这里面有一个不满足就代表这个点不合法,那么就不处理,所以跳过处理该方向的点。
                    continue;  
                }  
                
                //能走到这里说明点是合法的,但是还要看看这个点之前访问过了没。直接通过这个标记数组进行检查即可
                // 如果下一个节点没有被访问过,就会进去
                if (!visited[nextx][nexty]) {
                	//然后把这个没访问的节点放入待处理的队列中  
                    queue.offer(new int[]{nextx, nexty});
                    //然后把该点设置为已经访问  
                    visited[nextx][nexty] = true;  
                    // 在这里可以根据具体问题进行额外的处理  
                }
                //到了这里就会发现,循环里的某方向的一个点处理,而且还把该节点加入到了队列里。因为之后处理这个节点,往外面的方向扩的过程就是上面模拟的bfs。
                //这里一个方向就已经处理完毕,下一个循环就是下一个方向了。所以依次类推,就是一圈一圈的往外处理。
                //以中间的这个点来模拟,左方向处理完后加入了队列,然后假设转了一圈,跳转下一个节点的时候,从队列里第一个弹出来的节点就是这个左方向的节点,他也是如此的方式进行模拟。  
            }  
        }  
    }  
}

我学完这个模板之后,我感觉特别像层序遍历。不过是在图的角度上。

使用这个模板的步骤:

定义问题的网格(grid)。
创建一个与网格大小相同的 visited 数组。
选择起始位置(x, y)。
调用 bfs 方法。


岛屿数量深搜

题目已经说了,只有水平和竖直方向算,斜角度不算连着。这和遍历四个方向的考虑一致了。

算法思想:
1、遍历整个网格
2、当找到一个未访问的陆地时,将岛屿计数+1
3、然后调用DFS标记与这个陆地相连的所有陆地为已访问(用dfs就是递归,但是还是有一点BFS的影子,直接往四个方向都递归,当遇到节点是0就停下,或者节点不合法了也停下)
4、重复这个过程直到遍历完整个网络(相当于把整个网格都处理了。)

这种方法是可以有效的计算岛屿数量,因为每个岛屿只会被计数一次,而与他相连的所有陆地都会在dfs的过程中被标记。

import java.util.Scanner; 

public class Main{
	//规定四个方向,方便用来计算四个要遍历的方向
    static final int[][] dir = {{0,1},{1,0},{-1,0},{0,-1}};
    //主方法
    public static void main(String[] args){
    //定义网格
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        
        int[][] grid = new int[n][m];
        for(int i = 0;i<n;i++){
            for(int j = 0;j<m;j++){
                grid[i][j] = scanner.nextInt();
            }
        }
        //定义标记网格和计数器
        boolean[][] visited = new boolean[n][m];
        int result = 0;
        
        //遍历所有网格
        for(int i = 0;i<n;i++){
            for(int j = 0;j<m;j++){
            //遍历的过程中,如果遇到陆地,并且没有访问过,那就意味着这是一个新岛屿,所以先计数器++,然后dfs。把该陆地上所有相邻陆地全部标记为已经访问。
                if(!visited[i][j] && grid[i][j]==1){
                    result++;
                    //就是dfs这个第一个遇见的陆地,之后会dfs把相邻的陆地全部置为已经访问
                    dfs(grid,visited,i,j);
                }
            }
        }
        
        //结果输出
        System.out.println(result);
    }
    
    public static void dfs(int[][] grid,boolean[][] visited,int x,int y){
    //这里我写递归出口是已经考虑了,我是遇到了第一个陆地网格才进来的
    //所以这里递归出口的条件就会松一点。
        if(visited[x][y] || grid[x][y]==0){
            return;
        }
        
        //先处理当前节点,这里要进行处理就是把他置为true
        visited[x][y] = true;
        //然后遍历四个方向,每个方向都要进行dfs
        for(int[] d:dir){
        //dfs之前,把要dfs的坐标算出来
            int nextX = x+d[0];
            int nextY = y+d[1];
            //在进行dfs之前,还要判断这个方向的节点是否合法。
            //不合法就跳过了。
            if(nextX<0 || nextX>=grid.length || nextY<0 || nextY>=grid[0].length){
                continue;
            }
            //合法就dfs
            dfs(grid,visited,nextX,nextY);
        }
    }
}

岛屿数量广搜版

做这个题的时候,有一个细节需要特别的注意

只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过。如果用了后面这种方式,会导致同一节点被多次加入队列。

具体场景如下:

1 1 1  
1 1 1  
1 1 1

从左上角开始BFS,如果我们在将节点从队列拿出来的时候再去标记。那么
(0,0)加入队列
从队列中取出(0,0),标记为已经访问
然后BFS将(0,1)和(1,0)加入队列
从队列中取出(0,1)标记为已访问。
然后将(0,1)的邻居(0,2),(1,1)加入队列。
接下来从队列中取出(1,0)然后并标记为已经访问
将(1,0)的邻居加入队列,此时就包含了(1,1)
问题就来了,(1,1)被重复加入队列。

这就是会出现重复的问题,在更大的网格中,这种重复会更加的严重。
队列中会产生大量的重复节点,每个节点会被处理多次,导致算法效率大大降低甚至超时。

所以,应该在加入队列的同时,标记为已经访问。这样当考虑将某点加入队列的时候,发现已经标记过了就不会再重复加入了。


算法思想:
整体来说和DFS的解法一致。就是visited标记那里改成了用bfs的方式来做。

import java.util.*;  

public class Main {  
    private static final int[][] dir = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}}; // 四个方向  

    private static void bfs(int[][] grid, boolean[][] visited, int x, int y) {  
        Queue<int[]> queue = new LinkedList<>();  
        queue.offer(new int[]{x, y});  
        visited[x][y] = true; // 只要加入队列,立刻标记  

		//迭代的本质,和层序遍历非常相似,队列不空就不停
        while (!queue.isEmpty()) {  
        //取出节点
            int[] cur = queue.poll();  
            int curx = cur[0];  
            int cury = cur[1];  
			//处理四个方向
            for (int[] d : dir) {  
            //计算下一个方向的节点,准备将他加入队列中。
                int nextx = curx + d[0];  
                int nexty = cury + d[1];  
                //计算出来的这个节点,还要进行安全性判断
                if (nextx < 0 || nextx >= grid.length || nexty < 0 || nexty >= grid[0].length) continue; // 越界了,直接跳过  
                //然后再经过是否标记过,是否是岛屿的判断。才能将这个节点标记为true并加入队列
                if (!visited[nextx][nexty] && grid[nextx][nexty] == 1) {  
                    queue.offer(new int[]{nextx, nexty});  
                    visited[nextx][nexty] = true; // 只要加入队列立刻标记  
                }  
            }  
        }  
    }  

    public static void main(String[] args) {  
        Scanner scanner = new Scanner(System.in);  
        int n = scanner.nextInt();  
        int m = scanner.nextInt();  
        int[][] grid = new int[n][m];  

        for (int i = 0; i < n; i++) {  
            for (int j = 0; j < m; j++) {  
                grid[i][j] = scanner.nextInt();  
            }  
        }  

        boolean[][] visited = new boolean[n][m];  
        int result = 0;  

		//算法的核心思想,就是遍历网格。每遇到一个陆地,并且还没访问过
		//就统计+1,并且将其相邻陆地按照dfs或者bfs标记为已访问。
        for (int i = 0; i < n; i++) {  
            for (int j = 0; j < m; j++) {  
                if (!visited[i][j] && grid[i][j] == 1) {  
                    result++; // 遇到没访问过的陆地,+1  
                    bfs(grid, visited, i, j); // 将与其链接的陆地都标记上 true  
                }  
            }  
        }  

        System.out.println(result);  
    }  
}

200 岛屿数量

BFS解法:

class Solution {
    int[][] dir = {{0,1},{1,0},{0,-1},{-1,0}};
    

    public int numIslands(char[][] grid) {
        
        int n = grid.length;
        int m = grid[0].length;

        boolean[][] visited = new boolean[n][m];
        int result = 0;

        
        for(int i = 0;i<n;i++){
            for(int j = 0;j<m;j++){
                if(!visited[i][j] && grid[i][j]=='1'){
                    result++;
                    bfs(grid,visited,i,j);
                }
            }
        }

        return result;

        
    }

    void bfs(char[][] grid,boolean[][] visited,int x,int y){
        //先创建一个队列
        Queue<int[]> que = new LinkedList<>();
        //第一个节点加入进去,加之前进行标记
        visited[x][y] = true;
        que.offer(new int[]{x,y});

        while(!que.isEmpty()){
            //开始取出队列的节点进行处理
            int[] cur = que.poll();
            int curX = cur[0];
            int curY = cur[1];

            //然后开始处理该节点的四个方向
            for(int[] d : dir){
                //通过方向计算该节点的上下左右,然后加入队列中
                int nextX = curX+d[0];
                int nextY = curY+d[1];
                //在加入队列之前还要进行合法性判断
                if(nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length){
                    continue;
                }
                //还要判断该节点有没有访问过,并且是不是陆地节点
                if(!visited[nextX][nextY] && grid[nextX][nextY] == '1'){
                    visited[nextX][nextY] = true;
                    que.offer(new int[]{nextX,nextY});
                }
            }

        }
    }



}

DFS解法:

class Solution {
    int[][] dir = {{0,1},{1,0},{0,-1},{-1,0}};
    

    public int numIslands(char[][] grid) {
        
        int n = grid.length;
        int m = grid[0].length;

        boolean[][] visited = new boolean[n][m];
        int result = 0;

        
        for(int i = 0;i<n;i++){
            for(int j = 0;j<m;j++){
                if(!visited[i][j] && grid[i][j]=='1'){
                    result++;
                    dfs(grid,visited,i,j);
                }
            }
        }

        return result;

        
    }

    void dfs(char[][] grid,boolean[][] visited,int x,int y){
        if(grid[x][y] == '0' || visited[x][y]){
            return;
        }

        //先处理当前节点,先进行标记
        visited[x][y] = true;
        //然后处理四周
        for(int[] d : dir){
            int nextX = x+d[0];
            int nextY = y+d[1];
            //然后合法性判断
            if(nextX<0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length){
                continue;
            }
            //然后开始处理下一个节点
            dfs(grid,visited,nextX,nextY);

        }
    }

    



}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1954741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

21.发布确认模式-高级

问题 生产环境中由于一些不明原因&#xff0c;导致rabbitmq重启&#xff0c;在重启的期间生产者消息投递失败&#xff0c;导致消息丢失&#xff0c;需要手动处理恢复。那么如何才能进行rabbitmq的消息可靠性投递&#xff1f;特别是在极端的情况&#xff0c;rabbitmq集群不可用…

从json到protobuf,接口效率的提升

在express开发的前后端调用中&#xff0c;express作为服务端是不二之选&#xff0c;它有一些很好用的body解析器来解析传入数据&#xff1b;而作为请求发起方&#xff0c;axios是非常方便的&#xff0c;这是一个很好的选择&#xff0c;它可以传输多种类型的数据给接收方。 通常…

ios生成打包证书和描述文件(保姆级)

苹果开发者地址&#xff1a;Apple Developer (简体中文) 1.申请苹果App ID(App的唯一标识) 选择App IDs 选择App 输入APP ID的描述和Bundle ID Explicit&#xff1a;唯一的ID&#xff0c;用于唯一标识一个应用程序&#xff0c;一般选Explicit WildCard&#xff1a;通配符ID&am…

【初阶数据结构篇】顺序表和链表算法题

文章目录 顺序表算法题移除元素删除有序数组中的重复项合并两个有序数组 链表算法题移除链表元素反转链表链表的中间结点合并两个有序链表链表分割链表的回文结构 顺序表算法题 不熟悉顺序表的可以先了解一下 顺序表实现方法 移除元素 给你一个数组 nums 和一个值 val&#x…

谷歌DeepMind的AlphaProof和AlphaGeometry 2:AI系统在国际数学奥林匹克竞赛中取得突破

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

管不住人,你就当不好官:一流高手的3大管人秘籍,价值千金

管不住人&#xff0c;你就当不好官&#xff1a;一流高手的3大管人秘籍&#xff0c;价值千金 秘籍一&#xff1a;睁眼法 古语有云&#xff1a;“水至清则无鱼&#xff0c;人至察则无友。” 驾驭下属&#xff0c;学会睁一只眼闭一只眼&#xff0c;不要一竿子打死&#xff0c;…

如何在GPU服务器上安装Stable Diffusion webUI

一、前提条件 1、硬件条件 GPU&#xff1a;12G&#xff0c;建议16G以上&#xff0c;还是尽量勾搭&#xff0c;好像现在最大32G&#xff0c;目前个人性价比24G有时长出售。 内存&#xff1a;16G以上&#xff0c;建议32G&#xff0c;也是越大越好。 硬盘&#xff1a;最好使用…

谈谈面向对象

引言 无论你是刚入门的程序小白&#xff0c;还是混迹社会多年的程序大佬&#xff0c;谈起面向对象&#xff0c;想必多多少少都能侃上两句。面向对象作为程序界“家喻户晓”的一种编程思想&#xff0c;亦或是一种程序设计方法&#xff0c;重要性已是不言而喻。毫不夸张的说&…

打卡第22天------回溯算法

开始学习了,希望我可以尽快成功上岸! 一、回溯理论基础 什么是回溯法?回溯法也可以叫做回溯搜索法,它是一种搜索的方式。 回溯是递归的副产品,只要有递归就会有回溯。 回溯法的效率回溯法的本质是穷举,穷举所有可能,然后找出我们想要的答案。如果想让回溯法高效一些,可…

160. 相交链表(返回相交起点)

思路&#xff1a; 前提&#xff1a; PA headA&#xff0c;PB headB (B链表头节点) 过程&#xff1a; 1.PA与PB同时向后遍历 2.若PA遍历完&#xff0c;PA headB PB遍历完&#xff0c;PB headA 3.直到PA与PB指向相同节点&#xff08;实际遍历过两次中的较短的链表即可&am…

LLM与搜索推荐

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

24款奔驰E260后排电动座椅升级,舒适度全面提升

以下是关于 24 款奔驰 E 升级原厂后排电动座椅功能的案例讲解&#xff1a; 升级原厂后排电动座椅通常需要直接替换整个后排座椅&#xff0c;包括扶手等部件。 后排电动座椅的好处是可以通过电机调节靠背角度和座椅前后移动。例如&#xff0c;乘客可以通过车门上的座椅调节按钮…

聚焦民生服务 助力企业发展 区块链应用加速落地

聚焦民生服务&#xff0c;助力企业发展&#xff0c;区块链应用正在加速落地。这一趋势体现了区块链技术在多个领域的广泛应用和深远影响。以下是对这一主题的详细分析&#xff1a; 一、区块链在民生服务中的应用 政务服务 数据共享与打通&#xff1a;区块链技术利用其分布式账…

征服 Docker 镜像访问限制:KubeSphere v3.4.1 成功部署全攻略

近期&#xff0c;KubeSphere 社区的讨论中频繁出现关于 Docker 官方镜像仓库访问受限的问题。 本文旨在为您提供一个详细的指南&#xff0c; 展示在 Docker 官方镜像访问受限的情况下&#xff0c;如何通过 KubeKey v3.1.2 一次性成功部署 KubeSphere v3.4.1 以及 Kubernetes …

Java二叉树三序遍历的非递归实现

目录 零、本文中模拟实现的二叉树源码 一、前序遍历的非递归实现 1.代码示例&#xff1a; 2.与递归算法的比对演示&#xff1a; 二、中序遍历的非递归实现 1.代码示例&#xff1a; 2.与递归算法的比对演示&#xff1a; 三、后序遍历的非递归实现 1.代码示例&#xff1a; 2.与递…

VScode连接算力云服务器

打开VScode,找到插件市场,搜索Remote - SSH 下载插件Remote - SSH之后会出现下面这个,直接点击。 将下面这个恒源云租服务器的登陆指令 复制到下面之中,enter确认。 然后点第一个 然后点这个 复制粘贴这个云服务器的密码,(它不会显示,但你已经粘贴了)

Paddlenlp测试

1、环境安装 使用华为云euleros操作系统&#xff0c;python版本3.9.5&#xff0c;CPU无GPU服务器&#xff1a; &#xff08;1&#xff09;pip3 install setuptools_scm -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com &#xff08;2&#xf…

js 替换json中的转义字符 \

例如有以下字符串 "\"{\\\"account\\\":\\\"66\\\",\\\"name\\\":\\\"66\\\"}\"" 想得到如下字符串 {"account":"66","name":"66"} 执行替换字符串 "\"{…

【科研绘图】记录一次论文结果复现

复现原论文中的图片是科研的基本功之一&#xff0c;它不仅验证了研究结果的可靠性&#xff0c;确保了科学工作的准确性和可重复性&#xff0c;还深刻地评估了方法的有效性&#xff0c;体现了对原始研究的尊重和对科学过程的严谨态度。这个过程不仅提高了研究的透明度&#xff0…

科普文:docker基础概念、软件安装和常用命令

docker基本概念 一 容器的概念 1. 什么是容器&#xff1a;容器是在隔离的环境里面运行的一个进程&#xff0c;这个隔离的环境有自己的系统目录文件&#xff0c;有自己的ip地址&#xff0c;主机名等。也可以说&#xff1a;容器是一种轻量级虚拟化的技术。 2. 容器相对于kvm虚…