diff --git a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp index f006511b..08279375 100644 --- a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp +++ b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp @@ -302,3 +302,135 @@ static PyObject* minimum_spanning_tree_prim_adjacency_list(PyObject* self, PyObj } return reinterpret_cast(mst); } + +static PyObject* shortest_paths_dijkstra_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + const char* target_name = ""; + + static const char* kwlist[] = {"graph", "source_node", "target_node", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!s|s", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj, + &source_name, &target_name)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + const size_t V = graph->node_map.size(); + + std::unordered_map dist; + std::unordered_map pred; + + for (const auto& [name, node] : graph->node_map) { + dist[name] = std::numeric_limits::infinity(); + pred[name] = ""; + } + dist[source_name] = 0.0; + + using PQEntry = std::pair; + std::priority_queue, std::greater<>> pq; + pq.push({0.0, source_name}); + + while (!pq.empty()) { + auto [u_dist, u_name] = pq.top(); pq.pop(); + + if (u_dist > dist[u_name]) continue; + + AdjacencyListGraphNode* u = graph->node_map[u_name]; + for (const auto& [v_name, _] : u->adjacent) { + std::string edge_key = make_edge_key(u_name, v_name); + auto edge_it = graph->edges.find(edge_key); + if (edge_it == graph->edges.end()) continue; + + GraphEdge* edge = edge_it->second; + double weight = 0.0; + if (edge->value_type == DataType::Int) + weight = static_cast(std::get(edge->value)); + else if (edge->value_type == DataType::Double) + weight = std::get(edge->value); + else + continue; + + if (weight < 0) continue; + + double new_dist = dist[u_name] + weight; + if (new_dist < dist[v_name]) { + dist[v_name] = new_dist; + pred[v_name] = u_name; + pq.push({new_dist, v_name}); + } + } + } + + PyObject* dist_dict = PyDict_New(); + PyObject* pred_dict = PyDict_New(); + if (!dist_dict || !pred_dict) return nullptr; + + for (const auto& [v, d] : dist) { + PyObject* dval = PyFloat_FromDouble(d); + if (!dval || PyDict_SetItemString(dist_dict, v.c_str(), dval) < 0) { + Py_XDECREF(dval); + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + Py_DECREF(dval); + } + + for (const auto& [v, p] : pred) { + PyObject* py_pred; + if (p.empty()) { + Py_INCREF(Py_None); + py_pred = Py_None; + } else { + py_pred = PyUnicode_FromString(p.c_str()); + if (!py_pred) { + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + } + + if (PyDict_SetItemString(pred_dict, v.c_str(), py_pred) < 0) { + Py_DECREF(py_pred); + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + Py_DECREF(py_pred); + } + + if (strlen(target_name) > 0) { + PyObject* out = PyTuple_New(2); + if (!out) { + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + + PyObject* dist_val = PyFloat_FromDouble(dist[target_name]); + if (!dist_val) { + Py_DECREF(out); + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + + PyTuple_SetItem(out, 0, dist_val); + PyTuple_SetItem(out, 1, pred_dict); + Py_DECREF(dist_dict); + return out; + } + + PyObject* result = PyTuple_New(2); + if (!result) { + Py_DECREF(dist_dict); + Py_DECREF(pred_dict); + return nullptr; + } + + PyTuple_SetItem(result, 0, dist_dict); + PyTuple_SetItem(result, 1, pred_dict); + return result; +} diff --git a/pydatastructs/graphs/_backend/cpp/algorithms.cpp b/pydatastructs/graphs/_backend/cpp/algorithms.cpp index f4e044b1..1c2e738a 100644 --- a/pydatastructs/graphs/_backend/cpp/algorithms.cpp +++ b/pydatastructs/graphs/_backend/cpp/algorithms.cpp @@ -7,6 +7,7 @@ static PyMethodDef AlgorithmsMethods[] = { {"bfs_adjacency_list", (PyCFunction)breadth_first_search_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency list with callback"}, {"bfs_adjacency_matrix", (PyCFunction)breadth_first_search_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency matrix with callback"}, {"minimum_spanning_tree_prim_adjacency_list", (PyCFunction)minimum_spanning_tree_prim_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run Prim's algorithm on adjacency list"}, + {"shortest_paths_dijkstra_adjacency_list", (PyCFunction)shortest_paths_dijkstra_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Dijkstra's algorithm for adjacency list graphs"}, {NULL, NULL, 0, NULL} }; diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 47869e34..9324b727 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -804,15 +804,19 @@ def shortest_paths(graph: Graph, algorithm: str, .. [1] https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm .. [2] https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm """ - raise_if_backend_is_not_python( - shortest_paths, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_" + algorithm + "_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently %s algorithm isn't implemented for " - "finding shortest paths in graphs."%(algorithm)) - return getattr(algorithms, func)(graph, source, target) + backend = kwargs.get('backend', Backend.PYTHON) + if (backend == Backend.PYTHON): + import pydatastructs.graphs.algorithms as algorithms + func = "_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algorithm isn't implemented for " + "finding shortest paths in graphs."%(algorithm)) + return getattr(algorithms, func)(graph, source, target) + else: + from pydatastructs.graphs._backend.cpp._algorithms import shortest_paths_dijkstra_adjacency_list + if graph._impl == "adjacency_list" and algorithm == 'dijkstra': + return shortest_paths_dijkstra_adjacency_list(graph, source, target) def _bellman_ford_adjacency_list(graph: Graph, source: str, target: str) -> tuple: distances, predecessor, visited, cnts = {}, {}, {}, {} diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index f898bbc7..27e0a67a 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -367,6 +367,25 @@ def _test_shortest_paths_positive_edges(ds, algorithm): graph.add_edge('D', 'SLC', -10) assert raises(ValueError, lambda: shortest_paths(graph, 'bellman_ford', 'SLC')) + if (ds == 'List' and algorithm == 'dijkstra'): + vertices2 = [AdjacencyListGraphNode('S', 0, backend = Backend.CPP), AdjacencyListGraphNode('C', 0, backend = Backend.CPP), + AdjacencyListGraphNode('SLC', 0, backend = Backend.CPP), AdjacencyListGraphNode('SF', 0, backend = Backend.CPP), + AdjacencyListGraphNode('D', 0, backend = Backend.CPP)] + graph2 = Graph(*vertices2, backend = Backend.CPP) + graph2.add_edge('S', 'SLC', 2) + graph2.add_edge('C', 'S', 4) + graph2.add_edge('C', 'D', 2) + graph2.add_edge('SLC', 'C', 2) + graph2.add_edge('SLC', 'D', 3) + graph2.add_edge('SF', 'SLC', 2) + graph2.add_edge('SF', 'S', 2) + graph2.add_edge('D', 'SF', 3) + (dist2, pred2) = shortest_paths(graph2, algorithm, 'SLC', backend = Backend.CPP) + assert dist2 == {'S': 6, 'C': 2, 'SLC': 0, 'SF': 6, 'D': 3} + assert pred2 == {'S': 'C', 'C': 'SLC', 'SLC': None, 'SF': 'D', 'D': 'SLC'} + + + def _test_shortest_paths_negative_edges(ds, algorithm): import pydatastructs.utils.misc_util as utils GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")