Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Javascript

โครงสร้างข้อมูลกราฟใน Javascript


กราฟคือการแสดงภาพชุดของวัตถุที่วัตถุบางคู่เชื่อมต่อกันด้วยลิงก์ วัตถุที่เชื่อมต่อกันจะแสดงด้วยจุดที่เรียกว่า จุดยอด และลิงค์ที่เชื่อมจุดยอดเรียกว่า ขอบ .

อย่างเป็นทางการ กราฟคือคู่ของเซต (V, E) โดยที่ วี คือเซตของจุดยอดและ E คือเซตของขอบที่เชื่อมระหว่างจุดยอด ลองดูที่กราฟต่อไปนี้ −

โครงสร้างข้อมูลกราฟใน Javascript

ในกราฟด้านบน

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

ศัพท์เฉพาะ

กราฟทางคณิตศาสตร์สามารถแสดงได้ในโครงสร้างข้อมูล เราสามารถแสดงกราฟโดยใช้อาร์เรย์ของจุดยอดและอาร์เรย์ของขอบสองมิติ ก่อนที่เราจะดำเนินการต่อ มาทำความคุ้นเคยกับคำศัพท์ที่สำคัญบางคำก่อน -

  • จุดสุดยอด − แต่ละโหนดของกราฟแสดงเป็นจุดยอด ในตัวอย่างต่อไปนี้ วงกลมที่มีป้ายกำกับแสดงถึงจุดยอด ดังนั้น A ถึง G คือจุดยอด เราสามารถแสดงได้โดยใช้อาร์เรย์ดังแสดงในภาพต่อไปนี้ A สามารถระบุได้ด้วยดัชนี 0 B สามารถระบุได้โดยใช้ดัชนี 1 เป็นต้น

  • ขอบ − ขอบแสดงถึงเส้นทางระหว่างจุดยอดสองจุดหรือเส้นแบ่งระหว่างจุดยอดสองจุด ในตัวอย่างต่อไปนี้ เส้นจาก A ถึง B, B ถึง C และอื่นๆ แสดงถึงขอบ เราสามารถใช้อาร์เรย์สองมิติเพื่อแสดงอาร์เรย์ดังแสดงในภาพต่อไปนี้ ในที่นี้ AB สามารถแสดงเป็น 1 ที่แถว 0, คอลัมน์ 1, BC เป็น 1 ที่แถว 1, คอลัมน์ 2 และอื่นๆ โดยคงชุดค่าผสมอื่นๆ เป็น 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(", ") + "\n";
      });
      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);
   }
}