- 图是网络结构的抽象模型,是一组由边连接的节点
- 图可以表示任何二元关系,比如道路、航班....
- JS没有图,但是可以利用 Object 和 Array 构建图
- 图的表示法:邻接矩阵、关联矩阵、邻接表....
- 图的常用操作:深度优先遍历、广度优先遍历
邻接矩阵:
邻接表:
1、深度优先遍历
准备好一个图结构:
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((item) => {
// 没访问过才可以,进行递归访问
if (!visited.has(item)) {
dfs(item);
}
});
}
dfs(2);
打印结果:
2
0
1
3
2、广度优先遍历
- 新建一个队列,把根节点入队
- 把对头出队并访问
- 把对头的没访问过的相邻节点入队
- 重复第二、三步,直到队列为空
const visited = new Set();
// 新建一个队列, 根节点入队, 设2为根节点,并记录已访问
const q = [2];
visited.add(2);
while (q.length) {
const n = q.shift();
console.log(n);
graph[n].forEach((item) => {
if (!visited.has(item)) {
q.push(item);
visited.add(item);
}
});
}
打印结果:
2
0
3
1
3、LeetCode: 65.有效数字
- 以0为起始位置,沿着图结构往下走
- 能走到3、5、6即为有效数字
解题步骤:
- 构建一个表示状态的图
- 遍历字符串,并沿着图走,如果到了某个节点无路可走就返回 false
- 遍历结束,如走到 3/5/6,就返回 true,否则返回 false
// 时间复杂度 O(n) n是字符串长度
// 空间复杂度 O(1)
const 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(let c of s.trim()) {
// 转换字符
if(c >= "0" && c <= "9") {
c = "digit";
}else if(c === " ") {
c = "blank"
}else if(c === "+" || c === "-") {
c = "sign";
}else if(c === "E" || c === "e") {
c = "e";
}
// 寻找图,没找到返回false
state = graph[state][c];
if(!state) return false;
}
// 返判断是否是合法的数字
const validNumArr = [3, 5, 6];
if(validNumArr.includes(state)) {
return true;
}
return false;
};
4、LeetCode: 417. 太平洋大西洋水流问题
- 把矩阵想象成图
- 从海岸线逆流而上遍历图,所到之处就是可以流到某个大洋的坐标
解题步骤:
- 新建两个矩阵,分别记录能流到两个大洋的坐标
- 从海岸线,多管齐下,同时深度优先遍历图,过程中填充上述矩阵
- 遍历两个矩阵,找出能流到两个大洋的坐标
// 时间复杂度:O(m * n)
// 空间复杂度:O(m * n)
const pacificAtlantic = function(matrix) {
if(!matrix || !matrix[0]) return [];
const m = matrix.length;
const n = matrix[0].length;
const flow1 = Array.from({length: m}, () => new Array(n).fill(false));
const flow2 = Array.from({length: m}, () => new Array(n).fill(false));
const dfs = (row, column, flow) => {
flow[row][column] = true;
[[row - 1, column], [row + 1, column], [row, column -1], [row, column + 1]].forEach(([nr, nc]) => {
if(
// 保证矩阵中
nr >= 0 && nr < m &&
nc >= 0 && nc < n &&
// 防止死循环
!flow[nr][nc] &&
matrix[nr][nc] >= matrix[row][column]
) {
dfs(nr, nc, flow)
}
})
};
// 沿着海岸线逆流而上
for(let r = 0; r < m; r++) {
dfs(r, 0, flow1);
dfs(r, n - 1, flow2);
}
for(let c = 0; c < n; c++) {
dfs(0, c, flow1);
dfs(m - 1, c, flow2);
}
// 收集能流动到两个大洋的坐标
const res = [];
for(let r = 0; r < m; r++) {
for(let c = 0; c < n; c++) {
if(flow1[r][c] && flow2[r][c]) {
res.push([r, c])
}
}
}
return res;
};
5、LeetCode: 133.克隆图
解题思路:
- 拷贝所有节点
- 拷贝所有的边
解题步骤:
- 深度或广度优先遍历所有节点
- 拷贝所有的节点,存储起来
- 将拷贝的节点,按照原图的连接方法进行连接
// 时间复杂度:O(n) n为图的节点数
// 空间复杂度:O(n) 有一个队列
/**
* // Definition for a Node.
* function Node(val, neighbors) {
* this.val = val === undefined ? 0 : val;
* this.neighbors = neighbors === undefined ? [] : neighbors;
* };
*/
const cloneGraph = 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.neighbors.push(visited.get(ne));
});
}
dfs(node);
return visited.get(node);
};