02-图的JavaScript实现

1. Graph类

1-1. 创建Graph类

class Graph {
    constructor(isDirected = false) {
        // 接收参数来表示图是否有向
        this.isDirected = isDirected;
        // 使用数组来存储图中所有顶点的名字
        this.vertices = [];
        // 用字典来存储邻接表
        this.adjList = new Map();
    }

    // 向图中添加新的顶点
    addVertex(v) {

        if (!this.vertices.includes(v)) {
            // 将顶点添加到顶点列表中
            this.vertices.push(v);
            // 设置顶点 v 作为键对应的字典值为一个空数组
            this.adjList.set(v, []);
        }

    }

    // 添加顶点间的边
    addEdge(v, w) {

        // 如果顶点 v 或 w 不存在于图中,要将它们加入顶点列表
        if (!this.adjList.get(v)) {
            this.addVertex(v);
        }
        if (!this.adjList.get(w)) {
            this.addVertex(w);
        }

        // 通过将 w 加入到 v 的邻接表中,添加了一条自顶点 v 到顶点 w 的边
        this.adjList.get(v).push(w);
        // 若是有向图,则需要添加自顶点 w 到顶点 v 的边
        if (!this.isDirected) {
            this.adjList.get(w).push(v);
        }
    }

    // 返回顶点列表
    getVertices() {
        return this.vertices;
    }

    // 返回邻接表
    getAdjList() {
        return this.adjList;
    }
}

module.exports = Graph;

1-2. 实现图的创建

const Graph = require("./graph");
const graph = new Graph();

var myVertices = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
for (let i = 0; i < myVertices.length; i++) {
    graph.addVertex(myVertices[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');

const vertices = graph.getVertices();
const adjList = graph.getAdjList();

let s = "";
vertices.forEach(element => {
    s += `${element} -> `;
    const lists = adjList.get(element);
    lists.forEach(element => {
        s += `${element} `
    });
    s += "\n";
});
console.log(s)
  • 输出
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

2. 广度优先遍历

在这里插入图片描述

  • 新建一个队列,把根节点入队;
  • 把队头出队并访问;
  • 把队头的没访问过的相邻节点入队;
  • 重复第二、第三步,直到队列为空

2-1. 广度优先遍历的代码实现

bfs() {
    // 用来记录节点是否访问过
    const visited = new Set();
    // 将图的某一节点先放入队列中
    const queue = [this.vertices[0]];
    // 记录访问过的节点
    visited.add(this.vertices[0]);

    while (queue.length) {
        const v = queue.shift();
        console.log(v);
        // 取出节点的邻接节点
        const neighbors = this.adjList.get(v);
        neighbors.forEach(element => {
            if(!visited.has(element)){
                queue.push(element);
                visited.add(element);
            }
        });
    }
}
  • 测试
graph.bfs();
// 输出结果
A
B
C
D
E
F
G
H
I

2-2. 使用广度优先遍历寻找最短路径

  • 给定一个图 G和源顶点 v,找出每个顶点 u和 v之间最短路径的距离(衡量标准是边的数量)
  • 本章中的图不是加权图。如果要计算加权图中的最短路径(例如,城市 A 和城市 B 之间的最短路径——GPS和 Google Maps中用到的算法),广度优先搜索未必合适
min_distance(vertices, adjList, target) {
    // 标记访问过的节点
    const visited = new Set();

    // 将目标点先放入队列
    const queue = [target];
    visited.add(target);

    // 存储每个点到目标点的距离
    const distances = {};
    // 表示前溯点
    const predecessors = {};

    vertices.forEach(vertice => {
        // 初始化每个节点的距离为0
        distances[vertice] = 0;
        // 初始化前溯点为空
        predecessors[vertice] = null;
    });

    // 记录节点与目标点的距离 与 节点的前溯点
    while (queue.length) {
        const v = queue.shift();
        const neighbors = adjList.get(v);
        neighbors.forEach(neighbor => {
            if (!visited.has(neighbor)) {
                distances[neighbor] = distances[v] + 1;
                predecessors[neighbor] = v;
                queue.push(neighbor);
                visited.add(neighbor);
            }
        });
    }

    // 输出目标点与其它顶点的最短路径(衡量标准是边的数量)
    vertices.forEach(vertice => {
        // 对与目标节点有连接的节点进行遍历
        if (distances[vertice]) {
            const path = [];
            // 追溯节点到目标节点的路径
            for (let v = vertice; v != target; v = predecessors[v]) {
                path.push(v);
            }
            path.push(target);
            // 输出路径
            let s = path.pop();
            while (path.length) {
                s += ` - ${path.pop()}`
            }
            console.log(s);
        }
    });
}
  • 测试
const vertices = graph.getVertices();
const adjList = graph.getAdjList();

graph.min_distance(vertices, adjList, vertices[0]);
// 输出结果
A - B
A - C
A - D
A - B - E
A - B - F
A - C - G
A - D - H
A - B - E - I

3. 深度优先遍历

在这里插入图片描述

  • 访问根节点;
  • 对根节点的没访问过的相邻节点挨个进行深度优先遍历。

3-1. 深度度优先遍历的代码实现

dfs(vertice, adjList, visited) {
    // vertice:访问的节点;adjList:邻接表;visited:记录访问到的节点
    // 输出访问过的节点
    console.log(vertice);
    visited.add(vertice);
    const neighbors = adjList.get(vertice);
    neighbors.forEach(neighbor => {
        if (!visited.has(neighbor)) {
            this.dfs(neighbor, adjList, visited);
        }
    });
}
  • 测试
const vertices = graph.getVertices();
const adjList = graph.getAdjList();
const visited = new Set();
graph.dfs(vertices[0], adjList, visited);
// 输出结果
A
B
E
I
F
C
D
G
H
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值