Advertisement
ouija_bh

Graph traversal with open content

Aug 13th, 2023 (edited)
251
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 17.59 KB | Source Code | 0 0
  1. /* Data structures and methods for graph traversal. Ported from Open Data Structures
  2.  * by Pat Morin, with improvements to make them more user-friendly. Includes a short
  3.  * demonstration of their use.
  4.  */
  5.  
  6. // vertex colors
  7. const white = 0, grey = 1, black = 2;
  8.  
  9. class Vertex {
  10.   /** constructor() credit: Morin 12.2, 12.3.2.
  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.color = white;
  19.     this.level = 1;
  20.     this.parentName = "NULL";
  21.     this.parentIndex = -1;
  22.   }
  23. }
  24.  
  25. /** Represents a graph. */
  26. class AdjacencyLists {
  27.   #namesSet;
  28.   /** constructor() Builds array of vertices with given names. credit: Morin 12.2.
  29.    * @param {string[]} names names of all vertices
  30.    */
  31.   constructor(names) {
  32.     this.vertices = [];  // array of Vertex's
  33.     this.#namesSet = new Set(names);
  34.  
  35.     names = Array.from(this.#namesSet);  // remove any duplicate names
  36.     for (var i = 0; i < names.length; i++) {
  37.       this.vertices.push(new Vertex(names[i], i));
  38.     }
  39.   }
  40.  
  41.   /** getVertexFromName() get vertex ref from name
  42.    * @param {string} vertexName name of vertex to find
  43.    * @returns {Vertex} the vertex, or undefined if not found
  44.    */
  45.   getVertexFromName(vertexName) {
  46.     return this.vertices.find(function(a) { return a.name == vertexName; });
  47.   }
  48.  
  49.   /** getIndexFromName() get vertex index from name
  50.    * @param {string} vertexName name of vertex to find
  51.    * @returns {number} index of vertex, or -1 if not found
  52.    */
  53.   getIndexFromName(vertexName) {
  54.     var vertex = this.getVertexFromName(vertexName);
  55.     if (vertex == undefined) { return -1; }
  56.     return vertex.index;
  57.   }
  58.  
  59.   /** addVertex() ...simple, yes?
  60.    * @param {string} vertexName name of vertex to add
  61.    * @returns {boolean} success?
  62.    */
  63.   addVertex(vertexName) {
  64.     var sizePrev = this.#namesSet.size;
  65.     this.#namesSet.add(vertexName);
  66.     if (this.#namesSet.size == sizePrev) { return false; }
  67.     this.vertices.push(new Vertex(vertexName, this.vertices.length));
  68.     return true;
  69.   }
  70.  
  71.   /** removeVertex() ...should you need it.
  72.    * @param {number} vertexIndex index of vertex to remove
  73.    * @returns {boolean} success?
  74.    */
  75.   removeVertex(vertexIndex) {
  76.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  77.       return false;
  78.     }
  79.     var vertexName = this.vertices[vertexIndex].name;
  80.     this.#namesSet.delete(vertexName);
  81.     var bLast = false;  // removing last vertex?
  82.     if (vertexIndex == this.vertices.length - 1) { bLast = true; }
  83.  
  84.     // remove vertex in edges and parent relationships
  85.     for (var i = 0; i < this.vertices.length; i++) {
  86.       var vertex = this.vertices[i];
  87.       if (vertex.index != vertexIndex) {
  88.         vertex.adjacent = vertex.adjacent.filter(function(a) { return a != vertexIndex; });
  89.         if (vertex.parentIndex == vertexIndex) {
  90.           vertex.parentName = "NULL";
  91.           vertex.parentIndex = -1;
  92.         }
  93.       }
  94.     }
  95.     // remove the vertex
  96.     this.vertices = this.vertices.slice(0, vertexIndex).concat(this.vertices.slice(vertexIndex + 1));
  97.     // decrement indexes where needed
  98.     if (!bLast) {
  99.       for (var i = 0; i < this.vertices.length; i++) {
  100.         var vertex = this.vertices[i];
  101.         if (vertex.index > vertexIndex) { vertex.index--; }
  102.         if (vertex.parentIndex > vertexIndex) { vertex.parentIndex--; }
  103.         for (var j = 0; j < vertex.adjacent.length; j++) {
  104.           if (vertex.adjacent[j] > vertexIndex) { vertex.adjacent[j]--; }
  105.         }
  106.       }
  107.     }
  108.     return true;
  109.   }
  110.  
  111.   /** addEdgeByIndex() credit: Morin 12.2.
  112.    * @param {number} vertexIndex index of vertex to modify
  113.    * @param {number} edgeIndex index of edge to add
  114.    * @returns {boolean} success?
  115.    */
  116.   addEdgeByIndex(vertexIndex, edgeIndex) {
  117.     if ((vertexIndex < 0) || (edgeIndex < 0) ||
  118.         (vertexIndex >= this.vertices.length) || (edgeIndex >= this.vertices.length)) {
  119.       return false;
  120.     }
  121.     this.vertices[vertexIndex].adjacent.push(edgeIndex);
  122.     return true;
  123.   }
  124.  
  125.   /** addEdgeByName()
  126.    * @param {string} vertexName name of vertex to modify
  127.    * @param {string} edgeName name of edge to add
  128.    * @returns {boolean} success?
  129.    */
  130.   addEdgeByName(vertexName, edgeName) {
  131.     var vertexIndex = this.getIndexFromName(vertexName);
  132.     var edgeIndex = this.getIndexFromName(edgeName);
  133.     return this.addEdgeByIndex(vertexIndex, edgeIndex);
  134.   }
  135.  
  136.   /** removeEdgeByIndex() credit: Morin 12.2.
  137.    * @param {number} vertexIndex index of vertex to modify
  138.    * @param {number} edgeIndex index of edge to remove
  139.    * @returns {boolean} success?
  140.    */
  141.   removeEdgeByIndex(vertexIndex, edgeIndex) {
  142.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  143.       return false;
  144.     }
  145.     var vertex = this.vertices[vertexIndex];
  146.     var index = vertex.adjacent.findIndex(function(a) { return a == edgeIndex; });
  147.     if (index == -1) { return false; }
  148.     vertex.adjacent = vertex.adjacent.slice(0, index).concat(vertex.adjacent.slice(index + 1));
  149.     return true;
  150.   }
  151.  
  152.   /** removeEdgeByName()
  153.    * @param {string} vertexName name of vertex to modify
  154.    * @param {string} edgeName name of edge to remove
  155.    * @returns {boolean} success?
  156.    */
  157.   removeEdgeByName(vertexName, edgeName) {
  158.     var vertexIndex = this.getIndexFromName(vertexName);
  159.     var edgeIndex = this.getIndexFromName(edgeName);
  160.     return this.removeEdgeByIndex(vertexIndex, edgeIndex);
  161.   }
  162.  
  163.   /** hasEdgeByIndex() credit: Morin 12.2.
  164.    * @param {number} vertexIndex index of vertex to search
  165.    * @param {number} edgeIndex index of edge to find
  166.    * @returns {boolean} true if edge found
  167.    */
  168.   hasEdgeByIndex(vertexIndex, edgeIndex) {
  169.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  170.       return false;
  171.     }
  172.     var index = this.vertices[vertexIndex].adjacent.findIndex(function(a) { return a == edgeIndex; });
  173.     if (index == -1) { return false; }
  174.     return true;
  175.   }
  176.  
  177.   /** hasEdgeByName()
  178.    * @param {string} vertexName name of vertex to search
  179.    * @param {string} edgeName name of edge to find
  180.    * @returns {boolean} true if edge found
  181.    */
  182.   hasEdgeByName(vertexName, edgeName) {
  183.     var vertexIndex = this.getIndexFromName(vertexName);
  184.     var edgeIndex = this.getIndexFromName(edgeName);
  185.     return this.hasEdgeByIndex(vertexIndex, edgeIndex);
  186.   }
  187.  
  188.   /** outEdgesByIndex() credit: Morin 12.2.
  189.    * @param {number} vertexIndex index of vertex to search
  190.    * @returns {number[]} indexes of out edges, or undefined if no such vertex
  191.    */
  192.   outEdgesByIndex(vertexIndex) {
  193.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  194.       return undefined;
  195.     }
  196.     return this.vertices[vertexIndex].adjacent;
  197.   }
  198.  
  199.   /** outEdgesByName()
  200.    * @param {string} vertexName name of vertex to search
  201.    * @returns {string[]} names of out edges, or undefined if no such vertex
  202.    */
  203.   outEdgesByName(vertexName) {
  204.     var indexes = this.outEdgesByIndex(this.getIndexFromName(vertexName));
  205.     if (indexes == undefined) { return undefined; }
  206.     var names = [];
  207.     for (var i = 0; i < indexes.length; i++) {
  208.       names.push(this.vertices[indexes[i]].name);
  209.     }
  210.     return names;
  211.   }
  212.  
  213.   /** inEdgesByIndex() credit: Morin 12.2.
  214.    * @param {number} vertexIndex index of vertex to search
  215.    * @returns {number[]} indexes of in edges, or undefined if no such vertex
  216.    */
  217.   inEdgesByIndex(vertexIndex) {
  218.     if ((vertexIndex < 0) || (vertexIndex >= this.vertices.length)) {
  219.       return undefined;
  220.     }
  221.     var edgeIndexes = [];  // array of number's
  222.     for (var i = 0; i < this.vertices.length; i++) {
  223.       if (this.vertices[i].adjacent.findIndex(function(a) { return a == vertexIndex; }) != -1) {
  224.         edgeIndexes.push(i);
  225.       }
  226.     }
  227.     return edgeIndexes;
  228.   }
  229.  
  230.   /** inEdgesByName()
  231.    * @param {string} vertexName name of vertex to search
  232.    * @returns {string[]} names of in edges, or undefined if no such vertex
  233.    */
  234.   inEdgesByName(vertexName) {
  235.     var indexes = this.inEdgesByIndex(this.getIndexFromName(vertexName));
  236.     if (indexes == undefined) { return undefined; }
  237.     var names = [];
  238.     for (var i = 0; i < indexes.length; i++) {
  239.       names.push(this.vertices[indexes[i]].name);
  240.     }
  241.     return names;
  242.   }
  243.  
  244.   /** flat() makes all vertices have depth of 1 and no parent */
  245.   flat() {
  246.     for (var i = 0; i < this.vertices.length; i++) {
  247.       this.vertices[i].level = 1;
  248.       this.vertices[i].parentName = "NULL";
  249.       this.vertices[i].parentIndex = -1;
  250.     }
  251.   }
  252.  
  253.   /** unvisited() makes all vertices undiscovered */
  254.   unvisited() {
  255.     for (var i = 0; i < this.vertices.length; i++) {
  256.       this.vertices[i].color = white;
  257.     }
  258.   }
  259.  
  260.   /** bfs() Breadth-first search. The original method obtains neither vertex parent nor vertex
  261.    *  level, and neither is obtained here. credit: Morin 12.3.1.
  262.    * @param {number} startIndex index of vertex to start at
  263.    * @returns {Vertex[]} discovered vertices
  264.    */
  265.   bfs(startIndex) {
  266.     var bfsVertices = [];  // array of Vertex's
  267.     var queue = [];   // queue of indexes into this.vertices
  268.  
  269.     this.flat();
  270.     var vertexIndex = startIndex;
  271.     var vertex = this.vertices[vertexIndex];
  272.     queue.push(vertexIndex);
  273.     vertex.color = grey;
  274.     while (queue.length > 0) {
  275.       vertexIndex = queue.shift();
  276.       bfsVertices.push(this.vertices[vertexIndex]);
  277.       var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  278.       for (var i = 0; i < edgeIndexes.length; i++) {
  279.         var edge = this.vertices[edgeIndexes[i]];
  280.         if (edge.color == white) {
  281.           queue.push(edgeIndexes[i]);
  282.           edge.color = grey;
  283.         }
  284.       }
  285.     }
  286.     this.unvisited();
  287.     return bfsVertices;
  288.   }
  289.  
  290.   /** #dfsInner() Inner depth-first search. credit: Morin 12.3.2.
  291.    * @param {number} vertexIndex index of vertex to visit
  292.    * @param {Vertex[]} dfsVertices discovered vertices
  293.    */
  294.   #dfsInner(vertexIndex, dfsVertices) {
  295.     var vertex = this.vertices[vertexIndex];
  296.     vertex.color = grey;
  297.     var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  298.     for (var i = 0; i < edgeIndexes.length; i++) {
  299.       var edge = this.vertices[edgeIndexes[i]];
  300.       if (edge.color == white) {
  301.         edge.color = grey;
  302.         this.#dfsInner(edgeIndexes[i], dfsVertices);
  303.       }
  304.     }
  305.     dfsVertices.unshift(vertex);
  306.     vertex.color = black;
  307.   }
  308.  
  309.   /** dfs() Depth-first search. The original method obtains neither vertex parent nor vertex
  310.    *  level, and neither is obtained here. credit: Morin 12.3.2.
  311.    * @param {number} startIndex index of vertex to start at
  312.    * @returns {Vertex[]} discovered vertices
  313.    */
  314.   dfs(startIndex) {
  315.     var dfsVertices = [];  // array of Vertex's
  316.  
  317.     this.flat();
  318.     this.#dfsInner(startIndex, dfsVertices);
  319.     this.unvisited();
  320.     return dfsVertices;
  321.   }
  322.  
  323.   /** dfs2() Depth-first search. credit: Morin 12.3.2.
  324.    * @param {number} startIndex index of vertex to start at
  325.    * @returns {Vertex[]} discovered vertices
  326.    */
  327.   dfs2(startIndex) {
  328.     var dfsVertices = [];  // array of Vertex's
  329.     var stack = [];   // stack of indexes into this.vertices
  330.  
  331.     this.flat();
  332.     var vertexIndex = startIndex;
  333.     var vertex = this.vertices[vertexIndex];
  334.     stack.push(vertexIndex);
  335.     while (stack.length > 0) {
  336.       vertexIndex = stack.pop();
  337.       vertex = this.vertices[vertexIndex];
  338.       if (vertex.color == white) {
  339.         vertex.color = grey;
  340.         dfsVertices.push(vertex);
  341.         var edgeIndexes = this.outEdgesByIndex(vertexIndex);
  342.         for (var i = 0; i < edgeIndexes.length; i++) {  // push edge index
  343.           stack.push(edgeIndexes[i]);
  344.         }
  345.       }
  346.     }
  347.     this.unvisited();
  348.     return dfsVertices;
  349.   }
  350.  
  351.   /** bfsoTraverse() Traverse given breadth-first search order vertices to obtain their parents and
  352.    *  levels. There are a couple inefficiencies.
  353.    * --You could create the spanning tree by starting a graph with the same vertices and adding the
  354.    * same edge between vertices when they are found to be adjacent.
  355.    * @param {Vertex[]} bfsVertices vertices in breadth-first search order where first vertex is root
  356.    */
  357.   bfsoTraverse(bfsVertices) {
  358.     var cur = 1;   // index into bfsVertices
  359.  
  360.     if (bfsVertices.length < 2) { return; }
  361.     var level = bfsVertices[cur].level;
  362.     do {
  363.       var bAdjacent = false;
  364.       for (var i = 0; i < cur && !bAdjacent; i++) {
  365.         if (bfsVertices[i].color == white &&
  366.               this.hasEdgeByIndex(bfsVertices[i].index, bfsVertices[cur].index)) {
  367.           var levelPrev = level;
  368.           bfsVertices[cur].level = bfsVertices[i].level + 1;
  369.           bfsVertices[cur].parentName = bfsVertices[i].name;
  370.           bfsVertices[cur].parentIndex = bfsVertices[i].index;
  371.           level = bfsVertices[cur].level;
  372.           bAdjacent = true;
  373.           if (level != levelPrev) {  // level change
  374.             for (var j = 0; j < i; j++) {
  375.               if (bfsVertices[j].level < level - 1) {
  376.                 bfsVertices[j].color = grey;
  377.               }
  378.             }
  379.           }
  380.         }
  381.       }
  382.       cur++;
  383.     } while (cur < bfsVertices.length);
  384.     this.unvisited();
  385.   }
  386.  
  387.   /** dfsoTraverse() Traverse given depth-first search order vertices to obtain their parents and
  388.    *  levels. There are a couple inefficiencies.
  389.    * --You could create the spanning tree by starting a graph with the same vertices and adding the
  390.    * same edge between vertices when they are found to be adjacent.
  391.    * @param {Vertex[]} dfsVertices vertices in depth-first search order where first vertex is root
  392.    */
  393.   dfsoTraverse(dfsVertices) {
  394.     var prev = 0, cur = 1;   // indexes into dfsVertices
  395.    
  396.     if (dfsVertices.length < 2) { return; }
  397.     do {
  398.       if (this.hasEdgeByIndex(dfsVertices[prev].index, dfsVertices[cur].index)) {
  399.         dfsVertices[cur].level = dfsVertices[prev].level + 1;
  400.         dfsVertices[cur].parentName = dfsVertices[prev].name;
  401.         dfsVertices[cur].parentIndex = dfsVertices[prev].index;
  402.       } else {
  403.         var bAdjacent = false;
  404.         for (var i = 0; i < prev && !bAdjacent; i++) {
  405.           if (dfsVertices[i].color == white &&
  406.                 this.hasEdgeByIndex(dfsVertices[i].index, dfsVertices[cur].index)) {
  407.             dfsVertices[cur].level = dfsVertices[i].level + 1;
  408.             dfsVertices[cur].parentName = dfsVertices[i].name;
  409.             dfsVertices[cur].parentIndex = dfsVertices[i].index;
  410.             for (var j = i + 1; j <= prev; j++) {
  411.               dfsVertices[j].color = grey;
  412.             }
  413.             bAdjacent = true;
  414.           }
  415.         }
  416.       }
  417.       prev = cur++;
  418.     } while (cur < dfsVertices.length);
  419.     this.unvisited();
  420.   }
  421. }
  422.  
  423. /** formatVertices()
  424.  * --You could append the parent name.
  425.  * @param {Vertex[]} vertices vertices to format as tree
  426.  * @returns {string} vertex names formatted as tree
  427.  */
  428. function formatVertices(vertices) {
  429.   var retVertices = "";
  430.   for (var i = 0; i < vertices.length; i++) {
  431.     retVertices += '\n' + " ".repeat(vertices[i].level) + vertices[i].name;
  432.   }
  433.   return retVertices;
  434. }
  435.  
  436.  
  437. /** names of all vertices */
  438. var vertexNames = ["zero", "one", "two", "three", "four", "five",
  439.                   "six", "seven", "eight", "nine", "ten", "eleven"];
  440. // create vertices
  441. var graph = new AdjacencyLists(vertexNames);
  442. /** edges in the form [vertex][array of edge indexes].
  443.   * This is the example graph in Morin Figure 12.3.
  444.   */
  445. var edges = [ [1, 4],
  446.               [0, 2, 6, 5],
  447.               [1, 3, 6],
  448.               [2, 7],
  449.               [0, 5, 8],
  450.               [1, 2, 6, 9, 4],
  451.               [5, 2, 7, 10],
  452.               [6, 3, 11],
  453.               [4, 9],
  454.               [8, 5, 10],
  455.               [9, 6, 11],
  456.               [10, 7] ];
  457. // add edges
  458. for (var i = 0; i < edges.length; i++) {
  459.   for (var j = 0; j < edges[i].length; j++) {
  460.     graph.addEdgeByName(vertexNames[i], vertexNames[edges[i][j]]);
  461.   }
  462. }
  463. // breadth-first search
  464. var vertices = graph.bfs(0);
  465. graph.bfsoTraverse(vertices);
  466. console.log("breadth-first search" + formatVertices(vertices));
  467. // depth-first search
  468. vertices = graph.dfs(0);
  469. graph.dfsoTraverse(vertices);
  470. console.log("depth-first search" + formatVertices(vertices));
  471. // depth-first search #2
  472. vertices = graph.dfs2(0);
  473. graph.dfsoTraverse(vertices);
  474. console.log("depth-first search #2" + formatVertices(vertices));
  475. // breadth-first search starting at vertex one
  476. vertices = graph.bfs(graph.getIndexFromName("one"));
  477. graph.bfsoTraverse(vertices);
  478. console.log("breadth-first search starting at vertex one" + formatVertices(vertices));
  479. // remove a pair of edges
  480. console.log("vertex one out edges: " + graph.outEdgesByName("one"));
  481. console.log("vertex two in edges: " + graph.inEdgesByName("two"));
  482. graph.removeEdgeByName("one", "two");
  483. graph.removeEdgeByIndex(2, 1);
  484. console.log("--removed edges between vertices one and two--");
  485. console.log("vertex one out edges (by index): " + graph.outEdgesByIndex(1));
  486. console.log("vertex two in edges (by index): " + graph.inEdgesByIndex(2));
  487. // depth-first search
  488. vertices = graph.dfs(0);
  489. graph.dfsoTraverse(vertices);
  490. console.log("depth-first search" + formatVertices(vertices));
  491. // restore edges
  492. graph.addEdgeByIndex(1, 2);
  493. graph.addEdgeByName ("two", "one");
  494. console.log("--restored edges between vertices one and two--");
  495. // remove a vertex
  496. graph.removeVertex(graph.getIndexFromName("eleven"));
  497. console.log("--removed vertex eleven--");
  498. // depth-first search
  499. vertices = graph.dfs(0);
  500. graph.dfsoTraverse(vertices);
  501. console.log("depth-first search" + formatVertices(vertices));
  502.  
  503. /** @version 1.0f */
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement