Skip to content
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

[ALGO] Dijkstra Shortest Path (Tree Version) (#54) #62

Merged
merged 5 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
21 changes: 21 additions & 0 deletions include/graaflib/algorithm/shortest_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ std::optional<graph_path<WEIGHT_T>> dijkstra_shortest_path(
const graph<V, E, T>& graph, vertex_id_t start_vertex,
vertex_id_t end_vertex);

/**
* Find the shortest paths from a source vertex to all other vertices in the
* graph using Dijkstra's algorithm.
*
* @tparam V The vertex type of the graph.
* @tparam E The edge type of the graph.
* @tparam T The graph type (directed or undirected).
* @tparam WEIGHT_T The type of edge weights.
* @param graph The graph we want to search.
* @param source_vertex The source vertex from which to compute shortest paths.
* @return A map containing the shortest paths from the source vertex to all
* other vertices. The map keys are target vertex IDs, and the values are
* instances of graph_path, representing the shortest distance and the path
* (list of vertex IDs) from the source to the target. If a vertex is not
* reachable from the source, its entry will be absent from the map.
*/
template <typename V, typename E, graph_type T,
typename WEIGHT_T = decltype(get_weight(std::declval<E>()))>
[[nodiscard]] std::unordered_map<vertex_id_t, graph_path<WEIGHT_T>>
dijkstra_shortest_paths(const graph<V, E, T>& graph, vertex_id_t source_vertex);

} // namespace graaf::algorithm

#include "shortest_path.tpp"
54 changes: 48 additions & 6 deletions include/graaflib/algorithm/shortest_path.tpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ std::optional<graph_path<WEIGHT_T>> bfs_shortest_path(
break;
}

for (const auto& neighbor : graph.get_neighbors(current)) {
for (const auto neighbor : graph.get_neighbors(current)) {
if (!vertex_info.contains(neighbor)) {
vertex_info[neighbor] = {
neighbor, vertex_info[current].dist_from_start + 1, current};
Expand All @@ -78,12 +78,12 @@ template <typename V, typename E, graph_type T, typename WEIGHT_T>
std::optional<graph_path<WEIGHT_T>> dijkstra_shortest_path(
const graph<V, E, T>& graph, vertex_id_t start_vertex,
vertex_id_t end_vertex) {
std::unordered_map<vertex_id_t, detail::path_vertex<WEIGHT_T>> vertex_info;

using weighted_path_item = detail::path_vertex<WEIGHT_T>;
std::priority_queue<weighted_path_item, std::vector<weighted_path_item>,
std::greater<>>
to_explore{};
using dijkstra_queue_t =
std::priority_queue<weighted_path_item, std::vector<weighted_path_item>,
std::greater<>>;
dijkstra_queue_t to_explore{};
std::unordered_map<vertex_id_t, weighted_path_item> vertex_info;

vertex_info[start_vertex] = {start_vertex, 0, start_vertex};
to_explore.push(vertex_info[start_vertex]);
Expand Down Expand Up @@ -111,4 +111,46 @@ std::optional<graph_path<WEIGHT_T>> dijkstra_shortest_path(
return reconstruct_path(start_vertex, end_vertex, vertex_info);
}

template <typename V, typename E, graph_type T, typename WEIGHT_T>
[[nodiscard]] std::unordered_map<vertex_id_t, graph_path<WEIGHT_T>>
dijkstra_shortest_paths(const graph<V, E, T>& graph,
vertex_id_t source_vertex) {
std::unordered_map<vertex_id_t, graph_path<WEIGHT_T>> shortest_paths;

using weighted_path_item = detail::path_vertex<WEIGHT_T>;
using dijkstra_queue_t =
std::priority_queue<weighted_path_item, std::vector<weighted_path_item>,
std::greater<>>;
dijkstra_queue_t to_explore{};

shortest_paths[source_vertex].total_weight = 0;
shortest_paths[source_vertex].vertices.push_back(source_vertex);
to_explore.push(weighted_path_item{source_vertex, 0});

while (!to_explore.empty()) {
auto current{to_explore.top()};
to_explore.pop();

if (shortest_paths.contains(current.id) &&
current.dist_from_start > shortest_paths[current.id].total_weight) {
continue;
}

for (const auto neighbor : graph.get_neighbors(current.id)) {
WEIGHT_T distance = current.dist_from_start +
get_weight(graph.get_edge(current.id, neighbor));

if (!shortest_paths.contains(neighbor) ||
distance < shortest_paths[neighbor].total_weight) {
shortest_paths[neighbor].total_weight = distance;
shortest_paths[neighbor].vertices = shortest_paths[current.id].vertices;
shortest_paths[neighbor].vertices.push_back(neighbor);
to_explore.push(weighted_path_item{neighbor, distance});
}
}
}

return shortest_paths;
}

} // namespace graaf::algorithm
87 changes: 87 additions & 0 deletions test/graaflib/algorithm/shortest_path_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,91 @@ TYPED_TEST(DijkstraShortestPathTest, DijkstraCyclicShortestPath) {
ASSERT_EQ(path, expected_path);
}

TYPED_TEST(DijkstraShortestPathTest, DijkstraMinimalShortestPathTree) {
// GIVEN
using graph_t = typename TestFixture::graph_t;
using edge_t = typename TestFixture::edge_t;
using weight_t = decltype(get_weight(std::declval<edge_t>()));

graph_t graph{};

const auto vertex_id_1{graph.add_vertex(10)};

// WHEN;
const auto path_map = dijkstra_shortest_paths(graph, vertex_id_1);

// THEN
const graph_path<weight_t> path1{{vertex_id_1}, 0};
std::unordered_map<vertex_id_t, graph_path<weight_t>> expected_path_map;
expected_path_map[vertex_id_1] = path1;
ASSERT_EQ(path_map, expected_path_map);
}

TYPED_TEST(DijkstraShortestPathTest, DijkstraSimpleShortestPathTree) {
// GIVEN
using graph_t = typename TestFixture::graph_t;
using edge_t = typename TestFixture::edge_t;
using weight_t = decltype(get_weight(std::declval<edge_t>()));

graph_t graph{};

const auto vertex_id_1{graph.add_vertex(10)};
const auto vertex_id_2{graph.add_vertex(20)};
graph.add_edge(vertex_id_1, vertex_id_2, edge_t{static_cast<weight_t>(3)});

// WHEN
const auto path_map = dijkstra_shortest_paths(graph, vertex_id_1);

// THEN
const graph_path<weight_t> path1{{vertex_id_1}, 0};
const graph_path<weight_t> path2{{vertex_id_1, vertex_id_2}, 3};

std::unordered_map<vertex_id_t, graph_path<weight_t>> expected_path_map;
expected_path_map[vertex_id_1] = path1;
expected_path_map[vertex_id_2] = path2;
ASSERT_EQ(path_map, expected_path_map);
}

TYPED_TEST(DijkstraShortestPathTest, DijkstraMoreComplexShortestPathTree) {
// GIVEN
using graph_t = typename TestFixture::graph_t;
using edge_t = typename TestFixture::edge_t;
using weight_t = decltype(get_weight(std::declval<edge_t>()));

graph_t graph{};

const auto vertex_id_1{graph.add_vertex(10)};
const auto vertex_id_2{graph.add_vertex(20)};
const auto vertex_id_3{graph.add_vertex(30)};
const auto vertex_id_4{graph.add_vertex(40)};
const auto vertex_id_5{graph.add_vertex(50)};

graph.add_edge(vertex_id_1, vertex_id_2, edge_t{static_cast<weight_t>(1)});
graph.add_edge(vertex_id_2, vertex_id_3, edge_t{static_cast<weight_t>(1)});
graph.add_edge(vertex_id_1, vertex_id_3, edge_t{static_cast<weight_t>(3)});
graph.add_edge(vertex_id_3, vertex_id_4, edge_t{static_cast<weight_t>(4)});
graph.add_edge(vertex_id_4, vertex_id_5, edge_t{static_cast<weight_t>(5)});
graph.add_edge(vertex_id_3, vertex_id_5, edge_t{static_cast<weight_t>(6)});

// WHEN
const auto path_map = dijkstra_shortest_paths(graph, vertex_id_1);

// THEN
const graph_path<weight_t> path1{{vertex_id_1}, 0};
const graph_path<weight_t> path2{{vertex_id_1, vertex_id_2}, 1};
const graph_path<weight_t> path3{{vertex_id_1, vertex_id_2, vertex_id_3}, 2};
const graph_path<weight_t> path4{
{vertex_id_1, vertex_id_2, vertex_id_3, vertex_id_4}, 6};
const graph_path<weight_t> path5{
{vertex_id_1, vertex_id_2, vertex_id_3, vertex_id_5}, 8};

std::unordered_map<vertex_id_t, graph_path<weight_t>> expected_path_map;
expected_path_map[vertex_id_1] = path1;
expected_path_map[vertex_id_2] = path2;
expected_path_map[vertex_id_3] = path3;
expected_path_map[vertex_id_4] = path4;
expected_path_map[vertex_id_5] = path5;
ASSERT_EQ(path_map, expected_path_map);
}

} // namespace graaf::algorithm