{"id":20408689,"url":"https://github.com/mutazhelal/algorithm-comparison","last_synced_at":"2025-07-05T21:36:17.731Z","repository":{"id":184981316,"uuid":"672788970","full_name":"MutazHelal/Algorithm-Comparison","owner":"MutazHelal","description":"A comparison of shortest paths algorithms, a comparison of Dijkstra vs A*, a comparison of Dijkstra vs A* by constructing the graph from the London map(tube) database, and code refractor.","archived":false,"fork":false,"pushed_at":"2023-07-31T09:43:08.000Z","size":46,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-05T02:41:29.352Z","etag":null,"topics":["algorithms","dijkstra-algorithm","graph","python3","shortest-path","shortest-path-algorithm"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MutazHelal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-31T07:07:37.000Z","updated_at":"2023-08-01T00:50:32.000Z","dependencies_parsed_at":"2025-01-15T12:17:39.669Z","dependency_job_id":"3f61a46a-17d8-4274-b02b-db66be58826a","html_url":"https://github.com/MutazHelal/Algorithm-Comparison","commit_stats":null,"previous_names":["mutazhelal/algorithm-comparison"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MutazHelal/Algorithm-Comparison","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MutazHelal%2FAlgorithm-Comparison","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MutazHelal%2FAlgorithm-Comparison/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MutazHelal%2FAlgorithm-Comparison/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MutazHelal%2FAlgorithm-Comparison/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MutazHelal","download_url":"https://codeload.github.com/MutazHelal/Algorithm-Comparison/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MutazHelal%2FAlgorithm-Comparison/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263810899,"owners_count":23515226,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithms","dijkstra-algorithm","graph","python3","shortest-path","shortest-path-algorithm"],"created_at":"2024-11-15T05:35:37.672Z","updated_at":"2025-07-05T21:36:17.705Z","avatar_url":"https://github.com/MutazHelal.png","language":"Python","readme":"## Algorithm-Comparison\n\n## 1: Shortest Paths Algorithms\n\n### 1.1 Approximation of Dijkstra and Bellman-Ford Experiments\n\nBoth ”dijkstraapprox” and ”bellmanfordapprox” are implemented by taking the original algorithms, modifying\nthem such that this the following code, which relaxes the nodes, is run at most k times per node.\n\n```\nif d[u]+w(u,v)¡d[v]\nd[v] = d[u]+w(u,v)\n```\nThe following are experiments run, each testing a different aspect of the performance of these two algorithms.\n\n#### 1.1.1 Dijkstra and Bellman Approximation\n\nOutline\n\n- Graph size: 30 nodes and maximum edge weight is 50\n- Number of Edges: 870\n- Both algorithms’ approximations are run till achieving the actual shortest path\n  \n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/883165ab-4713-4f2f-9542-5cd471da01af)\n```\nFigure 1: Dijkstra Approximation vs BellmanFord Approximation\n```\n\nOutline Explanation\nThis is a very simple experiment which should showcase how close each of both approximations are to the the actual\nshortest path and how many relaxations per node are needed to get the shortest path. It is a general performance\ngauge.\n\nObservation\nDijkstra is a lot closer to the total distance of the actual shortest path and for this specific experiment reaches the\nactual shortest path with only around 20 relaxations per node while Bellman needed a bit over 50. Therefore, we\nconclude that Dijkstra’s approximation is a lot more accurate.\n\n\n#### 1.1.2 Number of Relaxation\n\nOutline\n\n- Number of Graphs: 50 graphs, from 1 node to 50 node graph\n- Number of Edges: (number of nodes) times (number of nodes - 1)\n- Edge Weight: from 1 to 50\n- Both algorithms’ approximations are run till achieving the actual shortest path\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/4ec1de8c-5e66-4ead-8b16-4de3846b8c73)\n```\nFigure 2: Dijkstra Approximation vs BellmanFord Required number of Relaxation\n```\nOutline Explanation\nThis experiment while not much different from the first experiment, focuses on the number of relaxations needed to\nget from the approximation to the actual shortest path. The two variables on the graph are the number of nodes\nand the number of relaxations to get the actual shortest path.\n\nObservation\nSimilar to experiment 1, Bellman almost always requires a higher number of relaxation per node to get the shortest\npath. These two experiments suggest that Dijkstra is a more efficient algorithm and Bellman should be used only\nwhen having negative edge-weight values.\n\n#### 1.1.3 Edge Weight Range Value\n\nOutline\n\n- Graph Size: 20 nodes\n- Number of Edges: (number of nodes) times (number of nodes - 1)\n- Edge Weight: from 1 to 100\n\n\n- Both algorithms’ approximations are run till achieving the actual shortest path\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/a7fa5ed8-3d8f-4398-8326-40013101f1ed)\n```\nFigure 3: Dijkstra Approximation vs BellmanFord edge weight limit\n```\n\nOutline Explanation\nThis experiment focuses on the variation in the range of values a weight of an edge can take. A lower range means an\nincrease in the number of shortest paths. The parameter should not have a huge impact on the number of relaxations\nneeded for each algorithm. However, it could highlight how the scenario is dealt with by both procedures.\n\nObservation\nWhile still confirming what was previously established by experiments 1 and 2 when it comes to efficiency. These\nexperiments show how less stable Bellman is compared to Dijkstra which is somewhat evident in experiment 2 (see\nFigure 2). This might be due to Bellman having to recheck nodes that are already visited. For Dijkstra, once a node\nis marked it is never visited again.\n\n#### 1.1.4 Relaxation Limit\n\nOutline\n\n- Graph Size: 100 graphs from 1 node graph to 100 nodes graph\n- Number of Edges: (number of nodes) times (number of nodes - 1)\n- Edge Weight:1 to 50\n- Number of relaxations: at most 20\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/cef7f592-2ebc-44fe-a9b6-c4f022874aa1)\n```\nFigure 4: Dijkstra Approximation vs BellmanFord number of relaxation limit\n```\n\nOutline Explanation\nThis experiment is a different take on the approximation of both algorithms. Limiting node relaxations to 20 per\nnode while increasing the graph size and comparing how close each algorithm is to the actual shortest path.\n\nObservation\nDijkstra clearly outperforms Bellman. Bellman approximations start getting very inaccurate at graphs with around\n20 nodes. On the other hand, Dijkstra, even though results are not exact, paths are really close to the actual shortest\npath all the way to graphs with 100 nodes. This suggests that for very large graphs and when results don’t have to\nbe exact, Dijkstra’s approximation might prove very useful.\n\n#### 1.1.5 Graph edge density\n\nOutline\n\n- Graph Size: fixed to 50 nodes for all graphs\n- Number of Edges: (number of nodes) times (number of nodes - 1) for the first graph and less (limited) for the\n    second graph\n- Edge Weight:1 to 50\n- Number of relaxations: As many as needed for the first graph and limited for the second graph from 1 to 50\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/7b4d0d87-c31a-4590-a051-3c533964f5d8)\n```\nFigure 5: Dijkstra Approximation vs BellmanFord edge density limit\n```\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/8c114535-32b5-4692-802b-3ff1da3e85f7)\n```\nFigure 6: Dijkstra Approximation vs BellmanFord edge density and relaxation limit\n```\nOutline Explanation\nThis experiment consists of limiting the number of edges. This is done by limiting the number of nodes connected to\nall nodes. For instance, in a 10-node graph, if the limit is 2, it means only the first and second nodes are connected\n\n\nto all other nodes. A prediction of the results of this experiment was an increasing number of relaxations required\nto find the shortest path compared to experiment 2.\n\nAs for graph 2, the number of edges is limited in the same way with the addition of limiting the number of relax-\nations along with the edge limit. The number on the x-axis represents both the edge limit and relaxation limit. This\nis done in order to gauge the degree of accuracy both algorithms perform when dealing with graphs that are less dense.\n\nObservation\nFor graph 1, as expected, the number of relaxation is higher for Bellman-Ford but surprisingly it is around the same\n(if not less) for Dijkstra (see Figure 2). This suggests that graph Dijkstra has equal efficiency with graphs of different\nedge density.\n\nFor graph 2, Dijkstra always resulted in the actual shortest path, which makes sense given how this experiment was\ndone (n nodes connected to all = n relaxations per node). Bellman-Ford was slightly inaccurate but not too far off.\n\n### 1.2 All Source Dijkstra and Bellman-Ford\n\nRunning an all-source Dijkstra and Bellman-Ford algorithms using algorithms by running them once for each node\nand saving each predecessor dictionary in a single list yields a mapping of the shortest map from any node to all nodes.\n\nThe complexities of all source algorithms would be O (V^3 ) for Dijkstra and O (V^4 ) for Bellman-Ford. This is self-\nevident by the fact that we run each algorithm V times which represents the number of nodes in the graph.\n\n### 1.3 Mystery function\n\nThe function seems to be a matrix of an all-source shortest-path algorithm. Whoever through testing, the function is\nfound not to be able to handle negative weight edges which might suggest it is a generalization from Dijkstra rather\nthan Bellman-Ford.\nBy code inspection, it seems that the function is O(V^3 ) for a dense graph. However, we run further experiments to\nconfirm or deny this time complexity.\n\nMystery function empirical testing\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/b529d192-ee65-47fb-8bd3-97b8ed1c98ac)\n```\nFigure 7: Mystery function log-log scaled graph\n```\nObservation\nUsing a log-log graph to plot the performance of the function we can determine the degree of the polynomial time\nto be around 2.7 (the slope of the line log(y) = n*log(x)+log(c)), which we could round to 3. This number,O(V^3 )\nagrees with the time complexity prediction based on the code inspection. This also makes sense, given that the\nalgorithm has 2 nested loops. These results reaffirm two initial claims stated above, which state that the Mystery\nfunction is a variation of Dijkstra for all source paths and that the time complexity is O(V^3 ) for dense graphs.\n\n## 2: Dijkstra and A∗\n\n### 2.1 How does A* it work?\n\nThis algorithm is very similar to Dijkstra’s pathfinding algorithm but it follows a heuristic. In the regular Dijkstra’s\nalgorithm, we take as inputs the weighted edge graph, the start node, and our destination node; in the A* algorithm\nwe take in an extra parameter which is a heuristic function. The function takes in a node and estimates the minimum\ncost from that node to the destination node. The estimate might be based on the Euclidean distance between the\ntwo nodes. This way we keep track of the total cost so far to reach a node ’n’ from the start node + the estimated\ncost from n to our destination node. The sum gives us the total estimated path to our destination node, through\nnode ’n’.\n\n\n### 2.2 What issues with Dijkstra’s algorithm is A* trying to address?\n\nThe problem with Dijkstra’s algorithm is that it tries to explore every node in the graph before it can come up with\nthe shortest path between two nodes. Although, this might not seem like a massive issue with smaller graphs, \nin graphs with more nodes and edges, running Dijkstra’s algorithm is very slow. This is where the A* algorithm\nsteps in and makes Dijkstra’s algorithm more efficient by using the heuristic function described earlier. The heuristic\nessentially guides the algorithm towards the destination node by making it explore the most promising paths first\nwhich in turn leads to a faster search for the shortest path.\n\nAlso, Dijkstra’s algorithm cannot handle negative edge weights as it would lead to sub-optimal solutions or might\nas well lead to a non-termination of the algorithm. Meanwhile, the A* algorithm can handle negative edge weights\nbecause it never expands a node such that the sum of the heuristic and the path has taken so far is greater than the\noptimal path length.\n\n### 2.3 How would you empirically test Dijkstra’s vs A*?\n\nWe can empirically test the difference in performance between these two algorithms by comparing their run times\nalong with the number of nodes expanded for the same graph. So we randomly generate edge-weighted graphs of\ndifferent sizes and edge numbers. For each of those graphs we randomly choose the start and the end node and then\nrun Dijkstra’s and A* algorithms. We then record their run time and the number of nodes expanded during the\nsearch and then repeat the same steps for a different graph. Finally, we use those two metrics to produce a graph\nand find some correlation.\n\nWe can also repeat the same experiment for different heuristic functions to figure out which performs the best.\n\n### 2.4 If you generated an arbitrary heuristic function (similar to randomly generating\n\n### weights), how would Dijkstra’s algorithm compare to A*?\n\nSince the efficiency of the A* algorithm depends heavily on the type and quality of the heuristic we follow, it may\nvery likely degrade if we just use any heuristic function. If the heuristic function is arbitrary and does not reflect the\nunderlying graph structure, A* may not be able to exploit the heuristic information effectively. Also, it might not\nnecessarily prevent the algorithm itself from exploring unnecessary nodes. This makes the algorithm at best similar\nto Dijkstra’s or probably even worse.\n\nIn such cases where we use an arbitrary heuristic, Dijkstra’s algorithm can guarantee consistence performance than\nA* as it does not depend on any heuristic.\n\n### 2.5 What applications would you use A* instead of Dijkstra’s?\n\nThe A* algorithms can be used in almost any place where it requires finding the shortest path between two points\namong many other channels efficiently.\n\nThe most common application is in satellite navigation where the algorithm needs to find the shortest path between\ntwo places in a geographic location. The heuristic function can be designed to consider the distance and the traffic\nconditions of the roads that vary over time.\n\nA* algorithm may be used in robotics where a robot needs to go from point A to B using the shortest path available.\nThe heuristic function can be designed to consider the robot’s ultrasound sensor readings from the obstacles in its\nenvironment\n\n\nNetwork routers commonly used the A* algorithm to find the quickest path to send a packet of data from the sender\nto the receiver. The heuristic function can be designed to consider the distance and the traffic in the network channel.\n\n### 2.6 A* Implementation\n\nAlong with this file, the astar.py file contains a simple implementation of A∗which works like Dijkstra with key\ndifferences being the order in which neighbours are explored, which is reordered on the basis of the value of f(x) =\ng(x) + h(x). g(x) is the edge cost and h(x) is the heuristic function cost. This allows a good path to be explored\nfirst. Thus, potentially resulting in better performance, giving a good choice for the heuristic function.\n\n## 3: A* vs Dijkstra Performance on Networks\n\n### 3.1 Experiment Outline\n\nThis experiment was run by constructing the graph from the London map(tube) database.\nDifferent parameters were taken as edge weights, these parameters are distance (x + y value), time, distance + time,\nand distance + line.\nAs for the heuristic function, it generates a dictionary that contains the distance from any station to all stations if\nthey were connected by a straight line.\nIt is important to note that both algorithms result in a shortest path. The difference lies in the order that explores\nnodes which also results in different paths that are equally short (follows from the definition of both algorithms).\n\nAll the following graphs run through a lot of different combinations of source destination nodes and plot a time\nperformance graph.\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/e88e7529-9158-47c3-ae0f-47e6f80883cb)\n```\nFigure 8: A* vs Dijkstra 1000 combinations with distance edge-weight\n```\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/8edb40e6-1618-4dfb-abbd-3ae746c7d936)\n```\nFigure 9: A* vs Dijkstra all combinations with distance edge-weight\n```\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/0fed70da-b354-45c8-ab73-57660b6a86e6)\n```\nFigure 10: A* vs Dijkstra 1000 combinations with distance + time edge-weight\n```\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/2f0755a7-8c7a-4de4-b401-61ab24625930)\n```\nFigure 11: A* vs Dijkstra 1000 combinations with line edge-weight\n```\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/6e2977b6-6187-4643-8eb2-2074bc77202d)\n```\nFigure 12: A* vs Dijkstra 1000 combinations with time edge-weight\n```\n\nObservation\nNo matter what parameter is taken for edge-weight, A* proves to be a hindrance rather than an improvement over\nDijkstra. The difference is constant which suggests that while A* is not inherently worse, it adds an unnecessary\noverhead cost. It is also important to note that A* is highly dependent on the heuristic function. We conclude from\nthese experiments that for this network, carefully chosen paths aren’t any better than random ones. However, a differ-\nent choice for the heuristic function might prove this wrong since it could be any function that prioritizes certain paths.\n\n### 3.2 Stations on the Same line\n\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/02646572-3d7d-49b3-8109-4132ea3ec6f7)\n```\nFigure 13: A* vs Dijkstra Stations on the same line\n```\nObservation\nWhen only considering stations on the same line, the performance difference between both algorithms is very\nminuscule, this makes sense since the path is very short, to begin with and does not require many iterations to reach the\ndestination.\n\n\n### 3.3 Stations on the Adjacent lines\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/cb828433-8e8b-4d05-b7b0-586b16a5a9fa)\n```\nFigure 14: A* vs Dijkstra Stations on adjacentLines\n```\nObservation\nMuch like the other experiments above. Dijkstra still outperforms A*. This graph reveals more about the relation\nbetween finding a shortest path for different parts of the network than about how A* and Dijkstra perform relative\nto each other. The periodic shape in the graph can easily be explained by how far two adjacent line stations are from\nthe connecting intersection. Stations start getting far apart until there is a new intersection that connects them.\n\n\n### 3.4 Stations with Several Transfers\n\n![image](https://github.com/MutazHelal/Algorithm-Comparison/assets/42630919/20bce925-3aee-48e2-a9a6-dca7471b2f31)\n```\nFigure 15: A* vs Dijkstra Stations with multiple transfers\n```\nObservation\nResults are very similar to adjacent stations which could be explained with the same reasoning as well. However, the\nonly difference is that this graph has a bump for later combinations which might suggest that some in-adjacent\nlines are further apart than others.\n\n## 4: Code Refactor\n\n### 4.1 Design Principles\n\nSingle Responsibility Principle:\n\nFigure 2 shows that every single module is responsible for a singular task. E.g The AStar module only does the\nA* pathfinding, similarly BellmanFord only does that unique path finding only. No module does more than one task.\n\nOpen-Closed Principle:\n\nIn Figure 2 we are using the two interfaces ”Graph” and ”SPAlgorithm” which we extend to make new classes. These\nnew classes can be given additional functionality, which means the original interface is open for extension but is also\nclosed for any modification.\n\nInterface Segregation Principle:\n\nIn Figure 2 it can be seen that the modules only depend on/use the interfaces they are supposed to use. E.g. Heuristic-\nGraph depends on WeightedGraph and in turn that depends on ”Graph” only, similarly Dijkstra, BellmanFord and\nAStar only depends on ”SPAlogrithm” because they don’t need any other interfaces to function correctly.\n\n\nDependency Inversion Principle:\n\nFigure 2 shows that the lower-level module HeuristicGraph depends on the higher-level module WeightedGraph and both\nin turn depend upon abstraction for their details. Similarly, Dijkstra, BellmanFord, and AStar also depend upon\nabstraction for details, and not the other way round.\n\n### 4.2 Design Patterns\n\nStrategy:\n\nAs seen in figure 2 the set algorithm function in ShortPathFinder is responsible for choosing which path-finding\nalgorithm is going to be used. The interface ”SPAlgorithm” contains the method for the algorithms Dijkstra, Bell-\nManford and AStar. During the execution of the program, the client code will set the strategy object for the context\nclass ”ShortPathFinder” to determine which algorithm to use.\n\n### 4.3 Modifying the design in Figure 2 to be robust to these potential changes.\n\nThe simple fix to this problem is to make the classes Generic. This means that every method within our generic\nclasses will also be generic. The data type will not be any primitive type, instead, we can specify the parameter\nduring object creation which can be integer, string, list, float, etc. This will allow us to use any kind of data type\nand also have multiple data types passed into a single method.\n\n### 4.4 Other types of Graphs and different implementations\n\nApart from the obvious way of implementing a graph using an adjacency set like the graph in Figure 2, we can also\nimplement the graph structure using an adjacency list and adjacency matrix.\n\nIn adjacency list, we use a linked list to keep track of which nodes are reachable from a particular node. In the adjacency\nmatrix, we use a 2D matrix where each cell represents an edge between two vertices. The value of the cell represents\nthe edge weight (Or it could be a negative value to indicate an edge does not exist).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutazhelal%2Falgorithm-comparison","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmutazhelal%2Falgorithm-comparison","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutazhelal%2Falgorithm-comparison/lists"}