文章目录
- 图的术语
- 图的三种表示
- 创建Graph类
- 图的遍历
- 广度优先搜索
- 使用BFS寻找最短路径
- 深度优先搜索
图的术语
图是网络结构的抽模型。是一组由边连接的节点,在二元关系中是使用图结构来表示的。
相邻顶点:通过一条边连接在一起的顶点
图的三种表示
- 邻接矩阵,每一个节点都和一个整数关联,该整数作为节点的索引。
- 邻接表,由图中每一个顶点的相邻顶点列表组成
- 关联矩阵,矩阵的行表示顶点,列表示边
创建Graph类
字典Dictionary的定义,在前面的文章中已经声明了,这里直接引入字典
class Graph {
constructor(isDirected = false) {
this.isDirected = isDirected; // 图是否有向
this.vertice = []; // 图顶点的名字
this.adjList = new Dictionary(); // 用字典来存储邻接表
}
/**
*
* @param {*} value
* 添加顶点value
* 如果这个顶点没有存在于图中,那就将这个顶点加入顶点列表中。
*/
addVertex(value) {
if (!this.vertice.includes(value)) {
this.vertice.push(value);
this.adjList.set(value, [])
}
}
/**
* 连接顶点
* @param {*顶点} value
* @param {*顶点} w
*
* 这个函数接受两个顶点,
* 先判断两个顶点是否存在于图中,如果没有,就先将他们加入顶点列表中
*/
addEdge(value,) {
if (!this.adjList.get(value)) {
this.addVertex(value);
}
if (!this.adjList.get(w)) {
this.addVertex(w);
}
this.adjList.get(value)["value"].push(w);
if (!this.isDirected) {
this.adjList.get(w)["value"].push(value)
}
}
getVertices() {
return this.vertice;
}
getAdjList() {
return this.adjList;
}
toString() {
let s = '';
for (let index = 0; index < this.vertice.length; index++) {
s += `${this.vertice[index]}->`;
const neighbors = this.adjList.get(this.vertice[index])['value'];
neighbors.forEach(item => {
s += `${item}`
})
s += '\n';
}
return s;
}
}
图的遍历
图的遍历,必须追踪每个第一次访问的顶点,并且追踪有哪些顶点还没有被完全探索。
完全探索顶点,需要我们查看该顶点的每一条边,如果一条边的所有连接都没有被访问,那么这个顶点就是未被访问。
有两种方式:
- 广度优先搜索,
- 深度优先搜索
使用这两种方式来遍历图,我们需要对顶点进行标记【白色:顶点没有访问;灰色:表示该顶点被访问过,但并未被探索过;黑色:表示该顶点被访问过,但并未被探索过。】
const Colors = {
WHITE: 0, // 顶点还没有被访问
GREY: 1, // 顶点被访问过,但是没有被探索过
BLACK: 2, // 顶点被访问且被探索过
}
另外还需要一个辅助方法来存储顶点是否被访问过。默认所有顶点都被标记为白色。
// 广度优先和深度优先的算法,需要一个辅助方法来存储顶点是否被访问过
// 在每个算法的开头,所有顶点都标记为白色,未被访问
const initializeColor = vertice => {
const color = {};
for (let index = 0; index < vertice.length; index++) {
color[vertice[index]] = Colors.WHITE;
}
return color;
}
广度优先搜索
从第一个顶点开始访问,先访问所有的相邻顶点。
1、首先是用initializeColor 函数给所有顶点初始化为白色;
2、创建一个队列Q,用来存储待访问和待探索的顶点;
3、breadthFirstSearch函数是接收一个图、起始顶点和一个回调函数;
4、如果队列Q非空,那么我们可以操作队列Q,从队列中出一个顶点,并且获得该顶点所有邻点的邻接表,给该顶点设置为灰色;遍历该顶点的所有邻点。
const breadthFirstSearch = (graph, startVertex,) => {
const vertice = graph.getVertices();
const adjList = graph.getAdjList();
const color = initializeColor(vertice);
const queue = new Queue();
queue.enqueue(startVertex);
while (!queue.isEmpty()) {
// 从队列中获取一个顶点
const u = queue.dequeue();
// 获取该顶点的所有邻点的邻接表
const neighbors = adjList.get(u)['value'];
// 该顶点被初始化为灰色
color[u] = Colors.GREY;
// 遍历顶点的每一个邻点
for (let index = 0; index < neighbors.length; index++) {
// 获取该邻点的值【该顶点的名字】
const w = neighbors[index];
// 如果该邻点未被访问【白色】,
if (color[w] === Colors.WHITE) {
// 标记为灰色
color[w] = Colors.GREY;
// 入栈,当这个顶点出栈的时候,我们可以完成对该顶点的探索。
queue.enqueue(w);
}
}
color[u] = Colors.BLACK;
if (callback) {
callback(u)
}
}
}
队列的声明:Queue
使用BFS寻找最短路径
给出一个图G和顶点v,找出每一个顶点u和v的之间的最短距离:
// 寻找最短距离
const BFS = (graph,) => {
const vertice = graph.getVertices();
const adjList = graph.getAdjList();
const color = initializeColor(vertice);
const queue = new Queue();
const distances = {}; // 距离
const predecessors = {}; // 前溯点
queue.enqueue(startVertex);
for (let index = 0; index < vertice.length; index++) {
distances[vertice[index]] = 0;
predecessors[vertice[index]] = null;
}
while (!queue.isEmpty()) {
const u = queue.dequeue();
const neighbors = adjList.get(u)['value'];
color[u] = Colors.GREY;
for (let index = 0; index < neighbors.length; index++) {
const w = neighbors[index];
if (color[w] === Colors.WHITE) {
color[w] = Colors.GREY;
distances[w] = distances[u] + 1;
predecessors[w] = u;
queue.enqueue(w);
}
}
color[u] = Colors.BLACK;
}
return {
distances,
predecessors
};
}
执行:
const graph = new Graph();
const arr = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
arr.forEach(item => {
graph.addVertex(item)
})
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
console.log("最短距离",BFS(graph,"B"))
在图graph中个各个顶点到点B的距离,如下图:
B到B的距离为0,B到A的距离为1。
深度优先搜索
先深度,再广度:
// 深度优先搜索
const depthFirstSearch = (graph,) => {
const vertice = graph.getVertices();
const adjList = graph.getAdjList();
const color = initializeColor(vertice);
for (let index = 0; index < vertice.length; index++) {
if (color[vertice[index]] === Colors.WHITE) {
depthFirstSearchVisit(vertice[index], color, adjList, callback)
}
}
}
const depthFirstSearchVisit = (u, color, adjList,) => {
color[u] = Colors.GREY;
if (callback) {
callback(u);
}
const neighbors = adjList.get(u)['value'];
for (let index = 0; index < neighbors.length; index++) {
const w = neighbors[index];
if (color[w] === Colors.WHITE) {
depthFirstSearchVisit(w, color, adjList, callback)
}
}
color[u] = Colors.BLACK;
}
分析如图:
持续更新中