-
Notifications
You must be signed in to change notification settings - Fork 65
Bellman ford #2452
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
base: master
Are you sure you want to change the base?
Bellman ford #2452
Conversation
|
It's a WIP. No need to review rn |
|
Algorithm is ready to be reviewed |
| 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()); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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() }); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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(¤t_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), | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
|
That's the article where I got the algorithm |
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