1. 1 : import { Edge } from '../core/Edge.js';
  2. 2 :
  3. 3 : /**
  4. 4 : * Implementation of Depth-first Search.
  5. 5 : *
  6. 6 : * @author {@link https://github.com/Mugen87|Mugen87}
  7. 7 : */
  8. 8 : class DFS {
  9. 9 :
  10. 10 : /**
  11. 11 : * Constructs a DFS algorithm object.
  12. 12 : *
  13. 13 : * @param {Graph} graph - The graph.
  14. 14 : * @param {Number} source - The node index of the source node.
  15. 15 : * @param {Number} target - The node index of the target node.
  16. 16 : */
  17. 17 : constructor( graph = null, source = - 1, target = - 1 ) {
  18. 18 :
  19. 19 : /**
  20. 20 : * The graph.
  21. 21 : * @type {?Graph}
  22. 22 : * @default null
  23. 23 : */
  24. 24 : this.graph = graph;
  25. 25 :
  26. 26 : /**
  27. 27 : * The node index of the source node.
  28. 28 : * @type {Number}
  29. 29 : * @default - 1
  30. 30 : */
  31. 31 : this.source = source;
  32. 32 :
  33. 33 : /**
  34. 34 : * The node index of the target node.
  35. 35 : * @type {Number}
  36. 36 : * @default - 1
  37. 37 : */
  38. 38 : this.target = target;
  39. 39 :
  40. 40 : /**
  41. 41 : * Whether the search was successful or not.
  42. 42 : * @type {Boolean}
  43. 43 : * @default false
  44. 44 : */
  45. 45 : this.found = false;
  46. 46 :
  47. 47 : this._route = new Map(); // this holds the route taken to the target
  48. 48 : this._visited = new Set(); // holds the visited nodes
  49. 49 :
  50. 50 : this._spanningTree = new Set(); // for debugging purposes
  51. 51 :
  52. 52 : }
  53. 53 :
  54. 54 : /**
  55. 55 : * Executes the graph search. If the search was successful, {@link DFS#found}
  56. 56 : * is set to true.
  57. 57 : *
  58. 58 : * @return {DFS} A reference to this DFS object.
  59. 59 : */
  60. 60 : search() {
  61. 61 :
  62. 62 : // create a stack(LIFO) of edges, done via an array
  63. 63 :
  64. 64 : const stack = new Array();
  65. 65 : const outgoingEdges = new Array();
  66. 66 :
  67. 67 : // create a dummy edge and put on the stack to begin the search
  68. 68 :
  69. 69 : const startEdge = new Edge( this.source, this.source );
  70. 70 :
  71. 71 : stack.push( startEdge );
  72. 72 :
  73. 73 : // while there are edges in the stack keep searching
  74. 74 :
  75. 75 : while ( stack.length > 0 ) {
  76. 76 :
  77. 77 : // grab the next edge and remove it from the stack
  78. 78 :
  79. 79 : const nextEdge = stack.pop();
  80. 80 :
  81. 81 : // make a note of the parent of the node this edge points to
  82. 82 :
  83. 83 : this._route.set( nextEdge.to, nextEdge.from );
  84. 84 :
  85. 85 : // and mark it visited
  86. 86 :
  87. 87 : this._visited.add( nextEdge.to );
  88. 88 :
  89. 89 : // expand spanning tree
  90. 90 :
  91. 91 : if ( nextEdge !== startEdge ) {
  92. 92 :
  93. 93 : this._spanningTree.add( nextEdge );
  94. 94 :
  95. 95 : }
  96. 96 :
  97. 97 : // if the target has been found the method can return success
  98. 98 :
  99. 99 : if ( nextEdge.to === this.target ) {
  100. 100 :
  101. 101 : this.found = true;
  102. 102 :
  103. 103 : return this;
  104. 104 :
  105. 105 : }
  106. 106 :
  107. 107 : // determine outgoing edges
  108. 108 :
  109. 109 : this.graph.getEdgesOfNode( nextEdge.to, outgoingEdges );
  110. 110 :
  111. 111 : // push the edges leading from the node this edge points to onto the
  112. 112 : // stack (provided the edge does not point to a previously visited node)
  113. 113 :
  114. 114 : for ( let i = 0, l = outgoingEdges.length; i < l; i ++ ) {
  115. 115 :
  116. 116 : const edge = outgoingEdges[ i ];
  117. 117 :
  118. 118 : if ( this._visited.has( edge.to ) === false ) {
  119. 119 :
  120. 120 : stack.push( edge );
  121. 121 :
  122. 122 : }
  123. 123 :
  124. 124 : }
  125. 125 :
  126. 126 : }
  127. 127 :
  128. 128 : this.found = false;
  129. 129 :
  130. 130 : return this;
  131. 131 :
  132. 132 : }
  133. 133 :
  134. 134 : /**
  135. 135 : * Returns the shortest path from the source to the target node as an array of node indices.
  136. 136 : *
  137. 137 : * @return {Array<Number>} The shortest path.
  138. 138 : */
  139. 139 : getPath() {
  140. 140 :
  141. 141 : // array of node indices that comprise the shortest path from the source to the target
  142. 142 :
  143. 143 : const path = new Array();
  144. 144 :
  145. 145 : // just return an empty path if no path to target found or if no target has been specified
  146. 146 :
  147. 147 : if ( this.found === false || this.target === - 1 ) return path;
  148. 148 :
  149. 149 : // start with the target of the path
  150. 150 :
  151. 151 : let currentNode = this.target;
  152. 152 :
  153. 153 : path.push( currentNode );
  154. 154 :
  155. 155 : // while the current node is not the source node keep processing
  156. 156 :
  157. 157 : while ( currentNode !== this.source ) {
  158. 158 :
  159. 159 : // determine the parent of the current node
  160. 160 :
  161. 161 : currentNode = this._route.get( currentNode );
  162. 162 :
  163. 163 : // push the new current node at the beginning of the array
  164. 164 :
  165. 165 : path.unshift( currentNode );
  166. 166 :
  167. 167 : }
  168. 168 :
  169. 169 : return path;
  170. 170 :
  171. 171 : }
  172. 172 :
  173. 173 : /**
  174. 174 : * Returns the search tree of the algorithm as an array of edges.
  175. 175 : *
  176. 176 : * @return {Array<Edge>} The search tree.
  177. 177 : */
  178. 178 : getSearchTree() {
  179. 179 :
  180. 180 : return [ ...this._spanningTree ];
  181. 181 :
  182. 182 : }
  183. 183 :
  184. 184 : /**
  185. 185 : * Clears the internal state of the object. A new search is now possible.
  186. 186 : *
  187. 187 : * @return {DFS} A reference to this DFS object.
  188. 188 : */
  189. 189 : clear() {
  190. 190 :
  191. 191 : this.found = false;
  192. 192 :
  193. 193 : this._route.clear();
  194. 194 : this._visited.clear();
  195. 195 : this._spanningTree.clear();
  196. 196 :
  197. 197 : return this;
  198. 198 :
  199. 199 : }
  200. 200 :
  201. 201 : }
  202. 202 :
  203. 203 : export { DFS };