JavaScript中的图数据结构


图是一种对象的集合的图形表示,其中某些对象对通过链接连接。相互连接的对象由称为**顶点**的点表示,连接顶点的链接称为**边**。

正式地,图是一对集合(V, E),其中V是顶点的集合,而E是连接顶点对的边的集合。请查看以下图形 -

Graph Data Structure

在上图中,

V = {a, b, c, d, e}
E = {ab, ac, bd, cd, de}

术语

数学图可以在数据结构中表示。我们可以使用顶点数组和二维边数组来表示图。在继续之前,让我们熟悉一些重要的术语 -

  • 顶点 - 图的每个节点都表示为顶点。在以下示例中,带标签的圆圈表示顶点。因此,A 到 G 是顶点。我们可以使用数组来表示它们,如以下图像所示。这里 A 可以用索引 0 标识。B 可以用索引 1 标识,依此类推。

  • - 边表示两个顶点之间的路径或两个顶点之间的线。在以下示例中,从 A 到 B、B 到 C 等的线表示边。我们可以使用二维数组来表示数组,如以下图像所示。这里 AB 可以表示为第 0 行、第 1 列的 1,BC 可以表示为第 1 行、第 2 列的 1,依此类推,保持其他组合为 0。

  • 邻接 - 如果两个节点或顶点通过一条边相互连接,则它们是邻接的。在以下示例中,B 与 A 邻接,C 与 B 邻接,依此类推。

  • 路径 - 路径表示两个顶点之间的一系列边。在以下示例中,ABCD 表示从 A 到 D 的路径。

以下是使用 Javascript 实现图类的完整实现。

示例

const Queue = require("./Queue");
const Stack = require("./Stack");
const PriorityQueue = require("./PriorityQueue");

class Graph {
   constructor() {
      this.edges = {};
      this.nodes = [];
   }

   addNode(node) {
      this.nodes.push(node);
      this.edges[node] = [];
   }

   addEdge(node1, node2, weight = 1) {
      this.edges[node1].push({ node: node2, weight: weight });
      this.edges[node2].push({ node: node1, weight: weight });
   }

   addDirectedEdge(node1, node2, weight = 1) {
      this.edges[node1].push({ node: node2, weight: weight });
   }

   // addEdge(node1, node2) {
      //   this.edges[node1].push(node2);
      //   this.edges[node2].push(node1);
   // }

   // addDirectedEdge(node1, node2) {
      //   this.edges[node1].push(node2);
   // }

   display() {
      let graph = "";
      this.nodes.forEach(node => {
         graph += node + "->" + this.edges[node].map(n => n.node).join(", ") + "
";       });       console.log(graph);    }    BFS(node) {       let q = new Queue(this.nodes.length);       let explored = new Set();       q.enqueue(node);       explored.add(node);       while (!q.isEmpty()) {          let t = q.dequeue();          console.log(t);          this.edges[t].filter(n => !explored.has(n)).forEach(n => {             explored.add(n);             q.enqueue(n);          });       }    }    DFS(node) {       // Create a Stack and add our initial node in it       let s = new Stack(this.nodes.length);       let explored = new Set();       s.push(node);       // Mark the first node as explored       explored.add(node);       // We'll continue till our Stack gets empty       while (!s.isEmpty()) {          let t = s.pop();          // Log every element that comes out of the Stack          console.log(t);          // 1. In the edges object, we search for nodes this node is directly connected to.          // 2. We filter out the nodes that have already been explored.          // 3. Then we mark each unexplored node as explored and push it to the Stack.          this.edges[t].filter(n => !explored.has(n)).forEach(n => {             explored.add(n);             s.push(n);          });       }    }    topologicalSortHelper(node, explored, s) {       explored.add(node);       this.edges[node].forEach(n => {             if (!explored.has(n)) {                this.topologicalSortHelper(n, explored, s);             }          });          s.push(node);       }       topologicalSort() {          // Create a Stack and add our initial node in it          let s = new Stack(this.nodes.length);          let explored = new Set();          this.nodes.forEach(node => {             if (!explored.has(node)) {                this.topologicalSortHelper(node, explored, s);             }          });          while (!s.isEmpty()) {             console.log(s.pop());          }       }       BFSShortestPath(n1, n2) {          let q = new Queue(this.nodes.length);          let explored = new Set();          let distances = { n1: 0 };          q.enqueue(n1);          explored.add(n1);          while (!q.isEmpty()) {             let t = q.dequeue();             this.edges[t].filter(n => !explored.has(n)).forEach(n => {                explored.add(n);                distances[n] = distances[t] == undefined ? 1 : distances[t] + 1;                q.enqueue(n);             });          }          return distances[n2];       }       primsMST() {          // Initialize graph that'll contain the MST          const MST = new Graph();          if (this.nodes.length === 0) {             return MST;          }          // Select first node as starting node          let s = this.nodes[0];          // Create a Priority Queue and explored set          let edgeQueue = new PriorityQueue(this.nodes.length * this.nodes.length);          let explored = new Set();          explored.add(s);          MST.addNode(s);          // Add all edges from this starting node to the PQ taking weights as priority          this.edges[s].forEach(edge => {             edgeQueue.enqueue([s, edge.node], edge.weight);          });          // Take the smallest edge and add that to the new graph          let currentMinEdge = edgeQueue.dequeue();          while (!edgeQueue.isEmpty()) {             // COntinue removing edges till we get an edge with an unexplored node             while (!edgeQueue.isEmpty() && explored.has(currentMinEdge.data[1])) {                currentMinEdge = edgeQueue.dequeue();             }             let nextNode = currentMinEdge.data[1];             // Check again as queue might get empty without giving back unexplored element             if (!explored.has(nextNode)) {                MST.addNode(nextNode);                MST.addEdge(currentMinEdge.data[0], nextNode, currentMinEdge.priority);             // Again add all edges to the PQ             this.edges[nextNode].forEach(edge => {                edgeQueue.enqueue([nextNode, edge.node], edge.weight);             });             // Mark this node as explored             explored.add(nextNode);             s = nextNode;          }       }       return MST;    }    kruskalsMST() {       // Initialize graph that'll contain the MST       const MST = new Graph();       this.nodes.forEach(node => MST.addNode(node));       if (this.nodes.length === 0) {          return MST;       }       // Create a Priority Queue       let edgeQueue = new PriorityQueue(this.nodes.length * this.nodes.length);       // Add all edges to the Queue:       for (let node in this.edges) {          this.edges[node].forEach(edge => {             edgeQueue.enqueue([node, edge.node], edge.weight);          });       }       let uf = new UnionFind(this.nodes);       // Loop until either we explore all nodes or queue is empty       while (!edgeQueue.isEmpty()) {          // Get the edge data using destructuring          let nextEdge = edgeQueue.dequeue();          let nodes = nextEdge.data;          let weight = nextEdge.priority;          if (!uf.connected(nodes[0], nodes[1])) {             MST.addEdge(nodes[0], nodes[1], weight);             uf.union(nodes[0], nodes[1]);          }       }       return MST;    }    djikstraAlgorithm(startNode) {       let distances = {};       // Stores the reference to previous nodes       let prev = {};       let pq = new PriorityQueue(this.nodes.length * this.nodes.length);       // Set distances to all nodes to be infinite except startNode       distances[startNode] = 0;       pq.enqueue(startNode, 0);       this.nodes.forEach(node => {          if (node !== startNode) distances[node] = Infinity;          prev[node] = null;       });       while (!pq.isEmpty()) {          let minNode = pq.dequeue();          let currNode = minNode.data;          let weight = minNode.priority;          this.edges[currNode].forEach(neighbor => {             let alt = distances[currNode] + neighbor.weight;             if (alt < distances[neighbor.node]) {                distances[neighbor.node] = alt;                prev[neighbor.node] = currNode;                pq.enqueue(neighbor.node, distances[neighbor.node]);             }          });       }       return distances;    }    floydWarshallAlgorithm() {       let dist = {};       for (let i = 0; i < this.nodes.length; i++) {          dist[this.nodes[i]] = {};       // For existing edges assign the dist to be same as weight       this.edges[this.nodes[i]].forEach(e => (dist[this.nodes[i]][e.node] = e.weight));    this.nodes.forEach(n => {       // For all other nodes assign it to infinity       if (dist[this.nodes[i]][n] == undefined)       dist[this.nodes[i]][n] = Infinity;       // For self edge assign dist to be 0       if (this.nodes[i] === n) dist[this.nodes[i]][n] = 0;    }); } this.nodes.forEach(i => {    this.nodes.forEach(j => {       this.nodes.forEach(k => {          // Check if going from i to k then from k to j is better          // than directly going from i to j. If yes then update          // i to j value to the new value          if (dist[i][k] + dist[k][j] < dist[i][j])          dist[i][j] = dist[i][k] + dist[k][j];       });    }); }); return dist; } } class UnionFind {    constructor(elements) {       // Number of disconnected components       this.count = elements.length;       // Keep Track of connected components       this.parent = {};       // Initialize the data structure such that all elements have themselves as parents       elements.forEach(e => (this.parent[e] = e));    }    union(a, b) {       let rootA = this.find(a);       let rootB = this.find(b);       // Roots are same so these are already connected.       if (rootA === rootB) return;       // Always make the element with smaller root the parent.       if (rootA < rootB) {          if (this.parent[b] != b) this.union(this.parent[b], a);          this.parent[b] = this.parent[a];       } else {          if (this.parent[a] != a) this.union(this.parent[a], b);          this.parent[a] = this.parent[b];       }    }    // Returns final parent of a node    find(a) {       while (this.parent[a] !== a) {       a = this.parent[a];    }    return a; } // Checks connectivity of the 2 nodes    connected(a, b) {       return this.find(a) === this.find(b);    } }

更新于: 2020年6月15日

1K+ 次查看

开启你的 职业生涯

通过完成课程获得认证

开始学习
广告