Advertisement
ouija_bh

Topological sort of directed graph

Sep 17th, 2023 (edited)
154
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 13.37 KB | Source Code | 0 0
  1. /* Data structures and methods for topological sort of directed graph. Includes
  2.  * a short demonstration.
  3.  * Sources:
  4.  * 1. Open Data Structures by Pat Morin.
  5.  * 2. Communications of the ACM, "Topological sorting of large networks" by
  6.  *    Arthur Kahn.
  7.  */
  8.  
  9. class Vertex {
  10.   /** constructor() credit: Morin 12.2, 12.3.1.
  11.    * @param {string} name vertex name
  12.    * @param {number} index index in adjacency lists
  13.    */
  14.   constructor(name, index) {
  15.     this.name = name;
  16.     this.index = index;
  17.     this.adjacent = [];  // indexes of adjacent vertices
  18.     this.inEdges;    // edges in the form [(index | -1 if discovered)*]
  19.     this.bSeen = false;
  20.   }
  21. }
  22.  
  23. /** Represents a graph. */
  24. class AdjacencyLists {
  25.   #namesSet;
  26.   /** constructor() Builds array of vertices with given names. credit: Morin 12.2.
  27.    * @param {string[]} names names of all vertices
  28.    */
  29.   constructor(names) {
  30.     this.vertices = [];  // array of Vertex's
  31.     this.#namesSet = new Set(names);
  32.  
  33.     names = Array.from(this.#namesSet);  // remove any duplicate names
  34.     for (var i = 0; i < names.length; i++) {
  35.       this.vertices.push(new Vertex(names[i], i));
  36.     }
  37.   }
  38.  
  39.   /** getIndexFromName() get vertex index from name
  40.    * @param {string} vertexName name of vertex to find
  41.    * @returns {number} index of vertex, or -1 if not found
  42.    */
  43.   getIndexFromName(vertexName) {
  44.     var vertex = this.vertices.find(function(a) { return a.name == vertexName; });
  45.     if (vertex == undefined) { return -1; }
  46.     return vertex.index;
  47.   }
  48.  
  49.   /** addEdgeByIndex() credit: Morin 12.2.
  50.    * @param {number} vertexIndex index of vertex to modify
  51.    * @param {number} edgeIndex index of edge to add
  52.    * @returns {boolean} success?
  53.    */
  54.   addEdgeByIndex(vertexIndex, edgeIndex) {
  55.     if ((vertexIndex < 0) || (edgeIndex < 0) ||
  56.         (vertexIndex >= this.vertices.length) || (edgeIndex >= this.vertices.length)) {
  57.       return false;
  58.     }
  59.     this.vertices[vertexIndex].adjacent.push(edgeIndex);
  60.     return true;
  61.   }
  62.  
  63.   /** addEdgeByName()
  64.    * @param {string} vertexName name of vertex to modify
  65.    * @param {string} edgeName name of edge to add
  66.    * @returns {boolean} success?
  67.    */
  68.   addEdgeByName(vertexName, edgeName) {
  69.     var vertexIndex = this.getIndexFromName(vertexName);
  70.     var edgeIndex = this.getIndexFromName(edgeName);
  71.     return this.addEdgeByIndex(vertexIndex, edgeIndex);
  72.   }
  73.  
  74.   /** outEdgesByIndex() credit: Morin 12.2.
  75.    * @param {number} vertexIndex index of vertex to search
  76.    * @returns {number[]} indexes of out edges, or undefined if no such vertex
  77.    */
  78.   outEdgesByIndex(vertexIndex) {
  79.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  80.       return undefined;
  81.     }
  82.     return this.vertices[vertexIndex].adjacent;
  83.   }
  84.  
  85.   /** outEdgesByName()
  86.    * @param {string} vertexName name of vertex to search
  87.    * @returns {string[]} names of out edges, or undefined if no such vertex
  88.    */
  89.   outEdgesByName(vertexName) {
  90.     var indexes = this.outEdgesByIndex(this.getIndexFromName(vertexName));
  91.     if (indexes == undefined) { return undefined; }
  92.     var names = [];
  93.     for (var i = 0; i < indexes.length; i++) {
  94.       names.push(this.vertices[indexes[i]].name);
  95.     }
  96.     return names;
  97.   }
  98.  
  99.   /** inEdgesByIndex() credit: Morin 12.2.
  100.    * @param {number} vertexIndex index of vertex to search
  101.    * @returns {number[]} indexes of in edges, or undefined if no such vertex
  102.    */
  103.   inEdgesByIndex(vertexIndex) {
  104.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  105.       return undefined;
  106.     }
  107.     var edgeIndexes = [];  // array of number's
  108.     for (var i = 0; i < this.vertices.length; i++) {
  109.       if (this.vertices[i].adjacent.findIndex(function(a) { return a == vertexIndex; }) != -1) {
  110.         edgeIndexes.push(i);
  111.       }
  112.     }
  113.     return edgeIndexes;
  114.   }
  115.  
  116.   /** inEdgesByName()
  117.    * @param {string} vertexName name of vertex to search
  118.    * @returns {string[]} names of in edges, or undefined if no such vertex
  119.    */
  120.   inEdgesByName(vertexName) {
  121.     var indexes = this.inEdgesByIndex(this.getIndexFromName(vertexName));
  122.     if (indexes == undefined) { return undefined; }
  123.     var names = [];
  124.     for (var i = 0; i < indexes.length; i++) {
  125.       names.push(this.vertices[indexes[i]].name);
  126.     }
  127.     return names;
  128.   }
  129.  
  130.   /** outDegreeByIndex() credit: Morin AdjacencyLists.java.
  131.    * @param {number} vertexIndex index of vertex to search
  132.    * @returns {number} out degree of vertex, or -1 if no such vertex
  133.    */
  134.   outDegreeByIndex(vertexIndex) {
  135.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  136.       return -1;
  137.     }
  138.     return this.vertices[vertexIndex].adjacent.length;
  139.   }
  140.  
  141.   /** outDegreeByName()
  142.    * @param {string} vertexName name of vertex to search
  143.    * @returns {number} out degree of vertex, or -1 if no such vertex
  144.    */
  145.   outDegreeByName(vertexName) {
  146.     return this.outDegreeByIndex(this.getIndexFromName(vertexName));
  147.   }
  148.  
  149.   /** inDegreeByIndex() credit: Morin AdjacencyLists.java.
  150.    * @param {number} vertexIndex index of vertex to search
  151.    * @returns {number} in degree of vertex, or -1 if no such vertex
  152.    */
  153.   inDegreeByIndex(vertexIndex) {
  154.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  155.       return -1;
  156.     }
  157.     var count = 0;
  158.     for (var i = 0; i < this.vertices.length; i++) {
  159.       if (this.vertices[i].adjacent.findIndex(function(a) { return a == vertexIndex; }) != -1) {
  160.         count++;
  161.       }
  162.     }
  163.     return count;
  164.   }
  165.  
  166.   /** inDegreeByName()
  167.    * @param {string} vertexName name of vertex to search
  168.    * @returns {number} in degree of vertex, or -1 if no such vertex
  169.    */
  170.   inDegreeByName(vertexName) {
  171.     return this.inDegreeByIndex(this.getIndexFromName(vertexName));
  172.   }
  173.  
  174.   /** #getEdgeCount() Get number of undiscovered edges.
  175.    * @param {number[]} edges indexes to search
  176.    * @returns {number} edge count
  177.    */
  178.   #getEdgeCount(edges) {
  179.     var count = 0;
  180.     for (var i = 0; i < edges.length; i++) {
  181.       if (edges[i] != -1) { count++; }
  182.     }
  183.     return count;
  184.   }
  185.  
  186.   /** #removeEdge() Mark edge as discovered.
  187.    * @param {number[]} edges indexes to search
  188.    * @param {number} edge index into this.vertices
  189.    */
  190.   #removeEdge(edges, edge) {
  191.     var index = edges.findIndex(function(a) { return a == edge; });
  192.     edges[index] = -1;
  193.   }
  194.  
  195.   /** unseen() makes all vertices undiscovered */
  196.   unseen() {
  197.     for (var i = 0; i < this.vertices.length; i++) {
  198.       this.vertices[i].inEdges = undefined;
  199.       this.vertices[i].bSeen = false;
  200.     }
  201.   }
  202.  
  203.   /** bfTopo() Breadth-first topological sort. credit: Kahn.
  204.    * @returns {Vertex[]} sorted vertices, or undefined if graph is cyclic
  205.    */
  206.   bfTopo() {
  207.     var bfVertices = [];  // array of Vertex's
  208.     var queue = [];   // indexes of vertices with in degree zero
  209.  
  210.     this.unseen();
  211.     for (var i = 0; i < this.vertices.length; i++) {
  212.       var res = this.inEdgesByIndex(i);
  213.       this.vertices[i].inEdges = res;
  214.       if (this.#getEdgeCount(res) == 0) {
  215.         queue.push(i);
  216.         this.vertices[i].bSeen = true;
  217.       }
  218.     }
  219.     if (queue.length == 0) { return undefined; }
  220.     while (queue.length > 0) {
  221.       var vertexIndex = queue.shift();
  222.       bfVertices.push(this.vertices[vertexIndex]);
  223.       var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  224.       for (var i = 0; i < edgeIndexes.length; i++) {
  225.         var edge = this.vertices[edgeIndexes[i]];
  226.         this.#removeEdge(edge.inEdges, vertexIndex);
  227.         if (this.#getEdgeCount(edge.inEdges) == 0) {
  228.           queue.push(edge.index);
  229.           edge.bSeen = true;
  230.         }
  231.       }
  232.     }
  233.     for (var i = 0; i < this.vertices.length; i++) {
  234.       if (this.#getEdgeCount(this.vertices[i].inEdges) > 0) { return undefined; }
  235.     }
  236.     return bfVertices;
  237.   }
  238.  
  239.   /** #topoInner() Inner depth-first topological sort. credit: Kahn.
  240.    * @param {number} vertexIndex index of vertex to visit
  241.    * @param {Vertex[]} dfVertices sorted vertices
  242.    */
  243.   #topoInner(vertexIndex , dfVertices) {
  244.     var vertex = this.vertices[vertexIndex];
  245.     vertex.bSeen = true;
  246.     var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  247.     for (var i = 0; i < edgeIndexes.length; i++) {
  248.       var edge = this.vertices[edgeIndexes[i]];
  249.       this.#removeEdge(edge.inEdges, vertexIndex);
  250.       edge.bSeen = true;
  251.       if (this.#getEdgeCount(edge.inEdges) == 0) {
  252.         this.#topoInner(edge.index, dfVertices);
  253.       }
  254.     }
  255.     dfVertices.unshift(vertex);
  256.   }
  257.  
  258.   /** topo() Depth-first topological sort. credit: Kahn.
  259.    * @returns {Vertex[]} sorted vertices, or undefined if graph is cyclic
  260.    */
  261.   topo() {
  262.     var dfVertices = [];  // array of Vertex's
  263.     var stack = [];   // indexes of vertices with in degree zero
  264.  
  265.     this.unseen();
  266.     for (var i = 0; i < this.vertices.length; i++) {
  267.       var res = this.inEdgesByIndex(i);
  268.       this.vertices[i].inEdges = res;
  269.       if (this.#getEdgeCount(res) == 0) { stack.push(i); }
  270.     }
  271.     if (stack.length == 0) { return undefined; }
  272.     while (stack.length > 0) {
  273.       var vertexIndex = stack.pop();
  274.       this.#topoInner(vertexIndex, dfVertices);
  275.     }
  276.     for (var i = 0; i < this.vertices.length; i++) {
  277.       if (this.#getEdgeCount(this.vertices[i].inEdges) > 0) { return undefined; }
  278.     }
  279.     return dfVertices;
  280.   }
  281.  
  282.   /** topo2() Depth-first topological sort. credit: Kahn.
  283.    * @returns {Vertex[]} sorted vertices, or undefined if graph is cyclic
  284.    */
  285.   topo2() {
  286.     var dfVertices = [];  // array of Vertex's
  287.     var stack = [];   // indexes of vertices with in degree zero
  288.  
  289.     this.unseen();
  290.     for (var i = 0; i < this.vertices.length; i++) {
  291.       var res = this.inEdgesByIndex(i);
  292.       this.vertices[i].inEdges = res;
  293.       if (this.#getEdgeCount(res) == 0) { stack.push(i); }
  294.     }
  295.     if (stack.length == 0) { return undefined; }
  296.     while (stack.length > 0) {
  297.       var vertexIndex = stack.pop();
  298.       var vertex = this.vertices[vertexIndex];
  299.       dfVertices.push(vertex);
  300.       if (!vertex.bSeen) {
  301.         vertex.bSeen = true;
  302.         var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  303.         for (var i = 0; i < edgeIndexes.length; i++) {
  304.           var edge = this.vertices[edgeIndexes[i]];
  305.           this.#removeEdge(edge.inEdges, vertexIndex);
  306.           if (this.#getEdgeCount(edge.inEdges) == 0) {
  307.             stack.push(edge.index);
  308.           }
  309.         }
  310.       }
  311.     }
  312.     for (var i = 0; i < this.vertices.length; i++) {
  313.       if (this.#getEdgeCount(this.vertices[i].inEdges) > 0) { return undefined; }
  314.     }
  315.     return dfVertices;
  316.   }
  317.  
  318.   /** formatVertices()
  319.    * @param {Vertex[]} vertices vertices in this graph to format as list,
  320.    *                   or undefined if graph is cyclic
  321.    * @returns {string} vertex names (start vertices followed by asterisk) formatted as list
  322.    */
  323.   formatVertices(vertices) {
  324.     if (vertices == undefined) { return "\n error: graph is cyclic"; }
  325.     var names = "";
  326.     for (var i = 0; i < vertices.length; i++) {
  327.       names += vertices[i].name;
  328.       if (this.inDegreeByIndex(vertices[i].index) == 0) { names += "*"; }
  329.       if (i < vertices.length - 1) { names += ", "; }
  330.     }
  331.     return "\n " + names;
  332.   }
  333. }
  334.  
  335.  
  336. /** names of all vertices */
  337. var vertexNames = ["adams", "boston", "chicago", "denver", "easy", "frank", "george", "henry"];
  338. // create vertices
  339. var graph = new AdjacencyLists(vertexNames);
  340. /** edges in the form [vertex][array of edge names]. */
  341. var edges = [ ["boston"],
  342.               ["chicago", "easy", "denver"],
  343.               ["frank"],
  344.               ["george"],
  345.               ["frank", "george"],
  346.               [],
  347.               [],
  348.               [] ];
  349. // add edges
  350. for (var i = 0; i < edges.length; i++) {
  351.   for (var j = 0; j < edges[i].length; j++) {
  352.     graph.addEdgeByName(vertexNames[i], edges[i][j]);
  353.   }
  354. }
  355. // edges and degrees
  356. console.log("out degree of george = " + graph.outDegreeByIndex(graph.getIndexFromName("george")));
  357. console.log("vertex frank in edges: " + graph.inEdgesByName("frank"));
  358. console.log("in degree of frank = " + graph.inDegreeByName("frank"));
  359. // bf sort
  360. var vertices = graph.bfTopo();
  361. console.log("bf sort" + graph.formatVertices(vertices));
  362. // df sort
  363. vertices = graph.topo();
  364. console.log("df sort" + graph.formatVertices(vertices));
  365. // df sort #2
  366. vertices = graph.topo2();
  367. console.log("df sort #2" + graph.formatVertices(vertices));
  368. // add an edge
  369. graph.addEdgeByName("george", "frank");
  370. console.log("--added edge from george to frank--");
  371. // edges and degrees
  372. console.log("vertex george out edges: " + graph.outEdgesByName("george"));
  373. console.log("out degree of george = " + graph.outDegreeByName("george"));
  374. console.log("in degree of frank = " + graph.inDegreeByIndex(graph.getIndexFromName("frank")));
  375. // bf sort
  376. vertices = graph.bfTopo();
  377. console.log("bf sort" + graph.formatVertices(vertices));
  378. // df sort
  379. vertices = graph.topo();
  380. console.log("df sort" + graph.formatVertices(vertices));
  381. // add another edge
  382. graph.addEdgeByName("chicago", "adams");
  383. console.log("--added edge from chicago to adams, creating a cycle--");
  384. // degrees
  385. console.log("out degree of chicago = " + graph.outDegreeByName("chicago"));
  386. console.log("in degree of adams = " + graph.inDegreeByName("adams"));
  387. // bf sort
  388. vertices = graph.bfTopo();
  389. console.log("bf sort" + graph.formatVertices(vertices));
  390. // df sort
  391. vertices = graph.topo();
  392. console.log("df sort" + graph.formatVertices(vertices));
  393.  
  394. /** @version 1.0a */
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement