Skip to content

Commit d076a35

Browse files
authored
Add C++ implementation for Dijkstra's single source shortest paths algorithm (#686)
1 parent 7a1e4cb commit d076a35

File tree

4 files changed

+165
-9
lines changed

4 files changed

+165
-9
lines changed

pydatastructs/graphs/_backend/cpp/Algorithms.hpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,135 @@ static PyObject* minimum_spanning_tree_prim_adjacency_list(PyObject* self, PyObj
302302
}
303303
return reinterpret_cast<PyObject*>(mst);
304304
}
305+
306+
static PyObject* shortest_paths_dijkstra_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) {
307+
PyObject* graph_obj;
308+
const char* source_name;
309+
const char* target_name = "";
310+
311+
static const char* kwlist[] = {"graph", "source_node", "target_node", nullptr};
312+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!s|s", const_cast<char**>(kwlist),
313+
&AdjacencyListGraphType, &graph_obj,
314+
&source_name, &target_name)) {
315+
return nullptr;
316+
}
317+
318+
AdjacencyListGraph* graph = reinterpret_cast<AdjacencyListGraph*>(graph_obj);
319+
320+
const size_t V = graph->node_map.size();
321+
322+
std::unordered_map<std::string, double> dist;
323+
std::unordered_map<std::string, std::string> pred;
324+
325+
for (const auto& [name, node] : graph->node_map) {
326+
dist[name] = std::numeric_limits<double>::infinity();
327+
pred[name] = "";
328+
}
329+
dist[source_name] = 0.0;
330+
331+
using PQEntry = std::pair<double, std::string>;
332+
std::priority_queue<PQEntry, std::vector<PQEntry>, std::greater<>> pq;
333+
pq.push({0.0, source_name});
334+
335+
while (!pq.empty()) {
336+
auto [u_dist, u_name] = pq.top(); pq.pop();
337+
338+
if (u_dist > dist[u_name]) continue;
339+
340+
AdjacencyListGraphNode* u = graph->node_map[u_name];
341+
for (const auto& [v_name, _] : u->adjacent) {
342+
std::string edge_key = make_edge_key(u_name, v_name);
343+
auto edge_it = graph->edges.find(edge_key);
344+
if (edge_it == graph->edges.end()) continue;
345+
346+
GraphEdge* edge = edge_it->second;
347+
double weight = 0.0;
348+
if (edge->value_type == DataType::Int)
349+
weight = static_cast<double>(std::get<int64_t>(edge->value));
350+
else if (edge->value_type == DataType::Double)
351+
weight = std::get<double>(edge->value);
352+
else
353+
continue;
354+
355+
if (weight < 0) continue;
356+
357+
double new_dist = dist[u_name] + weight;
358+
if (new_dist < dist[v_name]) {
359+
dist[v_name] = new_dist;
360+
pred[v_name] = u_name;
361+
pq.push({new_dist, v_name});
362+
}
363+
}
364+
}
365+
366+
PyObject* dist_dict = PyDict_New();
367+
PyObject* pred_dict = PyDict_New();
368+
if (!dist_dict || !pred_dict) return nullptr;
369+
370+
for (const auto& [v, d] : dist) {
371+
PyObject* dval = PyFloat_FromDouble(d);
372+
if (!dval || PyDict_SetItemString(dist_dict, v.c_str(), dval) < 0) {
373+
Py_XDECREF(dval);
374+
Py_DECREF(dist_dict);
375+
Py_DECREF(pred_dict);
376+
return nullptr;
377+
}
378+
Py_DECREF(dval);
379+
}
380+
381+
for (const auto& [v, p] : pred) {
382+
PyObject* py_pred;
383+
if (p.empty()) {
384+
Py_INCREF(Py_None);
385+
py_pred = Py_None;
386+
} else {
387+
py_pred = PyUnicode_FromString(p.c_str());
388+
if (!py_pred) {
389+
Py_DECREF(dist_dict);
390+
Py_DECREF(pred_dict);
391+
return nullptr;
392+
}
393+
}
394+
395+
if (PyDict_SetItemString(pred_dict, v.c_str(), py_pred) < 0) {
396+
Py_DECREF(py_pred);
397+
Py_DECREF(dist_dict);
398+
Py_DECREF(pred_dict);
399+
return nullptr;
400+
}
401+
Py_DECREF(py_pred);
402+
}
403+
404+
if (strlen(target_name) > 0) {
405+
PyObject* out = PyTuple_New(2);
406+
if (!out) {
407+
Py_DECREF(dist_dict);
408+
Py_DECREF(pred_dict);
409+
return nullptr;
410+
}
411+
412+
PyObject* dist_val = PyFloat_FromDouble(dist[target_name]);
413+
if (!dist_val) {
414+
Py_DECREF(out);
415+
Py_DECREF(dist_dict);
416+
Py_DECREF(pred_dict);
417+
return nullptr;
418+
}
419+
420+
PyTuple_SetItem(out, 0, dist_val);
421+
PyTuple_SetItem(out, 1, pred_dict);
422+
Py_DECREF(dist_dict);
423+
return out;
424+
}
425+
426+
PyObject* result = PyTuple_New(2);
427+
if (!result) {
428+
Py_DECREF(dist_dict);
429+
Py_DECREF(pred_dict);
430+
return nullptr;
431+
}
432+
433+
PyTuple_SetItem(result, 0, dist_dict);
434+
PyTuple_SetItem(result, 1, pred_dict);
435+
return result;
436+
}

pydatastructs/graphs/_backend/cpp/algorithms.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ static PyMethodDef AlgorithmsMethods[] = {
77
{"bfs_adjacency_list", (PyCFunction)breadth_first_search_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency list with callback"},
88
{"bfs_adjacency_matrix", (PyCFunction)breadth_first_search_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency matrix with callback"},
99
{"minimum_spanning_tree_prim_adjacency_list", (PyCFunction)minimum_spanning_tree_prim_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run Prim's algorithm on adjacency list"},
10+
{"shortest_paths_dijkstra_adjacency_list", (PyCFunction)shortest_paths_dijkstra_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Dijkstra's algorithm for adjacency list graphs"},
1011
{NULL, NULL, 0, NULL}
1112
};
1213

pydatastructs/graphs/algorithms.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -804,15 +804,19 @@ def shortest_paths(graph: Graph, algorithm: str,
804804
.. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
805805
.. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
806806
"""
807-
raise_if_backend_is_not_python(
808-
shortest_paths, kwargs.get('backend', Backend.PYTHON))
809-
import pydatastructs.graphs.algorithms as algorithms
810-
func = "_" + algorithm + "_" + graph._impl
811-
if not hasattr(algorithms, func):
812-
raise NotImplementedError(
813-
"Currently %s algorithm isn't implemented for "
814-
"finding shortest paths in graphs."%(algorithm))
815-
return getattr(algorithms, func)(graph, source, target)
807+
backend = kwargs.get('backend', Backend.PYTHON)
808+
if (backend == Backend.PYTHON):
809+
import pydatastructs.graphs.algorithms as algorithms
810+
func = "_" + algorithm + "_" + graph._impl
811+
if not hasattr(algorithms, func):
812+
raise NotImplementedError(
813+
"Currently %s algorithm isn't implemented for "
814+
"finding shortest paths in graphs."%(algorithm))
815+
return getattr(algorithms, func)(graph, source, target)
816+
else:
817+
from pydatastructs.graphs._backend.cpp._algorithms import shortest_paths_dijkstra_adjacency_list
818+
if graph._impl == "adjacency_list" and algorithm == 'dijkstra':
819+
return shortest_paths_dijkstra_adjacency_list(graph, source, target)
816820

817821
def _bellman_ford_adjacency_list(graph: Graph, source: str, target: str) -> tuple:
818822
distances, predecessor, visited, cnts = {}, {}, {}, {}

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,25 @@ def _test_shortest_paths_positive_edges(ds, algorithm):
367367
graph.add_edge('D', 'SLC', -10)
368368
assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC'))
369369

370+
if (ds == 'List' and algorithm == 'dijkstra'):
371+
vertices2 = [AdjacencyListGraphNode('S', 0, backend = Backend.CPP), AdjacencyListGraphNode('C', 0, backend = Backend.CPP),
372+
AdjacencyListGraphNode('SLC', 0, backend = Backend.CPP), AdjacencyListGraphNode('SF', 0, backend = Backend.CPP),
373+
AdjacencyListGraphNode('D', 0, backend = Backend.CPP)]
374+
graph2 = Graph(*vertices2, backend = Backend.CPP)
375+
graph2.add_edge('S', 'SLC', 2)
376+
graph2.add_edge('C', 'S', 4)
377+
graph2.add_edge('C', 'D', 2)
378+
graph2.add_edge('SLC', 'C', 2)
379+
graph2.add_edge('SLC', 'D', 3)
380+
graph2.add_edge('SF', 'SLC', 2)
381+
graph2.add_edge('SF', 'S', 2)
382+
graph2.add_edge('D', 'SF', 3)
383+
(dist2, pred2) = shortest_paths(graph2, algorithm, 'SLC', backend = Backend.CPP)
384+
assert dist2 == {'S': 6, 'C': 2, 'SLC': 0, 'SF': 6, 'D': 3}
385+
assert pred2 == {'S': 'C', 'C': 'SLC', 'SLC': None, 'SF': 'D', 'D': 'SLC'}
386+
387+
388+
370389
def _test_shortest_paths_negative_edges(ds, algorithm):
371390
import pydatastructs.utils.misc_util as utils
372391
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")

0 commit comments

Comments
 (0)