太平洋大西洋水流问题如何解决?一文了解图在前端中的应用

简介: 在下面的这篇文章中,将讲解关于图的一些基础知识,以及图在前端中的常见应用。

59.png

🎤一、图是什么?


1、定义


  • 图是由顶点的集合边的集合组成的。
  • 图是网络结构的抽象模型,是一组由连接的节点
  • 图可以表示任何二元关系,比如道路航班……。
  • JS 中没有图,但是可以用 ObjectArray 构建图。
  • 图的表示法:领接矩阵、邻接表、关联矩阵……


2、举例


地铁线路中每一个站点可以看成是一个顶点,而连接着每个站点的线路可以看做是边。


🎹二、图的表示法


图通常有两种表示法:领接矩阵和邻接表。下面一起来看这两种表示法~


1、邻接矩阵表示法

下面用一张图来展示邻接矩阵的表示法。详情见下图👇

60.png


2、邻接表表示法

大家可以看到上面的邻接矩阵,在矩阵中存在着大量的0,这将会占据程序中大量的内存。因此,我们引入了邻接表,来解决这个问题。详情见下图👇

61.png


🎺三、图的常用操作


1、图的深度优先遍历


(1)定义

  • 图的深度优先遍历,即尽可能深的搜索图的分支。


(2)口诀

  • 访问根节点。
  • 对根节点没访问过的相邻节点挨个进行深度优先遍历。


(3)代码实现

接下来我们用 JS 来实现图的深度优先遍历,这里我们采用邻接表的形式来表示。具体代码如下:

我们先来定义一个图的结构:

const graph = {
    0:[1, 2],
    1:[2],
    2:[0, 3],
    3:[3]
}
复制代码

接下来来对这个结构进行深度优先遍历:

const visited = new Set();
const dfs = (n) => { 
    console.log(n);
    //将访问过的节点加入集合中
    visited.add(n);
    //对当前节点所对应的数组挨个进行遍历
    graph[n].forEach(c => {
        // 对没有访问过的在此访问
        if(!visited.has(c)){
            //递归进行深度遍历
            dfs(c);
        }
    })
}
//以2为起始点进行深度优先遍历
dfs(2);
复制代码

最后我们来看下打印结果:

/*打印结果:
2
0
1
3
*/
复制代码


2、图的广度优先遍历


(1)定义

  • 图的广度优先遍历,先访问离根节点最近的节点。


(2)口诀

  • 新建一个队列,把根节点入队。
  • 把队头出队并访问。
  • 把队头每访问过的相邻节点入队。
  • 重复第二、三步操作,直到队列为空。


(3)代码实现

接下来我们用 JS 来实现图的广度优先遍历,这里我们采用邻接表的形式来表示。具体代码如下:

同样地我们先来定义一个图的结构:

const graph = {
    0:[1, 2],
    1:[2],
    2:[0, 3],
    3:[3]
}
复制代码

接下来来对这个结构进行深度优先遍历:

//新建一个集合,存放访问过的节点
const visited = new Set();
//初始节点放进集合中
visited.add(2);
//将初始节点放入队列q中
const q = [2];
while(q.length){
    //删除队列q的第一个元素,并将其值返回
    const n = q.shift();
    //打印返回后的值
    console.log(n);
    //将该值所对应邻接表的数组,挨个进行遍历
    graph[n].forEach(c => {
        //判断数组中的元素是否已经访问过
        if(!visited.has(c)){
            //如果没有访问过则加入访问队列和访问集合
            q.push(c);
            visited.add(c);
        }
    });
}
复制代码

最后我们来看下打印结果:

/*打印结果:
2
0
3
1
*/
复制代码


🎻四、leetcode经典题目解析


接下来我们引用几道经典的 leetcode 算法,来巩固的知识。

温馨小提示: 题意的内容范例是对官方题目的简单概要,并不是特别全面,建议大家先点击链接查看,使用体验更为友好~


1、leetcode417太平洋大西洋水流问题(中等)

(1)题意

附上题目链接:leetcode417太平洋大西洋水流问题

给定一个 m x n非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。

规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。

请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

提示:

  • 输出坐标的顺序不重要
  • m 和 n 都小于150

输入输出示例:

给定下面的 5x5 矩阵:
  太平洋 ~   ~   ~   ~   ~ 
       ~  1   2   2   3  (5) *
       ~  3   2   3  (4) (4) *
       ~  2   4  (5)  3   1  *
       ~ (6) (7)  1   4   5  *
       ~ (5)  1   1   2   4  *
          *   *   *   *   * 大西洋
返回:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
复制代码

(2)解题思路

  • 把矩阵想象成图。
  • 从海岸线逆流而上遍历图,所到之处就是可以留到某个大洋的坐标。

(3)解题步骤

  • 新建两个矩阵,分别记录能留到两个大洋的坐标。
  • 从海岸线,多管旗下,同时深度优先遍历图,过程中填充上述矩阵。
  • 遍历两个矩阵,找出能流到两个大洋的坐标。

(4)代码实现

/**
 * @param {number[][]} matrix
 * @return {number[][]}
 */
 let pacificAtlantic = function(matrix) {
    // 如果传入的不是一个矩阵,则返回一个空数组
    if(!matrix && !matrix[0]){
        return [];
    }
    // m表示矩阵的行数,n表示矩阵的列数
    const m = matrix.length;
    // matrix[0]表示矩阵的第一行
    const n = matrix[0].length;
    // 定义flow1记录留到太平洋的坐标,flow2记录留到大西洋的坐标
    // from方法构建长度为m的数组,第二个参数填充指定数组的值填充为什么样
    const flow1 = Array.from({length: m}, () => new Array(n).fill(false));
    const flow2 = Array.from({length: m}, () => new Array(n).fill(false));
    // console.log(flow1);
    // console.log(flow2);
    // 进行深度优先遍历
    // r即row,表示行;c即column,表示列
    // flow为二维数组
    const dfs = (r, c, flow) => {
        flow[r][c] = true;
        [[r -1, c],[r + 1, c],[r, c - 1], [r, c + 1]].forEach(([nr,nc]) => {
            if(
                // 保证在矩阵中
                nr >= 0 && nr < m &&
                nc >= 0 && nc < n &&
                // 防止死循环
                !flow[nr][nc] &&
                // 保证逆流而上,即保证下一个节点的值大于上一个节点的值
                matrix[nr][nc] >= matrix[r][c]
            ){
                dfs(nr, nc, flow);
            }
        });
    };
    // 沿着海岸线逆流而上
    for(let r = 0; r < m; r++){
        //第一列的流到太平洋,即flow1
        dfs(r, 0, flow1);
        //最后一列的留到大西洋,即flow2
        dfs(r, n - 1, flow2);
    }
    for(let c = 0; c < n; c++){
        //第一行的流到太平洋,即flow1
        dfs(0, c, flow1);
        //最后一行的留到大西洋,即flow2
        dfs(m - 1, c, flow2);
    }
    //收集能留到两个大洋里的坐标
    const res = [];
    for(let r = 0; r < m; r++){
        for(let c = 0; c < n; c++){
            //当flow1和flow2都为true时,则说明既能留到太平洋,也能流到大西洋
            if(flow1[r][c] && flow2[r][c]){
                res.push([r, c]);
            }
        }
    }
    return res;
};
console.log(pacificAtlantic([[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]))
/*打印结果:
[
  [ 0, 4 ], [ 1, 3 ],
  [ 1, 4 ], [ 2, 2 ],
  [ 3, 0 ], [ 3, 1 ],
  [ 4, 0 ]
]
*/
复制代码


2、leetcode133克隆图(中等)

(1)题意

附上题目链接:leetcode133克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List<Node> neighbors;
}
复制代码

输入输出示例:

  • 输入: adjList = [[2,4],[1,3],[2,4],[1,3]]
  • 输出: [[2,4],[1,3],[2,4],[1,3]]
  • 解释:
  • 图中有 4 个节点。
  • 节点 1 的值是 1,它有两个邻居:节点 24
  • 节点 2 的值是 2,它有两个邻居:节点 13
  • 节点 3 的值是 3,它有两个邻居:节点 24
  • 节点 4 的值是 4,它有两个邻居:节点 13

(2)解题思路

  • 拷贝所有节点。
  • 拷贝所有的边。

(3)解题步骤

  • 深度或广度优先遍历所有节点。
  • 拷贝所有的结点,存储起来。
  • 将拷贝的结点,按照原图的连接方法进行连接。

(4)代码实现

我们用两种方式来实现克隆图:深度优先遍历和广度优先遍历。具体代码如下:

深度优先遍历:

/**
 * // Definition for a Node.
 * function Node(val, neighbors) {
 *    this.val = val === undefined ? 0 : val;
 *    //邻居节点是一个数组
 *    this.neighbors = neighbors === undefined ? [] : neighbors;
 * };
 */
/**
 * @param {Node} node
 * @return {Node}
 */
// 深度优先遍历
let cloneGraph1 = function(node) {
    //如果当前节点为空,则直接返回
    if(!node){
        return;
    }
    //定义一个字典,存放访问过的节点
    const visited = new Map();
    //深度优先遍历
    const dfs = (n) => {
        // 拷贝一份当前初始节点的值
        const nCopy = new Node(n.val);
        //将拷贝后的节点放到访问字典当中
        visited.set(n, nCopy);
        //对初始节点的邻居节点挨个进行遍历
        (n.neighbors || []).forEach(ne => {
            //判断访问队列是否有过邻居节点
            if(!visited.has(ne)){
                /* 如果访问队列没有过该邻居节点,
                则将邻居节点继续进行深度遍历*/
                dfs(ne);
            }
            // 将访问过的邻居节点的值拷贝到nCopy上
            nCopy.neighbors.push(visited.get(ne));
        });
    };
    dfs(node);
    return visited.get(node);
};
复制代码

广度优先遍历:

/**
 * // Definition for a Node.
 * function Node(val, neighbors) {
 *    this.val = val === undefined ? 0 : val;
 *    //邻居节点是一个数组
 *    this.neighbors = neighbors === undefined ? [] : neighbors;
 * };
 */
/**
 * @param {Node} node
 * @return {Node}
 */
let cloneGraph2 = function(node) {
     //如果当前节点为空,则直接返回
    if(!node){
        return;
    }
    //定义一个字典,存放访问过的节点
    const visited = new Map();
    //visited存放节点以及节点的值
    visited.set(node, new Node(node.val));
    // 初始化一个队列
    const q = [node];
    // 当队列内有节点信息时
    while(q.length){
        // 删除队列中的第一个元素并返回值
        const n = q.shift();
        //将节点的邻居挨个进行遍历
        (n.neighbors || []).forEach(ne => {
            // 判断访问队列是否有过邻居节点
            if(!visited.has(ne)){
                // 将节点的邻居加入到队列中
                q.push(ne);
                // 将节点的邻居及邻居的值放入visited中
                visited.set(ne, new Node(ne.val));
            }
            /*如果访问队列已经有过该节点,
            则将此节点放入访问队列的邻居节点
             */
            visited.get(n).neighbors.push(visited.get(ne));
        });
    }
    //返回访问队列的节点信息
    return visited.get(node);
};
复制代码


3、leetcode65有效数字(困难)

(1)题意

附上题目链接:leetcode65有效数字

有效数字(按顺序)可以分成以下几个部分:

  • 一个 小数 或者 整数
  • (可选)一个 'e''E' ,后面跟着一个 整数

小数(按顺序)可以分成以下几个部分:

  • (可选)一个符号字符('+''-'
  • 下述格式之一:
  • 至少一位数字,后面跟着一个点 '.'
  • 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
  • 一个点 '.' ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  • (可选)一个符号字符('+''-'
  • 至少一位数字

输入输出示例:

  • 输入: s = "0"
  • 输出: true


(3)解题步骤

  • 构建一个表示状态的图。
  • 遍历字符串,并沿着图走,如果到了某个节点无路可走就返回false。
  • 遍历结束,如走到3/5/6,就返回true,否则返回false。

(4)代码实现

let isNumber = function(s){
    const graph = {
        0:{'blank': 0, 'sign': 1, '.': 2, 'digit': 6 },
        1:{'digit': 6, '.': 2 },
        2:{'digit': 3 },
        3:{'digit': 3, 'e': 4 },
        4:{'digit': 5, 'sign': 7 },
        5:{'digit': 5 },
        6:{'digit': 6, '.': 3, 'e': 4 },
        7:{'digit': 5 }
    }
    let state = 0;
    for(c of s.trim()){
        if(c >= '0' && c <= '9'){
            c = 'digit';
        }else if(c === ' '){
            c = 'blank';
        }else if(c === '+' || c === '-'){
            c = 'sign';
        }
        state = graph[state][c];
        if(state === undefined){
            return false;
        }
    }
    if(state === 3 || state === 5 || state === 6){
        return true;
    }
    return false;
}
复制代码


🎸五、结束语


通过上文的学习,我们了解到了图的两种表示法:邻接矩阵表示法邻接表表示法。同时,还学习了图的两种常用操作:图的深度优先遍历图的广度优先遍历。最后,我们引用了几道 leetcode 算法题,来解决了图的一些常用场景。

个人认为,图相对于其他数据结构来说,学习难度更大一点,但又是一个不得不学的基本知识,所以还是得多加练习。

除此之外呢,对于以上算法题,学有余力之余,可以考虑多调试一步步跟着调试走,慢慢的就理解的更透彻了。

关于图在前端中的应用讲到这里就结束啦!希望对大家有帮助~



相关文章
|
11月前
|
前端开发 JavaScript 应用服务中间件
在Docker部署的前端应用中使用动态环境变量
以上步骤展示了如何在 Docker 配置过程中处理并注入环墨遁形成可执行操作流程,并确保最终用户能够无缝地与之交互而无须关心背后复杂性。
595 13
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
1154 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
1063 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
人工智能 前端开发 JavaScript
AI程序员:通义灵码 2.0应用VScode前端开发深度体验
AI程序员:通义灵码 2.0应用VScode前端开发深度体验,在软件开发领域,人工智能技术的融入正深刻改变着程序员的工作方式。通义灵码 2.0 作为一款先进的 AI 编程助手,与广受欢迎的代码编辑器 Visual Studio Code(VScode)相结合,为前端开发带来了全新的可能性。本文将详细分享通义灵码 2.0 在 VScode 前端开发环境中的深度使用体验。
2694 2
AI程序员:通义灵码 2.0应用VScode前端开发深度体验
|
人工智能 前端开发 JavaScript
详解智能编码在前端研发的创新应用
接下来,人与智能体的交互将变得更为紧密,比如 N 年以后是否可以逐渐过渡。这个逐渐过渡的过程实际上是温和的,从依赖人类到依赖超大规模算力的转变,可能会取代我们的一些职责。这不仅仅是简单的叠加关系。对于AI和超大规模算力,这是否意味着我们可以大幅度提升软件质量,是否可以缩短研发周期并提高效率,还有创造出更优质的软件并持续发展,这无疑是肯定的。
1077 25
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
590 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
人工智能 前端开发 JavaScript
智能编码在前端研发的创新应用
在前端开发领域,智能编码技术正引领一场变革,通过大模型的强大能力将自然语言需求直接转化为高效、可靠的代码实现。
733 10
|
人工智能 前端开发 JavaScript
详解智能编码在前端研发的创新应用 | 领通义灵码蛇年红包封面
详解智能编码在前端研发的创新应用 | 领通义灵码蛇年红包封面
|
移动开发 缓存 前端开发
深入理解前端路由:原理、实现与应用
本书《深入理解前端路由:原理、实现与应用》全面解析了前端路由的核心概念、工作原理及其实现方法,结合实际案例探讨了其在现代Web应用中的广泛应用,适合前端开发者和相关技术人员阅读。
|
存储 前端开发 JavaScript
前端中对象的深度应用与最佳实践
前端对象应用涉及在网页开发中使用JavaScript等技术创建和操作对象,以实现动态交互效果。通过定义属性和方法,对象可以封装数据和功能,提升代码的组织性和复用性,是现代Web开发的核心技术之一。

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    1308
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    596
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    454
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    453
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    578
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    754
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    1476
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    327
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    1200
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    535