图
多接点的拓扑关系
在算法中,图是一种数据结构,它由节点(顶点)和连接节点的边组成。图可以用来表示各种实际问题中的关系和连接方式。
图由两个主要部分组成:节点和边。节点表示实体或对象,而边表示节点之间的连接关系。这些连接关系可以是有向的(箭头指示方向)或无向的(没有箭头指示方向)。图可以是有权重的(边具有相关的数值或权重)或无权重的。
图可以有多种形式,包括有向图、无向图、加权图等。在算法中,图被广泛用于解决各种问题,如路径搜索、最短路径、网络流、最小生成树等。常用的图算法包括深度优先搜索(DFS)、广度优先搜索(BFS)、Dijkstra算法、Bellman-Ford算法、Prim算法、Kruskal算法等。
通过使用图这种数据结构和相关的图算法,我们可以更好地理解和处理复杂的关系和网络结构,从而解决各种实际问题。
更多详细内容,请微信搜索“前端爱好者
“, 戳我 查看 。
图的分类
在图论中,图可以根据不同的特性和属性进行分类。以下是常见的图的分类方式:
-
有向图(Directed Graph)和无向图(Undirected Graph):
- 有向图中的边是有方向的,即从一个节点指向另一个节点。
- 无向图中的边没有方向,表示节点之间是对等关系。
-
加权图(Weighted Graph)和非加权图(Unweighted Graph):
- 加权图中的边具有相关联的权重或成本。
- 非加权图中的边没有权重,只表示连接关系。
-
稠密图(Dense Graph)和稀疏图(Sparse Graph):
- 稠密图指的是边的数量接近于节点数量的图。
- 稀疏图指的是边的数量远小于节点数量的图。
-
连通图(Connected Graph)和非连通图(Disconnected Graph):
- 连通图中任意两个节点之间都存在路径。
- 非连通图中存在孤立的部分,某些节点之间没有路径相连。
-
无环图(Acyclic Graph)和有环图(Cyclic Graph):
- 无环图中不存在形成环路的路径。
- 有环图中存在形成环路的路径。
-
完全图(Complete Graph):
- 完全图是指每一对不同的节点之间都有一条边相连的图。
这些是图的常见分类方式,而实际应用中的图可能同时符合多种分类标准。
例子:找到小镇的法官
leetcode地址 : https://leetcode.cn/problems/find-the-town-judge/
小镇里有 n 个人,按从 1 到 n 的顺序编号。传言称,这些人中有一个暗地里是小镇法官。
如果小镇法官真的存在,那么:
- 小镇法官不会信任任何人。
- 每个人(除了小镇法官)都信任这位小镇法官。
- 只有一个人同时满足属性 1 和属性 2 。
给你一个数组 trust ,其中 trust[i] = [ai, bi] 表示编号为 ai 的人信任编号为 bi 的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1 。
示例 1:
输入:n = 2, trust = [[1,2]]
输出:2
示例 2:
输入:n = 3, trust = [[1,3],[2,3]]
输出:3
示例 3:
输入:n = 3, trust = [[1,3],[2,3],[3,1]]
输出:-1
提示:
1 <= n <= 1000
0 <= trust.length <= 10^4
trust[i].length == 2
trust 中的所有trust[i] = [ai, bi] 互不相同
ai != bi
1 <= ai, bi <= n
实现代码
/**
* @param {number} n
* @param {number[][]} trust
* @return {number}
*/
var findJudge = function(n, trust) {
// 在多个拓扑节点关系中,是有方向的
// 每个节点包括入度和处度
// 用数组来代表一个图,长度是n+1,每个节点中都会有入度和出度
let graph = Array.from({length:n+1},()=>({
inDegree: 0,
outDegree: 0,
}))
// 找到法官,入度为 n-1, 出度为 0
// 遍历数组,
trust.forEach(([a,b]) => {
graph[a].outDegree ++
graph[b].inDegree ++
})
return graph.findIndex(({inDegree,outDegree},index) => {
return index !== 0 && outDegree === 0 && inDegree === n -1
})
};
例子:课程表
leetcode地址:https://leetcode.cn/problems/course-schedule/
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i] 中的所有课程对 互不相同
实现代码
let canFinish = function(numCourses, prerequisites) {
// 如果没有先决条件,即所有的课程均没有依赖关系
// 直接返回 true
if (prerequisites.length === 0) {
return true
}
// 维护入度表
let inDegree = new Array(numCourses).fill(0)
// 维护临接表
let adj = new Map()
for (let e of prerequisites) {
inDegree[e[0]]++
if(!adj.has(e[1])) adj.set(e[1], [])
let vEdge = adj.get(e[1])
vEdge.push(e[0])
}
let queue = []
// 首先加入入度为 0 的结点
for (let i = 0; i < numCourses; i++) {
if (inDegree[i] === 0) {
queue.push(i)
}
}
while (queue.length > 0) {
// 从队首移除
var v = queue.shift()
// 出队一门课程
numCourses--
if(!adj.has(v)) continue
// 遍历当前出队结点的所有临接结点
for(let w of adj.get(v)) {
inDegree[w]--
if (inDegree[w] === 0) {
queue.push(w)
}
}
}
return numCourses === 0
}