图和广度优先搜索

你经常要找出最短路径,这可能是前往朋友家的最短路径,也可能是国际象棋中把对方将死的最少步数。解决最短路径问题的算法被称为广度优先搜索
需要两个步骤
1). 使用图来构建问题模型。
2). 使用广度优先搜索解决问题。

1. 是什么

  • 网络结构的抽象模型,是一组由边连接的节点。
  • 用于模拟不同的东西是如何相连的。
  • js 里可以用 Object 和 Array 构建图。
  • 图的表示法:邻接矩阵、邻接表。关联矩阵......

1.1. 广度优先搜索

  • 一种用于图的查找算法,可帮助解决两类问题
    1). 从节点 A 出发,有前往节点 B 的路径吗?
    2). 从节点 A 出发,前往节点 B 的哪条路径最短?

1.1.1

有路径吗?
假设你经营一个芒果农场,需要找芒果经销商,以便将芒果卖给他。在 Facebook,你与芒果销售商有联系吗?为此,你可以在朋友中查找。


然后依次检查名单中的每个人,看看他是否是芒果销售商。

假设你没有朋友是芒果销售商,那么你必须在朋友的朋友中查找。

检查名单中的每个人时,你都将其朋友加入名单。

1.1.2 查找最短路径

以上面的案例为例,广度优先搜索可回答的两类问题

  • 从节点 A 出发,有前往节点 B 的路径吗?(在你的人际关系网中,有芒果销售商吗?)
  • 从节点 A 出发,前往节点 B 的哪条路径最短?(哪个芒果销售商与你的关系最近?)
    那么第二类哪个芒果销售商与你的关系最近
    例如:朋友是一度关系,朋友的朋友是二度以此类推

我们应先在一度中搜索,如果没有才在二度中搜索。也就是一度关系在二度关系之前加入查找名单。



而我们想保证我们的查找顺序就需要用到队列

2. 图的表示法

2.1. 邻接矩阵

2.2. 邻接表

3. 常用操作

  • 深度优先遍历
    尽可能深的搜索图的分支。
  • 广度优先遍历
    先访问离根节点最近的节点。

3.1. 深度优先遍历算法口诀

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

graph.js

const graph = {
  0: [1, 2],
  1: [2],
  2: [0, 3],
  3: [3]
};
module.exports = graph;
  • dfs.js
const graph = require('./graph');
const visited = new Set();
const dfs = (node) => {
  console.log(node)
  visited.add(node);
  graph[node].forEach(c => {
    if (!visited.has(c)) {
      dfs(c);
    }
  })
};
dfs(2); // 2 0, 1, 3 

从2开始首先遍历0,0遍历完了遍历0里面的1,1下面的2已经访问过了,所以不再继续遍历,开始遍历回到第一次的遍历遍历2里面的3

3.2. 广度优先遍历算法口诀

  • 新建一个队列,把根节点入队。
  • 把队头出队并访问。
  • 把队头的没访问过的相邻节点入队。
  • 重复第二三步,直到队列为空。
const graph = require('./graph');
const visited = new Set();
visited.add(2);
const q = [2];
while(q.length) {
  const n = q.shift();
  console.log(n);
  graph[n].forEach(c => {
    if (!visited.has(c)) {
      q.push(c);
      visited.add(c);
    }
  })
}

4. 场景

4.1. 有效数字 leetCode 65


上面图中左侧状态0为起点,每次都从状态0开始,并且只有3 5 6是有效的
比如第一个 "0" => true
开始的时候是0,值也是0,0-9之间的数字也就是到了左图中的6了,也就是有效的
第二个 " 0.1 " => true
从状态0开始,空格后还是状态0,然后是0到了状态6,点到了状态3,1还是状态3,后面的空格我们就可以忽略所以是true
第三个 "abc" => false
从状态0开始,第一位是a没有路可以走所以是 false

解题步骤


var isNumber = function(s) {
    const graph = {
        // 0通过空格是0,+/-是1,.是2,0-9是6
        0: {'blank': 0, '+/-': 1, '.': 2, '0-9': 6},
        1: {'0-9': 6, '.': 2},
        2: { '0-9': 3 },
        3: { '0-9': 3, 'e': 4 },
        4: { '0-9': 5, '+/-': 7 },
        5: { '0-9': 5 },
        6: { '0-9': 6, '.': 3, 'e': 4 },
        7: { '0-9': 5 }
    };
    // 起始状态为0
    let state = 0;
    // s.trim() 去除首尾空格,因为首尾空格不影响有效值
    for (c of s.trim()) {
        if (c === '') {
            c = 'blank'
        } else if (c === '+' || c === '-') {
            c = '+/-'
        } else if (c >= 0 && c <= 9) {
            c = '0-9'
        }
        // c.toLowerCase() 'E'也是有效数字
        state = graph[state][c.toLowerCase()]
        if (state === undefined) {
            return false;
        }
    }
    return [3, 5, 6].includes(state);
};

4.2. 太平洋大西洋水流问题 leetCode 417

给定一个 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]] (上图中带括号的单元).

解题思路

解题步骤

var pacificAtlantic = function(heights) {
    if (!heights || !heights.length) {return []}
    let m = heights.length; // m 矩阵的行数
    let n = heights[0].length; // n 矩阵的列数

    // 能流到太平洋的矩阵(得到长度为 m 里面每一项的长度是n并且值都是 false 的矩阵,false 节点不能流入大洋里)
    let flow1 = Array.from({ length: m }, () => new Array(n).fill(false));
    // 能流到大西洋的矩阵
    let flow2 = Array.from({ length: m }, () => new Array(n).fill(false));
    
    // 深度遍历row: 当前节点位于第几行,col: 第几列, flow: 用到哪个 flow
    const dfs = (row, col, flow) => {
        // 可以流入对应大洋的设置为 true
        flow[row][col] = true;
        // 遍历相邻节点(上下左右四个节点)
        // 上节点行数-1列数不变,下节点:行数加1列数不变,左节点:行数不变列数-1,右节点:行数不变,列数加1
        [[row -1, col], [row + 1, col], [row, col -1], [row, col + 1]].forEach(([nextRow, nextCol]) => {
            if (
                // 保证下一个节点在矩阵中(因为m和n是length,而nextRow 和 nextCol 是 index,所以只能小于)
                nextRow >= 0 && nextRow < m &&
                nextCol >= 0 && nextCol < n &&
                // 保证下一个节点没有访问过
                !flow[nextRow][nextCol] &&
                // 保证逆流而上(下一个节点大于等于上一节点)
                heights[nextRow][nextCol] >= heights[row][col]
            ) {
                dfs(nextRow, nextCol, flow)
            }
        })
    }

    // 沿着海岸线逆流而上
    // 对于太平洋来说海岸线是第一行和第一列,对于大西洋来说是最后一行和最后一列
    for (let row = 0; row < m; row++) {
        // 太平洋:递归遍历第一列
        dfs(row, 0, flow1)
        // 大西洋:递归遍历最后一列
        dfs(row, n - 1, flow2)
    }
    for (let col = 0; col < n; col++) {
        // 太平洋:递归遍历第一行
        dfs(0, col, flow1);
        // 大西洋:递归遍历最后一行
        dfs(m -1, col, flow2);
    }
    // 收集能同时流入两个大洋的坐标
    const res = [];
    for (let row = 0; row < m; row++) {
        for (let col = 0; col < n; col++) {
            if (flow1[row][col] && flow2[row][col]) {
                res.push([row, col])
            }
        }
    }
    return res;
};

时间复杂度mn 因为最后遍历的都是 mn 个格子,空间复杂度用到了两个临时变量 flow1 和 flow2 存的都是 mn 个格子,所以空间复杂度也是mn

4.3. 克隆图 leetCode 133

解题思路

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

解题步骤


var cloneGraph = function(node) {
    if (!node) return;
    const visited = new Map();
    const dfs = (n) => {
        // 拷贝当前节点
        const nodeCopy = new Node(n.val);
        // 建立一个当前节点和拷贝后的当前节点的映射关系
        visited.set(n, nodeCopy);
        n.neighbors?.forEach(ne => {
            if (!visited.has(ne)) {
                dfs(ne)
            }
            nodeCopy.neighbors.push(visited.get(ne))
        })
    }
    dfs(node);
    return visited.get(node);
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • 1. 图 1.1了解图 图是网状结构的抽象模型,图示由一组由边连接的节点.它由节点边组成,节点之间的由边连接,一个...
    小宇cool阅读 542评论 0 4
  • 图 图是网络结构的抽象模型,是一组由边连接的节点 图可以表示任何二元关系,比如道路、航班 JS中没有图,但是可以用...
    羽晞yose阅读 175评论 0 0
  • 图的概述 在众多数据结构中,图应该是最复杂的数据结构了,要了解图的全貌需要较深厚的数学基础,这里不可能在一篇文章内...
    GoFuncChan阅读 441评论 0 1
  • 目录 第六章 图 第一节 基本概念 1.1 定义和术语1.2 基本操作 第二节 存储结构 2.1 邻接矩阵2.2 ...
    Zal哥哥阅读 680评论 0 0
  • 1.图的基本概念 图由有限顶点集V和有限边(弧)集E组成,顶点集不可为空,边(弧)集可为空 有向图:E是有向边(即...
    WilsonEdwards阅读 1,592评论 0 1