Skip to content

feat: added Astar Search Algorithm in Graphs #1739

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fixed lint issue
  • Loading branch information
mathangpeddi committed Oct 20, 2024
commit a9e17ed1caead330cfb979666492d1b4f5bfa63a
181 changes: 90 additions & 91 deletions Graphs/Astar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,102 +7,101 @@
*/

function createGraph(V, E) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is unnecessary. Just take the graph in adjacency list representation. There is also no need to restrict this to undirected graphs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this function, created an adjacency list and directly passed it to the function.

// V - Number of vertices in graph
// E - Number of edges in graph (u,v,w)
const adjList = [] // Adjacency list
for (let i = 0; i < V; i++) {
adjList.push([])
}
for (let i = 0; i < E.length; i++) {
adjList[E[i][0]].push([E[i][1], E[i][2]])
adjList[E[i][1]].push([E[i][0], E[i][2]])
}
return adjList
// V - Number of vertices in graph
// E - Number of edges in graph (u,v,w)
const adjList = [] // Adjacency list
for (let i = 0; i < V; i++) {
adjList.push([])
}

// Heuristic function to estimate the cost to reach the goal
// You can modify this based on your specific problem, for now, we're using Manhattan distance
function heuristic(a, b) {
return Math.abs(a - b)
for (let i = 0; i < E.length; i++) {
adjList[E[i][0]].push([E[i][1], E[i][2]])
adjList[E[i][1]].push([E[i][0], E[i][2]])
}

function aStar(graph, V, src, target) {
const openSet = new Set([src]) // Nodes to explore
const cameFrom = Array(V).fill(-1) // Keep track of path
const gScore = Array(V).fill(Infinity) // Actual cost from start to a node
gScore[src] = 0

const fScore = Array(V).fill(Infinity) // Estimated cost from start to goal (g + h)
fScore[src] = heuristic(src, target)

while (openSet.size > 0) {
// Get the node in openSet with the lowest fScore
let current = -1
openSet.forEach((node) => {
if (current === -1 || fScore[node] < fScore[current]) {
current = node
}
})

// If the current node is the target, reconstruct the path and return
if (current === target) {
const path = []
while (cameFrom[current] !== -1) {
path.push(current)
current = cameFrom[current]
}
path.push(src)
return path.reverse()
return adjList
}

// Heuristic function to estimate the cost to reach the goal
// You can modify this based on your specific problem, for now, we're using Manhattan distance
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a useful manhattan distance. This is just the absolute distance between vertex IDs. Instead you want some kind of associated "points" (say, in 2d or 3d space) for which you compute distances.

In any case, the heuristic function should probably be a (mandatory) parameter.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made the changes, I am using Euclidian Distance here, do I need to make any other change here?

function heuristic(a, b) {
return Math.abs(a - b)
}

function aStar(graph, V, src, target) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, just take the graph in adjacency list representation. V is then redundant (and also conflicts with our naming scheme).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it.

const openSet = new Set([src]) // Nodes to explore
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a priority queue by fScore otherwise time complexity is messed up.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have changed the logic, have used the priority queue implementation - have coded it using the normal approach(without using inbuilt PQ package), is this fine or do I need to make any other changes here?

const cameFrom = Array(V).fill(-1) // Keep track of path
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use null instead of -1, it's the idiomatic value to use in JS for absence of a value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

const gScore = Array(V).fill(Infinity) // Actual cost from start to a node
gScore[src] = 0

const fScore = Array(V).fill(Infinity) // Estimated cost from start to goal (g + h)
fScore[src] = heuristic(src, target)

while (openSet.size > 0) {
// Get the node in openSet with the lowest fScore
let current = -1
openSet.forEach((node) => {
if (current === -1 || fScore[node] < fScore[current]) {
current = node
}

openSet.delete(current)

// Explore neighbors
for (let i = 0; i < graph[current].length; i++) {
const neighbor = graph[current][i][0]
const tentative_gScore = gScore[current] + graph[current][i][1]

if (tentative_gScore < gScore[neighbor]) {
cameFrom[neighbor] = current
gScore[neighbor] = tentative_gScore
fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, target)

if (!openSet.has(neighbor)) {
openSet.add(neighbor)
}
})

// If the current node is the target, reconstruct the path and return
if (current === target) {
const path = []
while (cameFrom[current] !== -1) {
path.push(current)
current = cameFrom[current]
}
path.push(src)
return path.reverse()
}

openSet.delete(current)

// Explore neighbors
for (let i = 0; i < graph[current].length; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just do for (const [neighbor, weight] of graph[current])

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

const neighbor = graph[current][i][0]
const tentative_gScore = gScore[current] + graph[current][i][1]

if (tentative_gScore < gScore[neighbor]) {
cameFrom[neighbor] = current
gScore[neighbor] = tentative_gScore
fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, target)

if (!openSet.has(neighbor)) {
openSet.add(neighbor)
}
}
}

return [] // Return empty path if there's no path to the target
}

module.exports = { createGraph, aStar }

// const V = 9
// const E = [
// [0, 1, 4],
// [0, 7, 8],
// [1, 7, 11],
// [1, 2, 8],
// [7, 8, 7],
// [6, 7, 1],
// [2, 8, 2],
// [6, 8, 6],
// [5, 6, 2],
// [2, 5, 4],
// [2, 3, 7],
// [3, 5, 14],
// [3, 4, 9],
// [4, 5, 10]
// ]

// const graph = createGraph(V, E)
// const path = aStar(graph, V, 0, 4) // Find path from node 0 to node 4
// console.log(path)

/**
* The function returns the optimal path from the source to the target node.
* The heuristic used is Manhattan distance but it can be modified.
*/

return [] // Return empty path if there's no path to the target
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be null instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

}

module.exports = { createGraph, aStar }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
module.exports = { createGraph, aStar }
export { aStar }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.


// const V = 9
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a test instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this part, do you want me to add a Astar.test.js file as a part of Unit Testing?

// const E = [
// [0, 1, 4],
// [0, 7, 8],
// [1, 7, 11],
// [1, 2, 8],
// [7, 8, 7],
// [6, 7, 1],
// [2, 8, 2],
// [6, 8, 6],
// [5, 6, 2],
// [2, 5, 4],
// [2, 3, 7],
// [3, 5, 14],
// [3, 4, 9],
// [4, 5, 10]
// ]

// const graph = createGraph(V, E)
// const path = aStar(graph, V, 0, 4) // Find path from node 0 to node 4
// console.log(path)

/**
* The function returns the optimal path from the source to the target node.
* The heuristic used is Manhattan distance but it can be modified.
*/