Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add the A* algorithm. #35

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 Branch and Bound/AStar Path Finding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AStar path finding

<img src="https://cdn.jsdelivr.net/gh/zjl9959/algviz-launch@master/svgs/AStarPathFinding_sec.svg" width=240px/>

AStar(A*) is an effective path-finding algorithm for a grid map (such as a 2D maze).
A* algorithm uses breadth-first search as the basic framework, but it uses a priority queue to sort the point to be explored. The algorithm will explore the point in the front of the priority queue first.

The key of the priority queue is the `hamiltonDistance(start, candidate) + hamiltonDistance(candidate, end)` and the value is the candidate point. The keys in the priority queue are heuristic information to guide the algorithm to explore the points that are more likely to be on the shortest path. So A* algorithm is more effective than the breadth-first search algorithm.

## Complexity

**Time Complexity:** `O(R·C)` for the worst case, `O(R + C)` for the best case.

**Space Complexity:** `O(R·C) ~ O(R + C)` cost by the priority queue.

*`R` is the grid map's row number and `C` is the grid map's column number.*

## Reference

+ [redblobgames - introduction to the A* Algorithm](https://www.redblobgames.com/pathfinding/a-star/introduction.html)
+ [algviz - an algorithm animation engine for Python](https://github.com/zjl9959/algviz-launch)
133 changes: 133 additions & 0 deletions Branch and Bound/AStar Path Finding/code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// import visualization libraries {
const { Tracer, Array2DTracer, Array1DTracer, Layout, VerticalLayout } = require('algorithm-visualizer');
// }

// define map information. {
const row = 10;
const col = 10;
let map = [
['', '', '', '', '', '', '', '', '', ''], // 0
['', '', '💣', '', '', '', '', '', '', ''], // 1
['', 'S', '', '💣', '', '', '', '', '', ''], // 2
['', '', '', '💣', '', '', '', '', '', ''], // 3
['', '', '', '', '', '💣', '', '', '', ''], // 4
['', '', '', '', '💣', '💣', '', '', '', ''], // 5
['', '', '', '', '', '💣', '', '', '', ''], // 6
['', '', '', '', '', '', '💣', '', 'E', ''], // 7
['', '', '', '', '', '', '💣', '', '', ''], // 8
['', '', '', '', '', '', '', '', '', ''] // 9
];
const start = [2, 1];
const end = [7, 8];
let pqueue = [start];
let que_keys = [0];
let visit = new Set();
// }

// define tracer variables {
const mapTracer = new Array2DTracer('Map');
const priorityQueueTracer = new Array1DTracer('PriorityQueue-key');
Layout.setRoot(new VerticalLayout([mapTracer, priorityQueueTracer]));

mapTracer.set(map);
priorityQueueTracer.set(que_keys);
Tracer.delay();
// }

function hamiltonDistance(pos1, pos2) {
return Math.abs(pos1[0]-pos2[0]) + Math.abs(pos1[1]-pos2[1]);
}

function insertQueue(que_keys, k, v) {
let put = false;
que_keys.push(0); pqueue.push(0);
if (que_keys.length > 1) {
for (let i = que_keys.length - 2; i >= 0; i--) {
if (k < que_keys[i]) {
pqueue[i+1] = v;
que_keys[i+1] = k;
put = true;
priorityQueueTracer.patch(i+1, k);
break;
} else {
pqueue[i+1] = pqueue[i];
que_keys[i+1] = que_keys[i];
priorityQueueTracer.patch(i+1, que_keys[i]);
}
}
}
if (put === false) {
que_keys[0] = k;
pqueue[0] = v;
priorityQueueTracer.patch(0, k);
}
}

function isEnd(pos) { return pos[0] == end[0] && pos[1] == end[1]; }

function isBlock(pos) {
return map[pos[0]][pos[1]] === '💣';
}

function isVisited(pos) {
return visit.has(pos[0]*col + pos[1]);
}

function findPath() {
while (pqueue.length > 0) {
let cur = pqueue.pop(); que_keys.pop();
if (isEnd(cur)) break;
if (isVisited(cur)) continue;
visit.add(cur[0]*col + cur[1]);
let up = [cur[0]-1, cur[1]];
let down = [cur[0]+1, cur[1]];
let left = [cur[0], cur[1]-1];
let right = [cur[0], cur[1]+1];
let cur_dist = hamiltonDistance(cur, start);
if (up[0] >= 0 && !isVisited(up) && !isBlock(up)) {
let dist = cur_dist + hamiltonDistance(up, end);
insertQueue(que_keys, dist, up);
map[up[0]][up[1]] = '↓';
// visualize {
if (!isEnd(up)) {
mapTracer.patch(up[0], up[1], '↓');
}
// }
}
if (down[0] < row && !isVisited(down) && !isBlock(down)) {
let dist = cur_dist + hamiltonDistance(down, end);
insertQueue(que_keys, dist, down);
map[down[0]][down[1]] = '↑';
// visualize {
if (!isEnd(down)) {
mapTracer.patch(down[0], down[1], '↑');
}
// }
}
if (left[1] >= 0 && !isVisited(left) && !isBlock(left)) {
let dist = cur_dist + hamiltonDistance(left, end);
insertQueue(que_keys, dist, left);
map[left[0]][left[1]] = '→';
// visualize {
if (!isEnd(left)) {
mapTracer.patch(left[0], left[1], '→');
}
// }
}
if (right[1] < col && !isVisited(right) && !isBlock(right)) {
let dist = cur_dist + hamiltonDistance(right, end);
insertQueue(que_keys, dist, right);
map[right[0]][right[1]] = '←';
// visualize {
if (!isEnd(right)) {
mapTracer.patch(right[0], right[1], '←');
}
// }
}
// visualize {
Tracer.delay();
// }
}
}

findPath();