一、图
1. 图的分类
- 无向图:边没有方向。边与边之间的关系仅仅是连接。
- 有向图:边不仅连接顶点,并具有方向性.
- 带权图:边带着权重。
2. 图的存储结构
- 顺序存储
- 链式存储
线性表:仅有的关系就是线性关系。 树:有清晰的层次关系。 图:集合中每一个元素都可能有关系,图由顶点和边构成,所以要存储图形结构的信息,无非就是存储图的顶点和边。如果使用数组,存储边时会比较复杂。
因此我们采用以下两种存储结构:
-
邻接矩阵:n*m数据的集合,矩阵是由行和列组成的一组数表,邻接矩阵让每一个节点和整数相关联。 用1表示有关系,用0表示没关系。 优点:表示非常的明确。 缺点:非常浪费计算机内存空间,存储了太多的0 -
邻接表:由图中每个顶点及与顶点相邻的顶点列表组成。数组、链表、字典
3.实现
class Graph {
constructor() {
this.vertiecs = []
this.edgeList = {}
}
addVerTex(v) {
this.vertiecs.push(v)
this.edgeList[v] = []
}
addEdge(a, b) {
this.edgeList[a].push(b)
this.edgeList[b].push(a)
}
toString() {
let rst = ''
for (let i = 0; i < this.vertiecs.length; i++) {
let vertex = this.vertiecs[i]
rst += `${vertex}=>`
let edge = this.edgeList[vertex]
for (let j = 0; j < edge.length; j++) {
rst += edge[j]
}
rst += '\n'
}
return rst
}
}
const graph = new Graph()
graph.addVerTex('A')
graph.addVerTex('B')
graph.addVerTex('C')
graph.addVerTex('D')
graph.addVerTex('E')
graph.addVerTex('F')
graph.addEdge('A', "B")
graph.addEdge('B', "C")
graph.addEdge('B', "F")
graph.addEdge('C', "D")
console.log(graph)
console.log(graph.toString())
4.遍历
遍历:从某个结点触发,一次访问数据结构中全部结点,每个结点访问一次。
- 广度优先遍历 BFC:优先横向遍历图,从图中的某个结点v出发,访问了v后,一次访问v的各个未曾访问过的邻接点,然后分别从这些结点触发,依次访问它们的邻接点,直到所有结点被访问过。
- 深度优先遍历 DFS:先遍历图的纵向。
- 图遍历的思路:
- 每一个顶点有三种状态:
- 未发现(没有发现这个顶点)
- 已经发现(发现其他顶点连接到这里,但是没有发现这个顶点全部的邻接点)
- 已经探索(已经发现这个顶点的所有邻接点)
- 记录顶点是否被访问,使用三种颜色老i标识
(1)广度优先遍历
- 广度优先遍历的过程
- 发现未发现的顶点后,存放至队列中,等待查找,并将结点标识为已发现
- 在队列中拿出以发现的结点,开始探索其他顶点,并跳过已探索的顶点
- 遍历完这个顶点以后,将顶点标识为已探索
- 继续在队列中探索下一个顶点
class Queue {
constructor() {
this.items = []
}
enqueue(ele) {
this.items.push(ele)
}
dequeue() {
return this.items.shift()
}
front() {
return this.items[0]
}
real() {
return this.items[this.items.length - 1]
}
isEmpty() {
return this.items.length === 0
}
size() {
return this.items.length
}
clear() {
this.items = []
}
}
class Graph {
constructor() {
this.vertiecs = []
this.edgeList = {}
}
addVerTex(v) {
this.vertiecs.push(v)
this.edgeList[v] = []
}
addEdge(a, b) {
this.edgeList[a].push(b)
this.edgeList[b].push(a)
}
toString() {
let rst = ''
for (let i = 0; i < this.vertiecs.length; i++) {
let vertex = this.vertiecs[i]
rst += `${vertex}=>`
let edge = this.edgeList[vertex]
for (let j = 0; j < edge.length; j++) {
rst += edge[j]
}
rst += '\n'
}
return rst
}
initColor() {
let colors = {}
for (let i = 0; i < this.vertiecs.length; i++) {
colors[this.vertiecs[i]] = 'white'
}
return colors
}
bfs(v, callback) {
let colors = this.initColor()
let queue = new Queue()
queue.enqueue(v)
while (!queue.isEmpty()) {
const qVertex = queue.dequeue(v)
const edge = this.edgeList[qVertex]
for (let i = 0; i < edge.length; i++) {
const e = edge[i]
if (colors[e] === 'white') {
colors[e] = 'gray'
queue.enqueue(e)
}
}
colors[qVertex] = 'black'
if (callback) {
callback(qVertex)
}
}
}
}
const graph = new Graph()
graph.addVerTex('A')
graph.addVerTex('B')
graph.addVerTex('C')
graph.addVerTex('D')
graph.addVerTex('E')
graph.addVerTex('F')
graph.addEdge('A', "B")
graph.addEdge('A', "C")
graph.addEdge('B', "E")
graph.addEdge('A', "D")
graph.addEdge('B', "F")
graph.bfs('A', (e) => {
console.log(e)
})
(2)深度优先遍历
广度优先遍历使用的是队列,深度优先的原理:递归 深度优先的过程:
- 从某一顶点开始查找,并把结点标识为灰色
- 从这个结点开始探索其他顶点,并跳过已经发现的顶点
- 遍历完这个结点后将这个结点标记为已探索(黑色)
- 递归返回,继续探索下一个路径的最深顶点
dfsVisit(v, color, callback) {
color[v] = 'gray'
if (callback) {
callback(v)
}
let edge = this.edgeList[v]
for (let i = 0; i < edge.length; i++) {
let e = edge[i]
if (color[e] === 'white'){
color[e] = 'gray'
this.dfsVisit(e,color,callback)
}
}
}
dfs(v, callback) {
const color = this.initColor()
this.dfsVisit(v,color,callback)
}
graph.dfs('A', (e) => {
console.log(e)
})
5.最短路径
最短路径:一个顶点到另一个顶点之间的最短路径。 回溯点:回溯点是离上一个顶点最近的顶点。 回溯路径:由所有回溯点组成。 两种常见的求最短路径的算法:
- 迪杰斯特拉算法:贪心算法的一个应用,解决单源点到其余顶点的最短路径问题。
思想:每次找到离源点(如1号结点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。 - 佛洛依德算法:经典动态规划应用,解决多源最短路径,时间复杂度O(n^3) ,空间复杂度O(n^2)
bfs(v, callback) {
let colors = this.initColor()
let queue = new Queue()
queue.enqueue(v)
let prev = {}
for (let i = 0; i < this.vertiecs.length; i++) {
prev[this.vertiecs[i]] = null
}
while (!queue.isEmpty()) {
const qVertex = queue.dequeue()
const edge = this.edgeList[qVertex]
for (let i = 0; i < edge.length; i++) {
const e = edge[i]
if (colors[e] === 'white') {
colors[e] = 'gray'
prev[e] = qVertex
queue.enqueue(e)
}
}
colors[qVertex] = 'black'
if (callback) {
callback(qVertex)
}
}
return prev
}
const prev = graph.bfs('A')
const shortPath = (from, to) => {
let vertex = to
let stack = new Stack()
while (vertex !== from) {
stack.push(vertex)
vertex = prev[vertex]
}
stack.push(vertex)
let path = ''
while (!stack.isEmpty()) {
path += `${stack.pop()}=>`
}
return path
}
const path = shortPath('A', 'F')
console.log(path)
|