赞
踩
表示图的常用方式为:邻接矩阵,如下图所示
邻接矩阵表示法示例,如上图所示:
另外一种表示图的常用方式为:邻接表,如下图所示
如上图所示:
这里我们采用邻接表的方式进行封装,使用之前封装过的字典结构(也可以理解为Map)来存储临近表
function Graph() {
this.vertexes = []; // 存放顶点
this.adList = new Dirtioany(); // 使用字典结构存放 边 信息
}
我们需要创建一个数组对象vertexes存储图的顶点
创建一个字典对象edges,来存储图的边,其中key为顶点值,value为存储key顶点相邻顶点的数组
如下图所示
// 1.添加顶点
Graph.prototype.addVertex = function (val) {
this.vertexes.push(val); // 添加顶点
this.adList.set(val, []); // 初始化顶点对应的边
};
// 2.添加边
Graph.prototype.addEdge = function (val1, val2) {
// 因为边是相互存在的,所以需要存储关联两个顶点的信息
// 这里实现的是无向图,所以不考虑方向的问题
this.adList.get(val1).push(val2);
this.adList.get(val2).push(val1);
};
为图类Graph添加toString方法,实现以邻接表的形式输出图中各顶点
// 3.toString方法
Graph.prototype.toString = function () {
var resString = "";
for (var i = 0; i < this.vertexes.length; i++) {
resString += this.vertexes[i] + "->";
var adList = this.adList.get(this.vertexes[i]);
for (var j = 0; j < adList.length; j++) {
resString += adList[j] + " ";
}
resString += "\n";
}
return resString;
};
// 测试代码
let graph = new Graph();
// 添加顶点
let myVertexes = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
for (let i = 0; i < myVertexes.length; i++) {
graph.addVertex(myVertexes[i]);
}
// 添加边
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(graph.toString());
// A->B C D
// B->A E F
// C->A D G
// D->A C G H
// E->B I
// F->B
// G->C D
// H->D
// I->E
为了记录顶点是否被访问过,这里使用三种颜色来表示它们的状态
首先封装initializeColor方法将图中的所有顶点初始化为白色,代码实现如下:
/**
* 4.初始化顶点颜色
* 白色表示该顶点还没有被访问.
* 灰色表示该顶点被访问过, 但并未被探索过.
* 黑色表示该顶点被访问过且被完全探索过.
*/
Graph.prototype._initColors = function () {
var colors = [];
for (var i = 0; i < this.vertexes.length; i++) {
colors[this.vertexes[i]] = "white";
}
return colors;
};
基于队列可以简单地实现广度优先搜索算法:
// 5.广度优先遍历
Graph.prototype.bfs = function (initV, handler) {
var colors = this._initColors();
var queue = new Queue();
// 将定点放入队列
queue.enqueue(initV);
while (!queue.isEmpty()) {
var qVal = queue.dequeue(); // 拿到队头的顶点
var qAdj = this.adList.get(qVal); // 拿到队头对应的边
// 将队头设置正在探索中 灰色
colors[qVal] = "gray";
// 将队头顶点相邻的边 全部拿出来遍历一遍,并加入到队列中
for (var i = 0; i < qAdj.length; i++) {
var vertexeValue = qAdj[i]; // 取出相邻顶点
// 如果这个顶点是白色,说明没有被访问过
if (colors[vertexeValue] === "white") {
// 正在访问
colors[vertexeValue] = "gray";
// 将相邻顶点入队
queue.enqueue(vertexeValue);
}
}
// 代表该定点已经探索完毕,设置为黑色
colors[qVal] = "black";
// 输出
handler && handler(qVal);
}
};
graph.bfs(graph.vertexes[0], function (value) {
console.log(value); // A,B,C,D,E,F,G,H,I,
});
下为指定的第一个顶点为A时的遍历过程:
如此循环直到队列中元素为0,即所有顶点都变黑并移出队列后才停止,此时图中顶点已被全部遍历
为了更好的理解BFS,这里提供一份简版BFS,核心思路都是一样的
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3],
};
const bfs = (root) => {
const visited = new Set(); // 用来存储,节点是否访问过
visited.add(root);
const q = [root]; //新建队列,根结点入队
while (q.length) {
let n = q.shift(); // 队列节点出队,拿到队头
console.log(n); // 访问节点
// 访问相邻节点,并入队
graph[n]?.forEach((item) => {
if (!visited.has(item)) { // 若没被访问过
q.push(item); // 入队
visited.add(item); // 表示已经访问过
}
});
}
};
bfs(2); // 2 0 3 1
基于递归实现深度优先搜索算法:定义dfs方法用于调用递归方法dfsVisit,定义dfsVisit方法用于递归访问图中的各个顶点。
这里实现dfs时,实现一个辅助函数dfsVisit,方便递归实现DFS。
在dfs方法中:
在dfsVisit方法中:
// 6.深度优先遍历
Graph.prototype.dfs = function (initValue, handler) {
var colors = this._initColors(); // 初始化顶点颜色
this.dfsVisit(initValue, colors, handler); // 遍历
};
Graph.prototype.dfsVisit = function (v, colors, handler) {
colors[v] = "gray"; // 访问中
handler && handler(v); // 访问该顶点
var vList = this.adList.get(v); // 获取相邻顶点
for (var i = 0; i < vList.length; i++) {
if (colors[vList[i]] === "white") {
this.dfsVisit(vList[i], colors, handler); // 递归访问
}
}
colors[v] = "black"; // 访问结束
};
graph.dfs(graph.vertexes[0], function (value) {
console.log(value); // A,B,E,I,F,C,D,G,H
});
这里主要解释一下代码中的第3步操作:访问指定顶点的相邻顶点。
下图为遍历图中各顶点的完整过程:
为了更好的理解DFS,这里也提供一份简版DFS
const graph = {
0: [1, 2],
1: [2],
2: [0, 3],
3: [3],
};
let set = new Set();
const dfs = (n) => {
console.log(n);
set.add(n);
graph[n]?.forEach((item) => {
if (!set.has(item)) {
dfs(item);
}
});
};
dfs(2); // 2 0 1 3
const Dirtioany = require("./dict");
const Queue = require("../03-队列结构/01-queue");
function Graph() {
this.vertexes = []; // 存放顶点
this.adList = new Dirtioany(); // 使用字典结构存放 边 信息
// 1.添加顶点
Graph.prototype.addVertex = function (val) {
this.vertexes.push(val); // 添加顶点
this.adList.set(val, []); // 初始化顶点对应的边
};
// 2.添加边
Graph.prototype.addEdge = function (val1, val2) {
// 因为边是相互存在的,所以需要存储关联两个顶点的信息
// 这里实现的是无向图,所以不考虑方向的问题
this.adList.get(val1).push(val2);
this.adList.get(val2).push(val1);
};
// 3.toString方法
Graph.prototype.toString = function () {
var resString = "";
for (var i = 0; i < this.vertexes.length; i++) {
resString += this.vertexes[i] + "->";
var adList = this.adList.get(this.vertexes[i]);
for (var j = 0; j < adList.length; j++) {
resString += adList[j] + " ";
}
resString += "\n";
}
return resString;
};
/**
* 4.初始化顶点颜色
* 白色表示该顶点还没有被访问.
* 灰色表示该顶点被访问过, 但并未被探索过.
* 黑色表示该顶点被访问过且被完全探索过.
*/
Graph.prototype._initColors = function () {
var colors = [];
for (var i = 0; i < this.vertexes.length; i++) {
colors[this.vertexes[i]] = "white";
}
return colors;
};
// 5.广度优先遍历
Graph.prototype.bfs = function (initV, handler) {
var colors = this._initColors();
var queue = new Queue();
// 将定点放入队列
queue.enqueue(initV);
while (!queue.isEmpty()) {
var qVal = queue.dequeue(); // 拿到队头的顶点
var qAdj = this.adList.get(qVal); // 拿到队头对应的边
// 将队头设置正在探索中 灰色
colors[qVal] = "gray";
// 将队头顶点相邻的边 全部拿出来遍历一遍,并加入到队列中
for (var i = 0; i < qAdj.length; i++) {
var vertexeValue = qAdj[i]; // 取出相邻顶点
// 如果这个顶点是白色,说明没有被访问过
if (colors[vertexeValue] === "white") {
// 正在访问
colors[vertexeValue] = "gray";
// 将相邻顶点入队
queue.enqueue(vertexeValue);
}
}
// 代表该定点已经探索完毕,设置为黑色
colors[qVal] = "black";
// 输出
handler && handler(qVal);
}
};
// 6.深度优先遍历
Graph.prototype.dfs = function (initValue, handler) {
var colors = this._initColors(); // 初始化顶点颜色
this.dfsVisit(initValue, colors, handler); // 遍历
};
Graph.prototype.dfsVisit = function (v, colors, handler) {
colors[v] = "gray"; // 访问中
handler && handler(v); // 访问该顶点
var vList = this.adList.get(v); // 获取相邻顶点
for (var i = 0; i < vList.length; i++) {
if (colors[vList[i]] === "white") {
this.dfsVisit(vList[i], colors, handler); // 递归访问
}
}
colors[v] = "black"; // 访问结束
};
}
// 测试代码
let graph = new Graph();
// 添加顶点
let myVertexes = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
for (let i = 0; i < myVertexes.length; i++) {
graph.addVertex(myVertexes[i]);
}
// 添加边
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(graph.toString());
// A->B C D
// B->A E F
// C->A D G
// D->A C G H
// E->B I
// F->B
// G->C D
// H->D
// I->E
graph.bfs(graph.vertexes[0], function (value) {
console.log(value); // A,B,C,D,E,F,G,H,I,
});
graph.dfs(graph.vertexes[0], function (value) {
console.log(value); // A,B,E,I,F,C,D,G,H
});
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。