From ee04735196d7d18da17ab985e17d64a743197ed1 Mon Sep 17 00:00:00 2001 From: zjl9959 Date: Mon, 12 Dec 2022 22:41:01 +0800 Subject: [PATCH] Implement the A* algorithm. --- Branch and Bound/AStar Path Finding/README.md | 21 +++ Branch and Bound/AStar Path Finding/code.js | 133 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 Branch and Bound/AStar Path Finding/README.md create mode 100644 Branch and Bound/AStar Path Finding/code.js diff --git a/Branch and Bound/AStar Path Finding/README.md b/Branch and Bound/AStar Path Finding/README.md new file mode 100644 index 00000000..08e5755c --- /dev/null +++ b/Branch and Bound/AStar Path Finding/README.md @@ -0,0 +1,21 @@ +# AStar path finding + + + +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) diff --git a/Branch and Bound/AStar Path Finding/code.js b/Branch and Bound/AStar Path Finding/code.js new file mode 100644 index 00000000..155cc3a7 --- /dev/null +++ b/Branch and Bound/AStar Path Finding/code.js @@ -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();