Skip to content

Commit aec284b

Browse files
committed
added Graph algos - BFS/DFS, topological sort, cycle dection for both directed and undirected graphs and bi-partite graph detection
1 parent 4c42966 commit aec284b

File tree

10 files changed

+458
-58
lines changed

10 files changed

+458
-58
lines changed

Algorithms/cycleDetectionDirected.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

Algorithms/graph/bfs.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// useful for shortest path in unweighted graph
2+
function bfs(start, graph) {
3+
const q = new Queue()
4+
q.push(start)
5+
const visited = new Set([start])
6+
7+
while (!q.isEmpty()) {
8+
const node = q.pop()
9+
for (const nei of graph[node]) {
10+
if (!visited.has(nei)) {
11+
visited.add(nei)
12+
q.push(nei)
13+
}
14+
}
15+
}
16+
17+
return visited
18+
}
19+
20+
21+
// example usage:
22+
// shortest path in a grid with obstacles elimination
23+
class Cell {
24+
constructor(row, col, remaining, distance) {
25+
this.row = row
26+
this.col = col
27+
this.remaining = remaining
28+
this.distance = distance
29+
}
30+
}
31+
32+
var shortestPath = function (grid, k) {
33+
let m = grid.length
34+
let n = grid[0].length
35+
36+
let q = new Queue()
37+
q.push(new Cell(0, 0, k, 0))
38+
39+
let visited = new Set()
40+
visited.add(`0,0,${k}`)
41+
42+
let directions = [
43+
[0, 1],
44+
[1, 0],
45+
[0, -1],
46+
[-1, 0],
47+
]
48+
49+
while (!q.isEmpty()) {
50+
let cell = q.pop()
51+
if (cell.row === m - 1 && cell.col === n - 1) return cell.distance
52+
53+
for (let [dr, dc] of directions) {
54+
let newR = cell.row + dr
55+
let newC = cell.col + dc
56+
57+
if (newR < 0 || newC < 0 || newR >= m || newC >= n) continue
58+
59+
let newRemaining = cell.remaining - grid[newR][newC]
60+
let key = `${newR},${newC},${newRemaining}`
61+
if (newRemaining >= 0 && !visited.has(key)) {
62+
visited.add(key)
63+
q.push(new Cell(newR, newC, newRemaining, cell.distance + 1))
64+
}
65+
}
66+
}
67+
68+
return -1
69+
}

Algorithms/graph/biPartite.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// BiPartite Graph Check using BFS
2+
3+
/*
4+
A bipartite graph is a graph whose vertices can be divided into two disjoint and independent
5+
sets U and V such that every edge connects a vertex in U to one in V. In other words,
6+
there are no edges between vertices within the same set.
7+
8+
Alternate coloring method can also be used to check bipartiteness.
9+
- A graph is bipartite if and only if it contains NO odd-length cycles
10+
11+
Algorithm Steps:
12+
1. Start coloring from an unvisited node, assign it one color (e.g., 0)
13+
2. Use BFS to traverse the graph:
14+
- Color all adjacent nodes with the alternate color (e.g., 1)
15+
- If an adjacent node is already colored with the same color, return false
16+
3. Repeat for all unvisited nodes to handle disconnected components
17+
4. If no conflicts found, return true
18+
19+
Time Complexity: O(V + E) where V is vertices and E is edges
20+
Space Complexity: O(V) for the color map and queue
21+
*/
22+
23+
24+
function isBipartite(graph) {
25+
const nodes = Object.keys(graph)
26+
let colors = {}
27+
28+
for(let node of nodes) {
29+
node = parseInt(node)
30+
colors[node] = -1 // -1 indicates uncolored
31+
}
32+
33+
for(let nei of nodes) {
34+
nei = parseInt(nei)
35+
if(colors[nei] === -1 && !bfsCheck(nei)) {
36+
return false
37+
}
38+
}
39+
40+
return true
41+
42+
function bfsCheck(start) {
43+
const queue = [start]
44+
colors[start] = 0 // Start coloring with color 0
45+
46+
while(queue.length > 0) {
47+
const current = queue.shift()
48+
49+
for(let nei of graph[current]) {
50+
if(colors[nei] === -1) {
51+
colors[nei] = 1 - colors[current]
52+
queue.push(nei)
53+
}else if(colors[nei] === colors[current]) {
54+
return false // Conflict in coloring
55+
}
56+
}
57+
}
58+
return true
59+
}
60+
}
61+
62+
const graph = { // Not Bipartite due to odd-length cycle
63+
0: [1, 2],
64+
1: [0, 3],
65+
2: [0, 4],
66+
3: [4, 1],
67+
4: [3, 2],
68+
}
69+
70+
const graph2 = { // Bipartite graph
71+
0: [1, 2],
72+
1: [0, 3],
73+
2: [0, 4],
74+
3: [5, 1],
75+
4: [5, 2],
76+
5: [3, 4]
77+
}
78+
79+
console.log('Graph - BFS:', isBipartite(graph)) // false
80+
console.log('Graph2 - BFS:', isBipartite(graph2)) // true
81+
82+
/*
83+
Questions to practice:
84+
- Possible Bipartition - LeetCode (https://leetcode.com/problems/possible-bipartition/)
85+
*/
86+
87+
88+
// BiPartite Graph Check using DFS
89+
function isBipartiteDFS(graph) {
90+
const nodes = Object.keys(graph)
91+
let colors = {}
92+
93+
for(let node of nodes) {
94+
node = parseInt(node)
95+
colors[node] = -1 // -1 indicates uncolored
96+
}
97+
98+
for(let nei of nodes) {
99+
nei = parseInt(nei)
100+
if(colors[nei] === -1 && !dfsCheck(nei, 0)) {
101+
return false
102+
}
103+
}
104+
105+
return true
106+
107+
function dfsCheck(node, color) {
108+
colors[node] = color
109+
110+
for(let nei of graph[node]) {
111+
if(colors[nei] === -1 && !dfsCheck(nei, 1 - color)) {
112+
return false
113+
} else if(colors[nei] === colors[node]) {
114+
return false
115+
}
116+
}
117+
118+
return true
119+
}
120+
}
121+
122+
console.log('Graph - DFS:', isBipartiteDFS(graph)) // false
123+
console.log('Graph2 - DFS:', isBipartiteDFS(graph2)) // true
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Detect cycle in directed graph using DFS
2+
function hasCycle(graph) {
3+
4+
const visited = new Set()
5+
const pathVisited = new Set() // Nodes currently in the recursion stack (current path)
6+
7+
function dfs(node) {
8+
if (pathVisited.has(node)) return true // cycle found
9+
if (visited.has(node)) return false // already processed, no cycle from this node
10+
11+
visited.add(node)
12+
pathVisited.add(node)
13+
14+
for (const nei of graph[node]) {
15+
if (dfs(nei)) return true // cyle found
16+
}
17+
18+
pathVisited.delete(node)
19+
return false // no cycle found from this node
20+
}
21+
22+
for (let node in graph) {
23+
node = parseInt(node) // Convert string (cause: for in loop) key to integer
24+
if (!visited.has(node) && dfs(node)) return true
25+
}
26+
27+
return false
28+
}
29+
30+
const graph = {
31+
0: [1],
32+
1: [2],
33+
2: [3],
34+
3: [0, 4],
35+
4: []
36+
}
37+
38+
console.log(hasCycle(graph)) // true
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Detect cycle in undirected graph using DFS
2+
function hasCycle(graph) {
3+
const visited = new Set()
4+
5+
// Check all nodes to handle disconnected components
6+
for (let node in graph) {
7+
node = parseInt(node) // Convert string (cause: for in loop) key to integer
8+
if (!visited.has(node)) {
9+
if (dfs(node, -1, visited, graph)) {
10+
return true
11+
}
12+
}
13+
}
14+
return false
15+
16+
function dfs(node, parent, visited, graph) {
17+
visited.add(node)
18+
19+
for (const neighbor of graph[node]) {
20+
if (!visited.has(neighbor)) {
21+
if (dfs(neighbor, node, visited, graph)) return true
22+
} else if (neighbor !== parent) {
23+
// Found a back edge - cycle detected
24+
return true
25+
}
26+
}
27+
return false
28+
}
29+
}
30+
31+
const graph = {
32+
0: [1, 2],
33+
1: [0, 3],
34+
2: [0, 3],
35+
3: [1, 2],
36+
}
37+
38+
console.log(hasCycle(graph)) // true

Algorithms/graph/dfs.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// useful for traversing all nodes in a graph depth-wise
2+
function dfs(node, graph, visited = new Set()) {
3+
if (visited.has(node)) return
4+
visited.add(node)
5+
6+
for (const nei of graph[node]) {
7+
dfs(nei, graph, visited)
8+
}
9+
}
10+
11+
// example usage:
12+
// number of islands in a grid
13+
function numIslands(grid) {
14+
let res = 0
15+
let m = grid.length
16+
let n = grid[0].length
17+
18+
let visited = Array.from({ length: m }, () => new Array(n).fill(false)) // m x n
19+
20+
let directions = [
21+
[0, 1],
22+
[1, 0],
23+
[0, -1],
24+
[-1, 0],
25+
]
26+
27+
function dfs(i, j) {
28+
if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j] || grid[i][j] === '0') return
29+
30+
visited[i][j] = true
31+
32+
for (let [dr, dc] of directions) {
33+
let newR = i + dr
34+
let newC = j + dc
35+
36+
dfs(newR, newC)
37+
}
38+
}
39+
40+
for (let i = 0; i < m; i++) {
41+
for (let j = 0; j < n; j++) {
42+
if (grid[i][j] === '1' && !visited[i][j]) {
43+
res++
44+
dfs(i, j)
45+
}
46+
}
47+
}
48+
return res
49+
}
50+
51+
// example usage:
52+
// Get subtree size for each node in a tree
53+
function getSubtreeSizes(node, graph, visited = new Set(), sizes = {}) {
54+
if (visited.has(node)) return 0
55+
visited.add(node)
56+
57+
let size = 1 // Count the current node
58+
59+
for (const child of graph[node]) {
60+
if (!visited.has(child)) {
61+
const childSize = getSubtreeSizes(child, graph, visited, sizes)
62+
size += childSize
63+
}
64+
}
65+
66+
sizes[node] = size
67+
return size
68+
}
69+
70+
71+
// bonus (bfs + dfs): Shortest Bridge Between Islands

0 commit comments

Comments
 (0)