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

图在前端中的应用

一文了解图在前端中的应用

  • 🎧序言
  • 🎤一、图是什么?
    • 1、定义
    • 2、举例
  • 🎹二、图的表示法
    • 1、邻接矩阵表示法
    • 2、邻接表表示法
  • 🎺三、图的常用操作
    • 1、图的深度优先遍历
      • (1)定义
      • (2)口诀
      • (3)代码实现
    • 2、图的广度优先遍历
      • (1)定义
      • (2)口诀
      • (3)代码实现
  • 🎻四、leetcode经典题目解析
    • 1、leetcode417太平洋大西洋水流问题(中等)
    • 2、leetcode133克隆图(中等)
    • 3、leetcode65有效数字(困难)
  • 🎸五、结束语
  • 🐣彩蛋时间Painted Eggshell
    • 往期推荐
    • 番外篇

🎧序言

在我们的日常生活中,图无处不在。小到一张小小地图,大到我们我们乘坐的航班,每一个都跟图有着紧密的联系。

而对于前端来说,图的应用也是相对比较广泛的。图常用于克隆图、太平洋大西洋水流问题、有效数字的判断等等场景。

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

一起来学习吧~☂️

🎤一、图是什么?

1、定义

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

2、举例

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

🎹二、图的表示法

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

1、邻接矩阵表示法

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

邻接矩阵表示法

2、邻接表表示法

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

邻接表表示法

🎺三、图的常用操作

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++){//第一列的流到太平洋,即flow1dfs(r, 0, flow1);//最后一列的留到大西洋,即flow2dfs(r, n - 1, flow2);}for(let c = 0; c < n; c++){//第一行的流到太平洋,即flow1dfs(0, c, flow1);//最后一行的留到大西洋,即flow2dfs(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

(2)解题思路-图例

有效数字图例

(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 算法题,来解决了图的一些常用场景。

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

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

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

如有疑问或文章有误欢迎评论区留言或公众号后台加我微信提问~

🐣彩蛋时间Painted Eggshell

往期推荐

栈👉栈在前端中的应用,顺便再了解下深拷贝和浅拷贝!

队列👉详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

链表👉详解链表在前端的应用,顺便再弄懂原型和原型链!

字典和集合👉ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用

树👉一文了解树在前端中的应用,掌握数据结构中树的生命线

动态规则和分而治之算法👉一文了解分而治之和动态规则算法在前端中的应用

贪心算法和回溯算法👉一文了解贪心算法和回溯算法在前端中的应用

番外篇

  • 关注公众号星期一研究室,第一时间关注学习干货,更多精选专栏待你解锁~
  • 如果这篇文章对你有用,记得留个脚印jio再走哦~
  • 以上就是本文的全部内容!我们下期见!👋👋👋

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

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

相关文章

统信发布UOS V20 进军个人市场 生态日益完善

日前&#xff0c;统信桌面操作系统V20个人版正式发布&#xff0c;统信UOS是国产操作系统中屈指可数的在民间拥有一批爱好者和发烧友的操作系统&#xff0c;本次统信发布UOS V20个人版&#xff0c;吹响了进军TO C市场的号角。根据官方宣传介绍&#xff0c;统信桌面操作系统V20个…

leetcode28. 实现 strStr(KMP详解)

一:题目 二&#xff1a;思路 三:上码 // class Solution { // public: // int strStr(string haystack, string needle) { // if (needle.size()0) // return 0; // if (needle.size() > haystack.size()) // return -1; // …

在Docker中配置ASP.NETCore的HTTPS模式

&#xff08;The Continued Rising Power of Developers&#xff09;使用HTTPS&#xff0c;让网站更安全PS&#xff1a;经过两周的学习和部署迁移&#xff0c;目前已经把所有后端都迁到了基于Docker的Jenkins里了&#xff0c;相关文章可以参考《使用Jenkins来发布和代理.NetCor…

最小堆最大堆了解吗?一文了解堆在前端中的应用

一文了解堆在前端中的应用⚡序言&#x1f998;一、堆是什么&#xff1f;&#x1f425;二、JS中的堆&#x1f41d;三、堆的应用&#x1f408;四、构建一个最小堆1. 定义2. 方法3. 用js代码实现最小堆&#xff08;1&#xff09;初始化一个堆&#xff08;2&#xff09;交换位置swa…

​如何编写高质量的C#代码(一)

如何编写高质量的C#代码&#xff08;一&#xff09;从”整洁代码“谈起一千个读者&#xff0c;就有一千个哈姆雷特&#xff0c;代码质量也同样如此。想必每一个对于代码有追求的开发者&#xff0c;对于“高质量”这个词&#xff0c;或多或少都有自己的一丝理解。当我在长沙.NET…

可视化太酷辽!一文了解排序和搜索算法在前端中的应用

一文了解排序和搜索算法在前端中的应用⏳序言&#x1f9ed;一、文章结构抢先知⌚二、排序和搜索1、定义2、JS中的排序和搜索⏰三、排序算法1、冒泡排序&#x1f4a1;&#xff08;1&#xff09;定义&#xff08;2&#xff09;实现思路&#xff08;3&#xff09;图例&#xff08;…

leetcode双指针合集

27. 移除元素 class Solution { public:int removeElement(vector<int>& nums, int val) {/**思路:使用快慢指针&#xff0c;快指针正常往后移动,并将获取的值给慢指针&#xff0c;但是当快指针所指向的值是val的时候慢指针便不再更新;**/ int slowIndex 0;for(…

网络安全逐渐成为程序员的必备技能

大家好&#xff0c;我是Z哥。不知道大家有没有发现。如今&#xff0c;曝光某些知名公司信息泄露的事件频率越来越高。与之对应的&#xff0c;网络安全问题也越来越受到重视。从百度指数摘录了两张图给大家分享下。可以看到&#xff0c;对网络安全相关的信息和关注度在逐渐走高&…

webpack入门核心知识还看不过瘾?速来围观万字入门进阶知识

一文了解webpack入门进阶知识&#x1f93e;‍♀️序言&#x1f3d3;一、Tree Shaking1. 引例阐述2. Tree Shaking配置&#x1f3f8;二、Development和Prodiction模式的区分打包1. 项目打包结构2. 共有配置webpack.common.js3. 开发环境webpack.dev.js4. 生产环境webpack.prod.j…

Power Automate生产现场实例分享回顾

Power Automate生产现场实例分享回顾8月28日&#xff08;周五&#xff09;19&#xff1a;30-21&#xff1a;00&#xff0c;Danfos智慧工厂数字化解决方案高级顾问Helena Wang通过Teams和B站为大家分享了Power Platform开发以及它在工业生产当中的应用。一、什么是低代码开发&am…

万字总结webpack实战案例配置

一文了解webpack中常见实战案例配置&#x1f6f4;序言&#x1f68c;一、Library的打包1. webpack打包库2. 库引用冲突&#x1f68d;二、PWA的打包配置1. PWA是什么2. webpack中的PWA&#x1f68e;三、TypeScript的打包配置1. 引例阐述2. webpack对ts的配置&#xff08;1&#x…

程序员过关斩将--应对高并发系统有没有通用的解决方案呢?

“灵魂拷问&#xff1a;应对高并发系统有没有一些通用的解决方案呢&#xff1f;这些方案解决了什么问题呢&#xff1f;这些方案有那些优势和劣势呢&#xff1f;对性能孜孜不倦的追求是互联网技术不断发展的根本驱动力&#xff0c;从最初的大型机到现在的微型机&#xff0c;在本…

org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the

一:报错 org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement com.wyj.Dao.Bill.BillMapper.getBill. Its likely that neither a Result Type nor a Result Map was specified.二:解决 未在mapper.xml…

.NET5.0 单文件发布打包操作深度剖析

.NET5.0 单文件发布打包操作深度剖析前言随着 .NET5.0 Preview 8 的发布&#xff0c;许多新功能正在被社区成员一一探索&#xff1b;这其中就包含了“单文件发布”这个炫酷的功能&#xff0c;实际上&#xff0c;这也是社区一直以来的呼声&#xff0c;从 WinForm 的 msi 开始&am…

webpack实战之手写一个loader和plugin

webpack实战之编写一个简易的loader和plugin&#x1f514;序言&#x1f3b5;一、如何编写一个Loader1. 碎碎念2. 项目结构3. 业务代码编写&#xff08;1&#xff09;入口文件代码&#xff08;2&#xff09;编写loader&#xff08;3&#xff09;引用loader&#xff08;4&#xf…

手写一个简易bundler打包工具带你了解Webpack原理

用原生js手写一个简易的打包工具bundler&#x1f95d;序言&#x1f349;一、模块分析(入口文件代码分析)1. 项目结构2. 安装第三方依赖3. 业务代码4. 开始打包&#x1f951;二、依赖图谱Dependencies Graph1. 结果分析2. 分析所有模块的依赖关系&#x1f350;三、生成代码1. 逻…

不喜欢 merge 分叉,那就用 rebase 吧

阅读本文大概需要 3 分钟。有些人不喜欢 merge&#xff0c;因为在 merge 之后&#xff0c;commit 历史就会出现分叉&#xff0c;这种分叉再汇合的结构会让有些人觉得混乱而难以管理。如果你不希望 commit 历史出现分叉&#xff0c;可以用 rebase 来代替 merge。rebase &#xf…

你可能对position和z-index有一些误解

一文详解css中的position和z-index&#x1f9c3;序言&#x1f377;一、文章结构抢先知&#x1f378;二、position1. position的取值2. 标准文档流3. 各取值解析&#xff08;1&#xff09;static&#xff08;2&#xff09;relative&#xff08;3&#xff09;absolute&#xff08…

数据结构与算法专题——第九题 外排序

说到排序&#xff0c;大家第一反应基本上是内排序&#xff0c;是的&#xff0c;算法嘛&#xff0c;玩的就是内存&#xff0c;然而内存是有限制的&#xff0c;总有装不下的那一天&#xff0c;此时就可以来玩玩外排序&#xff0c;当然在我看来&#xff0c;外排序考验的是一个程序…

谁动了我的选择器?深入理解CSS选择器优先级

深入理解CSS选择器优先级&#x1f60f;序言&#x1f9d0;文章内容抢先看&#x1f910;一、基础知识1、为什么CSS选择器很强2、CSS选择器的一些基本概念&#xff08;1&#xff09;4种基本概念Ⅰ. 选择器Ⅱ. 选择符Ⅲ. 伪类Ⅳ. 伪元素&#xff08;2&#xff09;CSS选择器的命名空…