|
| 1 | +import {bfs, reconstructPath} from './bfs'; |
| 2 | + |
| 3 | +describe('BFS Algorithm', () => { |
| 4 | + test('should find the shortest paths in a simple graph', () => { |
| 5 | + const graph = [ |
| 6 | + [1, 2], // Edges from vertex 0 |
| 7 | + [0, 3, 4], // Edges from vertex 1 |
| 8 | + [0, 5], // Edges from vertex 2 |
| 9 | + [1], // Edges from vertex 3 |
| 10 | + [1], // Edges from vertex 4 |
| 11 | + [2], // Edges from vertex 5 |
| 12 | + ]; |
| 13 | + const {distances, predecessors} = bfs(graph, 0); |
| 14 | + expect(distances).toEqual([0, 1, 1, 2, 2, 2]); |
| 15 | + expect(predecessors).toEqual([-1, 0, 0, 1, 1, 2]); |
| 16 | + }); |
| 17 | + test('should handle disconnected graph', () => { |
| 18 | + const graph = [ |
| 19 | + [1], // Edges from vertex 0 |
| 20 | + [0], // Edges from vertex 1 |
| 21 | + [3], // Edges from vertex 2 |
| 22 | + [2], // Edges from vertex 3 |
| 23 | + [], // Edges from vertex 4 (isolated) |
| 24 | + ]; |
| 25 | + const {distances, predecessors} = bfs(graph, 0); |
| 26 | + expect(distances).toEqual([0, 1, Infinity, Infinity, Infinity]); |
| 27 | + expect(predecessors).toEqual([-1, 0, -1, -1, -1]); |
| 28 | + expect(distances[2]).toBe(Infinity); |
| 29 | + expect(distances[3]).toBe(Infinity); |
| 30 | + expect(distances[4]).toBe(Infinity); |
| 31 | + expect(predecessors[2]).toBe(-1); |
| 32 | + expect(predecessors[3]).toBe(-1); |
| 33 | + expect(predecessors[4]).toBe(-1); |
| 34 | + }); |
| 35 | + test('should throw error for invalid start vertex', () => { |
| 36 | + const graph = [[1, 2], [0], [0]]; |
| 37 | + expect(() => bfs(graph, -1)).toThrow('Start vertex is out of range'); |
| 38 | + expect(() => bfs(graph, 3)).toThrow('Start vertex is out of range'); |
| 39 | + }); |
| 40 | + test('should handle a graph with a single vertex', () => { |
| 41 | + const graph = [[]]; |
| 42 | + const {distances, predecessors} = bfs(graph, 0); |
| 43 | + expect(distances).toEqual([0]); |
| 44 | + expect(predecessors).toEqual([-1]); |
| 45 | + }); |
| 46 | + test('should handle BFS on a tree structure', () => { |
| 47 | + const graph = [ |
| 48 | + [1, 2], // Root has two children |
| 49 | + [3, 4], // Left child has two children |
| 50 | + [5], // Right child has one child |
| 51 | + [], |
| 52 | + [], |
| 53 | + [], // Leaf nodes |
| 54 | + ]; |
| 55 | + const {distances, predecessors} = bfs(graph, 0); |
| 56 | + expect(distances).toEqual([0, 1, 1, 2, 2, 2]); |
| 57 | + expect(predecessors).toEqual([-1, 0, 0, 1, 1, 2]); |
| 58 | + }); |
| 59 | + test('should handle cyclic graphs correctly', () => { |
| 60 | + // A graph with cycles: 0-1-2-0 and 3-4-5-3 |
| 61 | + const graph = [ |
| 62 | + [1, 2], // Edges from vertex 0 |
| 63 | + [0, 2], // Edges from vertex 1 |
| 64 | + [0, 1], // Edges from vertex 2 |
| 65 | + [4, 5], // Edges from vertex 3 |
| 66 | + [3, 5], // Edges from vertex 4 |
| 67 | + [3, 4], // Edges from vertex 5 |
| 68 | + ]; |
| 69 | + const {distances} = bfs(graph, 0); |
| 70 | + expect(distances[0]).toBe(0); |
| 71 | + expect(distances[1]).toBe(1); |
| 72 | + expect(distances[2]).toBe(1); |
| 73 | + expect(distances[3]).toBe(Infinity); |
| 74 | + expect(distances[4]).toBe(Infinity); |
| 75 | + expect(distances[5]).toBe(Infinity); |
| 76 | + |
| 77 | + const result2 = bfs(graph, 3); |
| 78 | + expect(result2.distances[3]).toBe(0); |
| 79 | + expect(result2.distances[4]).toBe(1); |
| 80 | + expect(result2.distances[5]).toBe(1); |
| 81 | + expect(result2.distances[0]).toBe(Infinity); |
| 82 | + expect(result2.distances[1]).toBe(Infinity); |
| 83 | + expect(result2.distances[2]).toBe(Infinity); |
| 84 | + }); |
| 85 | + |
| 86 | + test('should handle large graphs efficiently', () => { |
| 87 | + // Create a larger graph (path graph with 1000 vertices) |
| 88 | + const largeGraph: number[][] = Array(1000) |
| 89 | + .fill(0) |
| 90 | + .map((_, i) => { |
| 91 | + if (i === 0) return [1]; |
| 92 | + if (i === 999) return [998]; |
| 93 | + return [i - 1, i + 1]; |
| 94 | + }); |
| 95 | + |
| 96 | + const startTime = performance.now(); |
| 97 | + const {distances} = bfs(largeGraph, 0); |
| 98 | + const endTime = performance.now(); |
| 99 | + |
| 100 | + expect(distances[0]).toBe(0); |
| 101 | + expect(distances[1]).toBe(1); |
| 102 | + expect(distances[10]).toBe(10); |
| 103 | + expect(distances[100]).toBe(100); |
| 104 | + expect(distances[999]).toBe(999); |
| 105 | + expect(endTime - startTime).toBeLessThan(1000); // Should complete in less than 1 second |
| 106 | + }); |
| 107 | +}); |
| 108 | + |
| 109 | +describe('reconstructPath', () => { |
| 110 | + test('should reconstruct the correct path', () => { |
| 111 | + const graph = [[1, 2], [3], [4], [], []]; |
| 112 | + const {predecessors} = bfs(graph, 0); |
| 113 | + const path1 = reconstructPath(0, 3, predecessors); |
| 114 | + expect(path1).toEqual([0, 1, 3]); |
| 115 | + |
| 116 | + const path2 = reconstructPath(0, 4, predecessors); |
| 117 | + expect(path2).toEqual([0, 2, 4]); |
| 118 | + }); |
| 119 | + |
| 120 | + test('should handle path from vertex to itself', () => { |
| 121 | + const graph = [[1], [2], []]; |
| 122 | + const {predecessors} = bfs(graph, 0); |
| 123 | + |
| 124 | + const path = reconstructPath(0, 0, predecessors); |
| 125 | + expect(path).toEqual([0]); |
| 126 | + }); |
| 127 | + |
| 128 | + test('should return null for unreachable vertices', () => { |
| 129 | + const graph = [[1], [0], [3], [2]]; |
| 130 | + const {predecessors, distances} = bfs(graph, 0); |
| 131 | + |
| 132 | + expect(distances[2]).toBe(Infinity); |
| 133 | + expect(distances[3]).toBe(Infinity); |
| 134 | + |
| 135 | + const path = reconstructPath(0, 2, predecessors); |
| 136 | + expect(path).toBeNull(); |
| 137 | + }); |
| 138 | + |
| 139 | + test('should handle invalid target vertex', () => { |
| 140 | + const graph = [[1], [0]]; |
| 141 | + const {predecessors} = bfs(graph, 0); |
| 142 | + |
| 143 | + const path = reconstructPath(0, 2, predecessors); |
| 144 | + expect(path).toBeNull(); |
| 145 | + }); |
| 146 | +}); |
0 commit comments