Skip to content

Conversation

@DanielLacina
Copy link
Contributor

@DanielLacina DanielLacina commented Jan 23, 2026

Bellman Ford Algorithm -> Shortest path algorithm that works with negative weights. Returns shortest distance and path for each target node.
Has time complexity of O(V x E). Here's a link to the paper: https://web.stanford.edu/class/archive/cs/cs161/cs161.1168/lecture14.pdf

@DanielLacina
Copy link
Contributor Author

It's a WIP. No need to review rn

@DanielLacina
Copy link
Contributor Author

Algorithm is ready to be reviewed

Comment on lines +105 to +112
for node in g.nodes() {
predecessor.insert(node.node, node.node);
if node.node == source_node.node {
dist.insert(source_node.node, dist_val.clone());
} else {
dist.insert(node.node, max_val.clone());
}
}
Copy link
Contributor Author

@DanielLacina DanielLacina Jan 29, 2026

Choose a reason for hiding this comment

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

Initialize every node to be it's own predecessor. Only the source node has a path to source node (with distance 0). Non source nodes have not established a path so they have a distance of max_val from the source node.

Comment on lines +114 to +153
for _ in 1..n_nodes {
let mut changed = false;
for node in g.nodes() {
if node.node == source_node.node {
continue;
}
let mut min_dist = dist.get(&node.node).unwrap().clone();
let mut min_node = predecessor.get(&node.node).unwrap().clone();
let edges = match direction {
Direction::IN => node.out_edges(),
Direction::OUT => node.in_edges(),
Direction::BOTH => node.edges(),
};
for edge in edges {
let edge_val = match weight {
None => Prop::U8(1),
Some(weight) => match edge.properties().get(weight) {
Some(prop) => prop,
_ => continue,
},
};
let neighbor_vid = edge.nbr().node;
let neighbor_dist = dist.get(&neighbor_vid).unwrap();
if neighbor_dist == &max_val {
continue;
}
let new_dist = neighbor_dist.clone().add(edge_val).unwrap();
if new_dist < min_dist {
min_dist = new_dist;
min_node = neighbor_vid;
changed = true;
}
}
dist.insert(node.node, min_dist);
predecessor.insert(node.node, min_node);
}
if !changed {
break;
}
}
Copy link
Contributor Author

@DanielLacina DanielLacina Jan 29, 2026

Choose a reason for hiding this comment

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

This a dynamic programming solution.

The correctness version of this algorithm ensures that after each ith iteration, the distance and path of every node corresponds to the shortest path in i or fewer edges. In each ith iteration, a node chooses whether to stay on its current shortest path (i - 1) edges or connect to one of its neighbor vertex and go down its shortest path. The node chooses the path that minimizes the distance.

After n - 1 iterations, each nodes path corresponds to the shortest path in n - 1 edges or fewer which means its the final shortest path. If it can be made shorter after n - 1 iterations, that means there's a negative cycle which is caught later in the code.

The version I implemented has it so for each iteration i, as each node is being iterated over to find a shorter path, its neighbors paths could've been modified (made shorter) depending on whether the neighbors were processed before the corresponding node. This means for all iterations, all vertices will have paths with distance less than or equal to their corresponding paths in the correctness algorithm. However, after the n - 1 iterations, every vertex must have paths that are the actual shortest paths. If they're shorter than that means there's a negative cycle which is caught later in the code.

In each iteration, after each vertex's optimal path is found, the corresponding distance is updated and the corresponding predecessor is updated to the corresponding neighbor that the vertex needs to connect to. This will help form a chain of predecessors that ends at the source node.

We know that change of each vertex path relies on the initial state of all the vertices' paths. If no vertex path changes in one iteration, all vertices' paths will have the same initial state in future iterations. Thus, we can end early if there's no changes in one iteration.

Comment on lines +155 to +180
for node in g.nodes() {
let edges = match direction {
Direction::IN => node.out_edges(),
Direction::OUT => node.in_edges(),
Direction::BOTH => node.edges(),
};
let node_dist = dist.get(&node.node).unwrap();
for edge in edges {
let edge_val = match weight {
None => Prop::U8(1),
Some(weight) => match edge.properties().get(weight) {
Some(prop) => prop,
_ => continue,
},
};
let neighbor_vid = edge.nbr().node;
let neighbor_dist = dist.get(&neighbor_vid).unwrap();
if neighbor_dist == &max_val {
continue;
}
let new_dist = neighbor_dist.clone().add(edge_val).unwrap();
if new_dist < *node_dist {
return Err(GraphError::InvalidProperty { reason: "Negative cycle detected".to_string() });
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If after finding shortest path and a shorter distance can be found then that means there's a negative cycle.

Comment on lines +182 to +209
for target in targets.into_iter() {
let target_ref = target.as_node_ref();
let target_node = match g.node(target_ref) {
Some(tgt) => tgt,
None => {
let gid = match target_ref {
NodeRef::Internal(vid) => g.node_id(vid),
NodeRef::External(gid) => gid.to_owned(),
};
return Err(GraphError::NodeMissingError(gid));
}
};
let mut path = IndexSet::default();
path.insert(target_node.node);
let mut current_node_id = target_node.node;
while let Some(prev_node) = predecessor.get(&current_node_id) {
if *prev_node == current_node_id {
break;
}
path.insert(*prev_node);
current_node_id = *prev_node;
}
path.reverse();
shortest_paths.insert(
target_node.node,
(dist.get(&target_node.node).unwrap().as_f64().unwrap(), path),
);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For each target, return corresponding path from target -> source node and min distance.

@DanielLacina
Copy link
Contributor Author

@DanielLacina
Copy link
Contributor Author

That's the article where I got the algorithm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants