1
1
/**
2
- * Author: Mathang Peddi
3
- * A* Search Algorithm implementation in JavaScript
2
+ * @author Mathang Peddi
4
3
* A* Algorithm calculates the minimum cost path between two nodes.
5
4
* It is used to find the shortest path using heuristics.
6
5
* It uses graph data structure.
7
6
*/
8
7
9
- function createGraph ( V , E ) {
10
- // V - Number of vertices in graph
11
- // E - Number of edges in graph (u,v,w)
12
- const adjList = [ ] // Adjacency list
13
- for ( let i = 0 ; i < V ; i ++ ) {
14
- adjList . push ( [ ] )
8
+ // Euclidean distance heuristic for 2D points
9
+ function euclideanHeuristic ( pointA , pointB ) {
10
+ const dx = pointA [ 0 ] - pointB [ 0 ] ;
11
+ const dy = pointA [ 1 ] - pointB [ 1 ] ;
12
+ return Math . sqrt ( dx * dx + dy * dy ) ;
15
13
}
16
- for ( let i = 0 ; i < E . length ; i ++ ) {
17
- adjList [ E [ i ] [ 0 ] ] . push ( [ E [ i ] [ 1 ] , E [ i ] [ 2 ] ] )
18
- adjList [ E [ i ] [ 1 ] ] . push ( [ E [ i ] [ 0 ] , E [ i ] [ 2 ] ] )
19
- }
20
- return adjList
21
- }
22
-
23
- // Heuristic function to estimate the cost to reach the goal
24
- // You can modify this based on your specific problem, for now, we're using Manhattan distance
25
- function heuristic ( a , b ) {
26
- return Math . abs ( a - b )
27
- }
28
-
29
- function aStar ( graph , V , src , target ) {
30
- const openSet = new Set ( [ src ] ) // Nodes to explore
31
- const cameFrom = Array ( V ) . fill ( - 1 ) // Keep track of path
32
- const gScore = Array ( V ) . fill ( Infinity ) // Actual cost from start to a node
33
- gScore [ src ] = 0
34
-
35
- const fScore = Array ( V ) . fill ( Infinity ) // Estimated cost from start to goal (g + h)
36
- fScore [ src ] = heuristic ( src , target )
37
-
38
- while ( openSet . size > 0 ) {
39
- // Get the node in openSet with the lowest fScore
40
- let current = - 1
41
- openSet . forEach ( ( node ) => {
42
- if ( current === - 1 || fScore [ node ] < fScore [ current ] ) {
43
- current = node
14
+
15
+ // Priority Queue (Min-Heap) implementation
16
+ class PriorityQueue {
17
+ constructor ( ) {
18
+ this . elements = [ ] ;
19
+ }
20
+
21
+ enqueue ( node , priority ) {
22
+ this . elements . push ( { node, priority } ) ;
23
+ this . bubbleUp ( ) ;
24
+ }
25
+
26
+ bubbleUp ( ) {
27
+ let index = this . elements . length - 1 ;
28
+ while ( index > 0 ) {
29
+ let parentIndex = Math . floor ( ( index - 1 ) / 2 ) ;
30
+ if ( this . elements [ index ] . priority >= this . elements [ parentIndex ] . priority ) break ;
31
+ [ this . elements [ index ] , this . elements [ parentIndex ] ] = [ this . elements [ parentIndex ] , this . elements [ index ] ] ;
32
+ index = parentIndex ;
44
33
}
45
- } )
46
-
47
- // If the current node is the target, reconstruct the path and return
48
- if ( current === target ) {
49
- const path = [ ]
50
- while ( cameFrom [ current ] !== - 1 ) {
51
- path . push ( current )
52
- current = cameFrom [ current ]
34
+ }
35
+
36
+ dequeue ( ) {
37
+ if ( this . elements . length === 1 ) {
38
+ return this . elements . pop ( ) . node ;
53
39
}
54
- path . push ( src )
55
- return path . reverse ( )
40
+
41
+ const node = this . elements [ 0 ] . node ;
42
+ this . elements [ 0 ] = this . elements . pop ( ) ;
43
+ this . sinkDown ( 0 ) ;
44
+ return node ;
56
45
}
57
-
58
- openSet . delete ( current )
59
-
60
- // Explore neighbors
61
- for ( let i = 0 ; i < graph [ current ] . length ; i ++ ) {
62
- const neighbor = graph [ current ] [ i ] [ 0 ]
63
- const tentative_gScore = gScore [ current ] + graph [ current ] [ i ] [ 1 ]
64
-
65
- if ( tentative_gScore < gScore [ neighbor ] ) {
66
- cameFrom [ neighbor ] = current
67
- gScore [ neighbor ] = tentative_gScore
68
- fScore [ neighbor ] = gScore [ neighbor ] + heuristic ( neighbor , target )
69
-
70
- if ( ! openSet . has ( neighbor ) ) {
71
- openSet . add ( neighbor )
46
+
47
+ sinkDown ( index ) {
48
+ const length = this . elements . length ;
49
+ const element = this . elements [ index ] ;
50
+ while ( true ) {
51
+ let leftChildIndex = 2 * index + 1 ;
52
+ let rightChildIndex = 2 * index + 2 ;
53
+ let swapIndex = null ;
54
+
55
+ if ( leftChildIndex < length && this . elements [ leftChildIndex ] . priority < element . priority ) {
56
+ swapIndex = leftChildIndex ;
57
+ }
58
+
59
+ if ( rightChildIndex < length && this . elements [ rightChildIndex ] . priority < ( swapIndex === null ? element . priority : this . elements [ leftChildIndex ] . priority ) ) {
60
+ swapIndex = rightChildIndex ;
72
61
}
62
+
63
+ if ( swapIndex === null ) break ;
64
+
65
+ [ this . elements [ index ] , this . elements [ swapIndex ] ] = [ this . elements [ swapIndex ] , this . elements [ index ] ] ;
66
+ index = swapIndex ;
73
67
}
74
68
}
69
+
70
+ isEmpty ( ) {
71
+ return this . elements . length === 0 ;
72
+ }
75
73
}
76
-
77
- return [ ] // Return empty path if there's no path to the target
78
- }
79
-
80
- module . exports = { createGraph, aStar }
81
-
82
- // const V = 9
83
- // const E = [
84
- // [0, 1, 4],
85
- // [0, 7, 8],
86
- // [1, 7, 11],
87
- // [1, 2, 8],
88
- // [7, 8, 7],
89
- // [6, 7, 1],
90
- // [2, 8, 2],
91
- // [6, 8, 6],
92
- // [5, 6, 2],
93
- // [2, 5, 4],
94
- // [2, 3, 7],
95
- // [3, 5, 14],
96
- // [3, 4, 9],
97
- // [4, 5, 10]
98
- // ]
99
-
100
- // const graph = createGraph(V, E)
101
- // const path = aStar(graph, V, 0, 4) // Find path from node 0 to node 4
102
- // console.log(path)
103
-
104
- /**
105
- * The function returns the optimal path from the source to the target node.
106
- * The heuristic used is Manhattan distance but it can be modified.
107
- */
74
+
75
+ function aStar ( graph , src , target , points ) {
76
+ const openSet = new PriorityQueue ( ) ; // Priority queue to explore nodes
77
+ openSet . enqueue ( src , 0 ) ;
78
+
79
+ const cameFrom = Array ( graph . length ) . fill ( null ) ; // Keep track of path
80
+ const gScore = Array ( graph . length ) . fill ( Infinity ) ; // Actual cost from start to a node
81
+ gScore [ src ] = 0 ;
82
+
83
+ const fScore = Array ( graph . length ) . fill ( Infinity ) ; // Estimated cost from start to goal (g + h)
84
+ fScore [ src ] = euclideanHeuristic ( points [ src ] , points [ target ] ) ;
85
+
86
+ while ( ! openSet . isEmpty ( ) ) {
87
+ // Get the node in openSet with the lowest fScore
88
+ const current = openSet . dequeue ( ) ;
89
+
90
+ // If the current node is the target, reconstruct the path and return
91
+ if ( current === target ) {
92
+ const path = [ ] ;
93
+ while ( cameFrom [ current ] !== - 1 ) {
94
+ path . push ( current ) ;
95
+ current = cameFrom [ current ] ;
96
+ }
97
+ path . push ( src ) ;
98
+ return path . reverse ( ) ;
99
+ }
100
+
101
+ // Explore neighbors using destructuring for cleaner code
102
+ for ( const [ neighbor , weight ] of graph [ current ] ) {
103
+ const tentative_gScore = gScore [ current ] + weight ;
104
+
105
+ if ( tentative_gScore < gScore [ neighbor ] ) {
106
+ cameFrom [ neighbor ] = current ;
107
+ gScore [ neighbor ] = tentative_gScore ;
108
+ const priority = gScore [ neighbor ] + euclideanHeuristic ( points [ neighbor ] , points [ target ] ) ;
109
+ fScore [ neighbor ] = priority ;
110
+
111
+ openSet . enqueue ( neighbor , priority ) ;
112
+ }
113
+ }
114
+ }
115
+
116
+ return null ; // Return null if there's no path to the target
117
+ }
118
+
119
+ // Define the graph as an adjacency list
120
+ const graph = [
121
+ [ [ 1 , 4 ] , [ 7 , 8 ] ] , // Node 0 connects to node 1 (weight 4), node 7 (weight 8)
122
+ [ [ 0 , 4 ] , [ 2 , 8 ] , [ 7 , 11 ] ] , // Node 1 connects to node 0, node 2, node 7
123
+ [ [ 1 , 8 ] , [ 3 , 7 ] , [ 5 , 4 ] , [ 8 , 2 ] ] , // Node 2 connects to several nodes
124
+ [ [ 2 , 7 ] , [ 4 , 9 ] , [ 5 , 14 ] ] , // Node 3 connects to nodes 2, 4, 5
125
+ [ [ 3 , 9 ] , [ 5 , 10 ] ] , // Node 4 connects to nodes 3 and 5
126
+ [ [ 2 , 4 ] , [ 3 , 14 ] , [ 4 , 10 ] , [ 6 , 2 ] ] , // Node 5 connects to several nodes
127
+ [ [ 5 , 2 ] , [ 7 , 1 ] , [ 8 , 6 ] ] , // Node 6 connects to nodes 5, 7, 8
128
+ [ [ 0 , 8 ] , [ 1 , 11 ] , [ 6 , 1 ] , [ 8 , 7 ] ] , // Node 7 connects to several nodes
129
+ [ [ 2 , 2 ] , [ 6 , 6 ] , [ 7 , 7 ] ] // Node 8 connects to nodes 2, 6, 7
130
+ ] ;
131
+
132
+ // Define 2D coordinates for each node (these can be changed based on actual node positions)
133
+ const points = [
134
+ [ 0 , 0 ] , // Point for node 0
135
+ [ 1 , 2 ] , // Point for node 1
136
+ [ 2 , 1 ] , // Point for node 2
137
+ [ 3 , 5 ] , // Point for node 3
138
+ [ 4 , 3 ] , // Point for node 4
139
+ [ 5 , 6 ] , // Point for node 5
140
+ [ 6 , 8 ] , // Point for node 6
141
+ [ 7 , 10 ] , // Point for node 7
142
+ [ 8 , 12 ] // Point for node 8
143
+ ] ;
144
+
145
+ // Call the aStar function with the graph, source node (0), and target node (4)
146
+ const path = aStar ( graph , 0 , 4 , points ) ;
147
+
148
+ console . log ( 'Shortest path from node 0 to node 4:' , path ) ;
149
+
150
+ /**
151
+ * The function returns the optimal path from the source to the target node.
152
+ * The heuristic used is Euclidean distance between nodes' 2D coordinates.
153
+ */
154
+
0 commit comments