1+ /**
2+ * Builds an adjacency list from the edge array.
3+ * @param {Array } nodes - Array of node objects.
4+ * @param {Array } edges - Array of edge objects.
5+ * @returns {Map<number, number[]> } - An adjacency list.
6+ */
7+ function buildAdjList ( nodes , edges ) {
8+ const adj = new Map ( ) ;
9+ nodes . forEach ( ( n ) => adj . set ( n . id , [ ] ) ) ;
10+ edges . forEach ( ( edge ) => {
11+ // This assumes a directed graph
12+ if ( adj . has ( edge . from ) ) {
13+ adj . get ( edge . from ) . push ( edge . to ) ;
14+ }
15+ } ) ;
16+ return adj ;
17+ }
18+
19+ /**
20+ * Generates all steps for a Breadth-First Search traversal.
21+ * @param {Array } nodes - Array of node objects.
22+ * @param {Array } edges - Array of edge objects.
23+ * @param {number } startNodeId - The ID of the node to start from.
24+ * @returns {Array } - An array of step objects for the visualization.
25+ */
26+ export function bfsSteps ( nodes , edges , startNodeId ) {
27+ const adj = buildAdjList ( nodes , edges ) ;
28+ const steps = [ ] ;
29+ const queue = [ ] ;
30+
31+ // 'visited' tracks all nodes that have been *at least enqueued*.
32+ // This prevents adding the same node to the queue multiple times.
33+ const visited = new Set ( ) ;
34+
35+ // 'discoveryOrder' tracks nodes *after* they are processed (dequeued).
36+ // This will control the "green" (visited) state in the UI.
37+ const discoveryOrder = [ ] ;
38+
39+ // --- Initial Step ---
40+ queue . push ( startNodeId ) ;
41+ visited . add ( startNodeId ) ; // Mark as visited *when enqueuing*
42+
43+ steps . push ( {
44+ type : "enqueue" ,
45+ node : startNodeId ,
46+ log : `Starting BFS. Enqueueing Start Node ${ startNodeId } .` ,
47+ queueState : [ ...queue ] ,
48+ visitedSet : new Set ( ) , // Nothing is fully processed yet
49+ order : [ ] ,
50+ } ) ;
51+
52+ // --- Traversal Loop ---
53+ while ( queue . length > 0 ) {
54+ const currentNodeId = queue . shift ( ) ; // FIFO: Dequeue from the front
55+ discoveryOrder . push ( currentNodeId ) ;
56+
57+ // --- Dequeue Step ---
58+ steps . push ( {
59+ type : "dequeue" ,
60+ node : currentNodeId ,
61+ log : `Dequeuing Node ${ currentNodeId } to process.` ,
62+ queueState : [ ...queue ] ,
63+ visitedSet : new Set ( discoveryOrder ) , // Add to processed set
64+ order : [ ...discoveryOrder ] ,
65+ } ) ;
66+
67+ const neighbors = adj . get ( currentNodeId ) || [ ] ;
68+
69+ for ( const neighborId of neighbors ) {
70+ if ( ! visited . has ( neighborId ) ) {
71+ // --- Enqueue Neighbor Step ---
72+ visited . add ( neighborId ) ;
73+ queue . push ( neighborId ) ;
74+
75+ steps . push ( {
76+ type : "enqueue" ,
77+ node : neighborId ,
78+ log : `Found unvisited neighbor ${ neighborId } . Enqueueing.` ,
79+ queueState : [ ...queue ] ,
80+ visitedSet : new Set ( discoveryOrder ) , // Processed set is unchanged
81+ order : [ ...discoveryOrder ] ,
82+ } ) ;
83+ } else {
84+ // --- Skip Neighbor Step ---
85+ steps . push ( {
86+ type : "skip" ,
87+ node : neighborId ,
88+ log : `Neighbor ${ neighborId } already in queue or processed. Skipping.` ,
89+ queueState : [ ...queue ] ,
90+ visitedSet : new Set ( discoveryOrder ) ,
91+ order : [ ...discoveryOrder ] ,
92+ } ) ;
93+ }
94+ }
95+ }
96+
97+ // --- Final Step ---
98+ steps . push ( {
99+ type : "done" ,
100+ node : null ,
101+ log : "BFS complete. Queue is empty." ,
102+ queueState : [ ] ,
103+ visitedSet : new Set ( discoveryOrder ) ,
104+ order : [ ...discoveryOrder ] ,
105+ } ) ;
106+
107+ return steps ;
108+ }
0 commit comments