Skip to content

Commit 57e45f2

Browse files
authored
Merge pull request #915 from abckhush/main
Added A* Algorithm
2 parents 050e719 + c20bfa6 commit 57e45f2

File tree

2 files changed

+265
-1
lines changed

2 files changed

+265
-1
lines changed

dsa-solutions/lc-solutions/0000-0099/0017-letter-combinations-of-a-phone-number.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Given a string containing digits from 2-9 inclusive, return all possible letter
3939
- **Output:** `["a","b","c"]`
4040

4141
### Constraints:
42+
4243
- `0 ≤ digits.length ≤ 4`
4344
- `0 ≤ digits.length ≤ 4digits[𝑖]`
4445
- `digits[i] is a digit in the range ['2', '9'].`
@@ -313,4 +314,4 @@ Here's a step-by-step algorithm for generating all possible letter combinations
313314
- Call the backtracking function with the initial index set to 0 and an empty string as the initial combination.
314315
- Return the list of combinations.
315316

316-
This algorithm ensures that all possible combinations are generated by exploring all valid paths through backtracking.
317+
This algorithm ensures that all possible combinations are generated by exploring all valid paths through backtracking.

dsa/Algorithms/a-star.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
---
2+
id: a-star
3+
title: A* Algorithm
4+
sidebar_label: A* Algorithm
5+
tags: [python, c++, programming, algorithms, A*, graph, shortest-path, data structures, tutorial, in-depth]
6+
description: In this tutorial, we will learn about A* Algorithm and its implementation in Python, and C++ with detailed explanations and examples.
7+
---
8+
9+
# A* Algorithm
10+
11+
The A* Algorithm is an informed search algorithm used for finding the shortest path between two nodes in a graph. It is widely used in various applications such as pathfinding for games, AI, and robotics due to its efficiency and accuracy.
12+
13+
## How A* Algorithm Works
14+
15+
The A* Algorithm combines the advantages of Dijkstra's Algorithm and Greedy Best-First Search. It uses a priority queue to explore nodes based on the cost function:
16+
17+
𝑓(𝑛)=𝑔(𝑛)+ℎ(𝑛)
18+
19+
Where:
20+
g(n) is the cost from the start node to the current node.
21+
h(n) is the heuristic function that estimates the cost from the current node n to the goal node.
22+
23+
## Heuristics in A* Algorithm
24+
The choice of the heuristic function h(n) is crucial for the efficiency of the A* Algorithm. Common heuristics include:
25+
26+
1. **Manhattan Distance:** Used for grid-based maps.
27+
2. **Euclidean Distance:** Used for geometric spaces.
28+
3. **Chebyshev Distance:** Used when diagonal movement is allowed.
29+
The heuristic function should be admissible, meaning it never overestimates the actual cost to reach the goal.
30+
31+
## Complexity Analysis
32+
The time complexity of the A* Algorithm is
33+
𝑂(𝑏𝑑)
34+
35+
WHERE:
36+
b is the branching factor (the average number of successors per state) and
37+
d is the depth of the goal. The space complexity is also
38+
𝑂(𝑏𝑑) as it needs to store all generated nodes in the worst case.
39+
40+
## Comparison with Other Algorithms
41+
42+
1. **Dijkstra's Algorithm:** A* is generally faster because it uses heuristics to guide its search, while Dijkstra's explores all possible paths.
43+
2. **Greedy Best-First Search:** A* is more accurate because it takes into account both the actual cost from the start and the estimated cost to the goal, while Greedy Best-First only considers the latter.
44+
45+
## Common Applications
46+
47+
1. **Game Development:** For pathfinding characters in video games.
48+
2. **Robotics:** For navigating robots through environments.
49+
3. **Geographic Information Systems (GIS):** For finding the shortest route in maps.
50+
51+
## Pseudocode
52+
Here is the pseudocode for the A* Algorithm:
53+
54+
```pseudo
55+
function A*(start, goal)
56+
openSet := {start}
57+
cameFrom := empty map
58+
gScore := map with default value of Infinity
59+
gScore[start] := 0
60+
fScore := map with default value of Infinity
61+
fScore[start] := heuristic(start, goal)
62+
63+
while openSet is not empty
64+
current := node in openSet with lowest fScore[] value
65+
if current == goal
66+
return reconstruct_path(cameFrom, current)
67+
68+
openSet.Remove(current)
69+
for each neighbor of current
70+
tentative_gScore := gScore[current] + d(current, neighbor)
71+
if tentative_gScore < gScore[neighbor]
72+
cameFrom[neighbor] := current
73+
gScore[neighbor] := tentative_gScore
74+
fScore[neighbor] := gScore[neighbor] + heuristic(neighbor, goal)
75+
if neighbor not in openSet
76+
openSet.Add(neighbor)
77+
78+
return failure
79+
80+
function reconstruct_path(cameFrom, current)
81+
total_path := {current}
82+
while current in cameFrom.Keys
83+
current := cameFrom[current]
84+
total_path.Prepend(current)
85+
return total_path
86+
````
87+
88+
## Implementation in Python
89+
```python
90+
import heapq
91+
92+
def heuristic(a, b):
93+
return abs(a[0] - b[0]) + abs(a[1] - b[1])
94+
95+
def a_star(graph, start, goal):
96+
open_set = []
97+
heapq.heappush(open_set, (0, start))
98+
came_from = {}
99+
g_score = {start: 0}
100+
f_score = {start: heuristic(start, goal)}
101+
102+
while open_set:
103+
_, current = heapq.heappop(open_set)
104+
105+
if current == goal:
106+
return reconstruct_path(came_from, current)
107+
108+
for neighbor in graph[current]:
109+
tentative_g_score = g_score[current] + graph[current][neighbor]
110+
if tentative_g_score < g_score.get(neighbor, float('inf')):
111+
came_from[neighbor] = current
112+
g_score[neighbor] = tentative_g_score
113+
f_score[neighbor] = g_score[neighbor] + heuristic(neighbor, goal)
114+
heapq.heappush(open_set, (f_score[neighbor], neighbor))
115+
116+
return None
117+
118+
def reconstruct_path(came_from, current):
119+
path = [current]
120+
while current in came_from:
121+
current = came_from[current]
122+
path.append(current)
123+
return path[::-1]
124+
125+
126+
# Example usage
127+
graph = {
128+
(0, 0): {(0, 1): 1, (1, 0): 1},
129+
(0, 1): {(0, 0): 1, (1, 1): 1, (0, 2): 1},
130+
(1, 0): {(0, 0): 1, (1, 1): 1, (2, 0): 1},
131+
(1, 1): {(1, 0): 1, (0, 1): 1, (1, 2): 1, (2, 1): 1},
132+
(2, 0): {(1, 0): 1, (2, 1): 1},
133+
(2, 1): {(2, 0): 1, (1, 1): 1, (2, 2): 1},
134+
(0, 2): {(0, 1): 1, (1, 2): 1},
135+
(1, 2): {(1, 1): 1, (0, 2): 1, (2, 2): 1},
136+
(2, 2): {(2, 1): 1, (1, 2): 1},
137+
}
138+
139+
start = (0, 0)
140+
goal = (2, 2)
141+
print(a_star(graph, start, goal))
142+
```
143+
144+
145+
## Implementation in C++
146+
```cpp
147+
#include <iostream>
148+
#include <vector>
149+
#include <queue>
150+
#include <unordered_map>
151+
#include <cmath>
152+
153+
using namespace std;
154+
155+
struct Node {
156+
int x, y, f, g, h;
157+
Node* parent;
158+
159+
Node(int x, int y) : x(x), y(y), f(0), g(0), h(0), parent(nullptr) {}
160+
161+
bool operator==(const Node& other) const {
162+
return x == other.x && y == other.y;
163+
}
164+
165+
struct HashFunction {
166+
size_t operator()(const Node& node) const {
167+
return hash<int>()(node.x) ^ hash<int>()(node.y);
168+
}
169+
};
170+
};
171+
172+
int heuristic(Node* a, Node* b) {
173+
return abs(a->x - b->x) + abs(a->y - b->y);
174+
}
175+
176+
vector<Node*> reconstructPath(unordered_map<Node*, Node*, Node::HashFunction>& cameFrom, Node* current) {
177+
vector<Node*> path;
178+
while (current != nullptr) {
179+
path.push_back(current);
180+
current = cameFrom[current];
181+
}
182+
reverse(path.begin(), path.end());
183+
return path;
184+
}
185+
186+
vector<Node*> aStar(Node* start, Node* goal, unordered_map<Node*, vector<Node*>, Node::HashFunction>& graph) {
187+
auto cmp = [](Node* left, Node* right) { return left->f > right->f; };
188+
priority_queue<Node*, vector<Node*>, decltype(cmp)> openSet(cmp);
189+
unordered_map<Node*, Node*, Node::HashFunction> cameFrom;
190+
unordered_map<Node*, int, Node::HashFunction> gScore;
191+
gScore[start] = 0;
192+
start->f = heuristic(start, goal);
193+
openSet.push(start);
194+
195+
while (!openSet.empty()) {
196+
Node* current = openSet.top();
197+
openSet.pop();
198+
199+
if (*current == *goal) {
200+
return reconstructPath(cameFrom, current);
201+
}
202+
203+
for (Node* neighbor : graph[current]) {
204+
int tentativeGScore = gScore[current] + 1;
205+
if (tentativeGScore < gScore[neighbor]) {
206+
cameFrom[neighbor] = current;
207+
gScore[neighbor] = tentativeGScore;
208+
neighbor->f = tentativeGScore + heuristic(neighbor, goal);
209+
openSet.push(neighbor);
210+
}
211+
}
212+
}
213+
return {};
214+
}
215+
216+
int main() {
217+
Node* start = new Node(0, 0);
218+
Node* goal = new Node(2, 2);
219+
220+
unordered_map<Node*, vector<Node*>, Node::HashFunction> graph;
221+
graph[start] = {new Node(0, 1), new Node(1, 0)};
222+
graph[new Node(0, 1)] = {start, new Node(1, 1), new Node(0, 2)};
223+
graph[new Node(1, 0)] = {start, new Node(1, 1), new Node(2, 0)};
224+
graph[new Node(1, 1)] = {new Node(1, 0), new Node(0, 1), new Node(1, 2), new Node(2, 1)};
225+
graph[new Node(2, 0)] = {new Node(1, 0), new Node(2, 1)};
226+
graph[new Node(2, 1)] = {new Node(2, 0), new Node(1, 1), new Node(2, 2)};
227+
graph[new Node(0, 2)] = {new Node(0, 1), new Node(1, 2)};
228+
graph[new Node(1, 2)] = {new Node(1, 1), new Node(0, 2), new Node(2, 2)};
229+
graph[new Node(2, 2)] = {new Node(2, 1), new Node(1, 2)};
230+
231+
vector<Node*> path = aStar(start, goal, graph);
232+
for (Node* node : path) {
233+
cout << "(" << node->x << ", " << node->y << ")" << endl;
234+
}
235+
236+
// Clean up dynamically allocated nodes
237+
for (auto& pair : graph) {
238+
delete pair.first;
239+
for (Node* node : pair.second) {
240+
delete node;
241+
}
242+
}
243+
244+
return 0;
245+
}
246+
```
247+
248+
## Optimizations and Variations
249+
250+
1. **Bidirectional A*** **:** Runs two simultaneous searches—one forward from the start and one backward from the goal.
251+
2. **Weighted A*** **:** Modifies the heuristic function to allow faster, though potentially suboptimal, solutions.
252+
3. **Iterative Deepening A*** **:** Combines the benefits of depth-first and breadth-first search, useful for memory-constrained environments.
253+
254+
## Visualization Tools
255+
256+
1. **Pathfinding.js:** A library for visualizing pathfinding algorithms in JavaScript.
257+
2. **Graphhopper:** An open-source routing library and server, ideal for map-based applications.
258+
3. **Mazewar:** A web-based tool to visualize and understand different pathfinding algorithms.
259+
260+
## Conclusion
261+
The A* Algorithm is a powerful and efficient pathfinding algorithm that can be implemented in various programming languages. By understanding its principles and applying the appropriate heuristic functions, you can leverage A* for a wide range of applications.
262+
263+
In this tutorial, we have covered the theoretical background, provided pseudocode, and demonstrated implementations in Python, Java, C++, and JavaScript. With this knowledge, you should be well-equipped to utilize the A* Algorithm in your own projects.

0 commit comments

Comments
 (0)