blog/assets/js/493c0536.6ae62548.js
github-actions[bot] 1d4ae5f520 deploy: 5549494f67
2024-01-03 14:14:53 +00:00

1 line
No EOL
17 KiB
JavaScript

"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[7292],{45594:(e,s,a)=>{a.r(s),a.d(s,{assets:()=>o,contentTitle:()=>r,default:()=>m,frontMatter:()=>i,metadata:()=>l,toc:()=>h});var t=a(85893),n=a(11151);const i={id:"astar",slug:"/paths/bf-to-astar/astar",title:"A* algorithm",description:"Moving from Dijkstra's algorithm into the A* algorithm.\n",tags:["cpp","dynamic programming","astar"],last_update:{date:new Date("2024-01-03T00:00:00.000Z")}},r=void 0,l={id:"paths/2024-01-01-bf-to-astar/astar",title:"A* algorithm",description:"Moving from Dijkstra's algorithm into the A* algorithm.\n",source:"@site/algorithms/11-paths/2024-01-01-bf-to-astar/03-astar.md",sourceDirName:"11-paths/2024-01-01-bf-to-astar",slug:"/paths/bf-to-astar/astar",permalink:"/algorithms/paths/bf-to-astar/astar",draft:!1,unlisted:!1,editUrl:"https://github.com/mfocko/blog/tree/main/algorithms/11-paths/2024-01-01-bf-to-astar/03-astar.md",tags:[{label:"cpp",permalink:"/algorithms/tags/cpp"},{label:"dynamic programming",permalink:"/algorithms/tags/dynamic-programming"},{label:"astar",permalink:"/algorithms/tags/astar"}],version:"current",lastUpdatedAt:170424e4,formattedLastUpdatedAt:"Jan 3, 2024",sidebarPosition:3,frontMatter:{id:"astar",slug:"/paths/bf-to-astar/astar",title:"A* algorithm",description:"Moving from Dijkstra's algorithm into the A* algorithm.\n",tags:["cpp","dynamic programming","astar"],last_update:{date:"2024-01-03T00:00:00.000Z"}},sidebar:"autogeneratedBar",previous:{title:"Dijkstra's algorithm",permalink:"/algorithms/paths/bf-to-astar/dijkstra"},next:{title:"Hash Tables",permalink:"/algorithms/category/hash-tables"}},o={},h=[{value:"Intro",id:"intro",level:2},{value:"A* description",id:"a-description",level:2},{value:"Roadmap heuristic",id:"roadmap-heuristic",level:2},{value:"Heuristic for our map",id:"heuristic-for-our-map",level:2},{value:"Passing the heuristic",id:"passing-the-heuristic",level:2},{value:"Implementation",id:"implementation",level:2},{value:"Running on our map",id:"running-on-our-map",level:2},{value:"Comparison",id:"comparison",level:2},{value:"Summary",id:"summary",level:2}];function c(e){const s={admonition:"admonition",annotation:"annotation",code:"code",em:"em",h2:"h2",li:"li",math:"math",mi:"mi",mo:"mo",mrow:"mrow",msub:"msub",ol:"ol",p:"p",pre:"pre",semantics:"semantics",span:"span",...(0,n.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(s.h2,{id:"intro",children:"Intro"}),"\n",(0,t.jsx)(s.p,{children:"Let's start by the recap of what we've achieved so far:"}),"\n",(0,t.jsxs)(s.ol,{children:["\n",(0,t.jsx)(s.li,{children:"We have implemented a na\xefve brute-force algorithm that tries to relax paths\nas long as there are any paths to be relaxed."}),"\n",(0,t.jsxs)(s.li,{children:["Then we have fixed an issue caused by negative loops that can result in\na non-terminating run of our brute-force method. At this moment we have made\nsome small arguments why are bounding is enough and doesn't prevent any\nshortest path to ",(0,t.jsx)(s.em,{children:"not be"})," discovered."]}),"\n",(0,t.jsx)(s.li,{children:"Finally we have converted our bounded brute-force algorithm into the\nBellman-Ford algorithm."}),"\n",(0,t.jsx)(s.li,{children:"We have mentioned the worst-case time complexity of our bounded na\xefve\napproach and also the Bellman-Ford algorithm. Our worst-case depended on the\nfact that we assumed the worst possible ordering of the relaxations. However\nwe could also try to relax in the most ideal ordering which could result in a\nfaster algorithm and that's how we got to the Dijkstra's algorithm."}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["Now the question is, could we improve the Dijkstra's algorithm to get even\nbetter results? And the answer is ",(0,t.jsx)(s.em,{children:"maybe"}),"!"]}),"\n",(0,t.jsx)(s.p,{children:"Dijkstra's algorithm chooses the next cheapest vertex for relaxing. This is good\nas long as there is no additional information. However, imagine a roadmap of\nsome country. If you're in the middle of the map and you want to go south, it\ndoesn't make much sense for you to go to the north (in the opposite direction),\nbut a little bit might make sense, so that you can switch to highway and go much\nfaster."}),"\n",(0,t.jsxs)(s.p,{children:["The important question here is how to ",(0,t.jsx)(s.em,{children:"influence"})," the algorithm, so that it does\nchoose the path that ",(0,t.jsx)(s.em,{children:"makes more sense"})," rather than the one that costs the\nleast."]}),"\n",(0,t.jsx)(s.h2,{id:"a-description",children:"A* description"}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.em,{children:"A* algorithm"})," can be considered a modification of Dijkstra's algorithm. The\ncost is still the same, we cannot change it, right? However when we pick the\nvertices from the heap, we can influence the order by some ",(0,t.jsx)(s.em,{children:"heuristic"}),". In this\ncase, we introduce a function that can suggest how feasible the vertex is."]}),"\n",(0,t.jsx)(s.h2,{id:"roadmap-heuristic",children:"Roadmap heuristic"}),"\n",(0,t.jsx)(s.p,{children:"Let's have a look at the heuristic we could use for the roadmap example. There\nare roads (the edges) and towns (the vertices). Cost could be an average time to\ntravel the road. What heuristic could we use to influence our algorithm to\nchoose a better ordering of the vertices when relaxing?"}),"\n",(0,t.jsx)(s.p,{children:"In the former example we've said that it doesn't make much sense to go in the\nopposite direction than our goal is\u2026 We could choose the distance from our goal\nas the heuristic, e.g. right now we're 100 km away from our goal, using this\nroad makes us 50 km away and using the other road we will be 200 km away."}),"\n",(0,t.jsx)(s.h2,{id:"heuristic-for-our-map",children:"Heuristic for our map"}),"\n",(0,t.jsxs)(s.p,{children:["Our map is a bit simpler, but we can use a very similar principle. We will use\nthe ",(0,t.jsx)(s.em,{children:"Manhattan distance"}),", which is defined in a following way:"]}),"\n",(0,t.jsx)(s.span,{className:"katex-display",children:(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",display:"block",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mi,{mathvariant:"normal",children:"\u2223"}),(0,t.jsxs)(s.msub,{children:[(0,t.jsx)(s.mi,{children:"x"}),(0,t.jsx)(s.mi,{children:"a"})]}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsxs)(s.msub,{children:[(0,t.jsx)(s.mi,{children:"x"}),(0,t.jsx)(s.mi,{children:"b"})]}),(0,t.jsx)(s.mi,{mathvariant:"normal",children:"\u2223"}),(0,t.jsx)(s.mo,{children:"+"}),(0,t.jsx)(s.mi,{mathvariant:"normal",children:"\u2223"}),(0,t.jsxs)(s.msub,{children:[(0,t.jsx)(s.mi,{children:"y"}),(0,t.jsx)(s.mi,{children:"a"})]}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsxs)(s.msub,{children:[(0,t.jsx)(s.mi,{children:"y"}),(0,t.jsx)(s.mi,{children:"b"})]}),(0,t.jsx)(s.mi,{mathvariant:"normal",children:"\u2223"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"\\vert x_a - x_b \\vert + \\vert y_a - y_b \\vert"})]})})}),(0,t.jsxs)(s.span,{className:"katex-html","aria-hidden":"true",children:[(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mord",children:"\u2223"}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord mathnormal",children:"x"}),(0,t.jsx)(s.span,{className:"msupsub",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.1514em"},children:(0,t.jsxs)(s.span,{style:{top:"-2.55em",marginLeft:"0em",marginRight:"0.05em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"2.7em"}}),(0,t.jsx)(s.span,{className:"sizing reset-size6 size3 mtight",children:(0,t.jsx)(s.span,{className:"mord mathnormal mtight",children:"a"})})]})}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200b"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.15em"},children:(0,t.jsx)(s.span,{})})})]})})]}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}}),(0,t.jsx)(s.span,{className:"mbin",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}})]}),(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord mathnormal",children:"x"}),(0,t.jsx)(s.span,{className:"msupsub",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.3361em"},children:(0,t.jsxs)(s.span,{style:{top:"-2.55em",marginLeft:"0em",marginRight:"0.05em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"2.7em"}}),(0,t.jsx)(s.span,{className:"sizing reset-size6 size3 mtight",children:(0,t.jsx)(s.span,{className:"mord mathnormal mtight",children:"b"})})]})}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200b"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.15em"},children:(0,t.jsx)(s.span,{})})})]})})]}),(0,t.jsx)(s.span,{className:"mord",children:"\u2223"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}}),(0,t.jsx)(s.span,{className:"mbin",children:"+"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}})]}),(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mord",children:"\u2223"}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.03588em"},children:"y"}),(0,t.jsx)(s.span,{className:"msupsub",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.1514em"},children:(0,t.jsxs)(s.span,{style:{top:"-2.55em",marginLeft:"-0.0359em",marginRight:"0.05em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"2.7em"}}),(0,t.jsx)(s.span,{className:"sizing reset-size6 size3 mtight",children:(0,t.jsx)(s.span,{className:"mord mathnormal mtight",children:"a"})})]})}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200b"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.15em"},children:(0,t.jsx)(s.span,{})})})]})})]}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}}),(0,t.jsx)(s.span,{className:"mbin",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}})]}),(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.03588em"},children:"y"}),(0,t.jsx)(s.span,{className:"msupsub",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.3361em"},children:(0,t.jsxs)(s.span,{style:{top:"-2.55em",marginLeft:"-0.0359em",marginRight:"0.05em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"2.7em"}}),(0,t.jsx)(s.span,{className:"sizing reset-size6 size3 mtight",children:(0,t.jsx)(s.span,{className:"mord mathnormal mtight",children:"b"})})]})}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200b"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.15em"},children:(0,t.jsx)(s.span,{})})})]})})]}),(0,t.jsx)(s.span,{className:"mord",children:"\u2223"})]})]})]})}),"\n",(0,t.jsx)(s.p,{children:"Since we cannot move in diagonals, it makes sense to maintain the distance in\nthe actual steps from the goal."}),"\n",(0,t.jsx)(s.h2,{id:"passing-the-heuristic",children:"Passing the heuristic"}),"\n",(0,t.jsx)(s.p,{children:"In our case, when we're using C++, we can just template the function that will\ncalculate the shortest path and pass the heuristic as a parameter."}),"\n",(0,t.jsx)(s.h2,{id:"implementation",children:"Implementation"}),"\n",(0,t.jsx)(s.p,{children:"Actual implementation is very easy once we have the Dijkstra's algorithm:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-cpp",children:"auto astar(const graph& g, const vertex_t& source, const auto& h)\n -> std::vector<std::vector<int>> {\n // make sure that \u2039source\u203a exists\n assert(g.has(source));\n\n // initialize the distances\n std::vector<std::vector<int>> distances(\n g.height(), std::vector(g.width(), graph::unreachable()));\n\n // initialize the visited\n std::vector<std::vector<bool>> visited(g.height(),\n std::vector(g.width(), false));\n\n // \u2039source\u203a destination denotes the beginning where the cost is 0\n auto [sx, sy] = source;\n distances[sy][sx] = 0;\n\n pqueue_t priority_queue{std::make_pair(0 + h(source), source)};\n std::optional<pqueue_item_t> item{};\n while ((item = popq(priority_queue))) {\n auto [cost, u] = *item;\n auto [x, y] = u;\n\n // we have already found the shortest path\n if (visited[y][x]) {\n continue;\n }\n visited[y][x] = true;\n\n for (const auto& [dx, dy] : DIRECTIONS) {\n auto v = std::make_pair(x + dx, y + dy);\n auto cost = g.cost(u, v);\n\n // if we can move to the cell and it's better, relax\xb9 it and update queue\n if (cost != graph::unreachable() &&\n distances[y][x] + cost < distances[y + dy][x + dx]) {\n distances[y + dy][x + dx] = distances[y][x] + cost;\n pushq(priority_queue,\n std::make_pair(distances[y + dy][x + dx] + h(v), v));\n }\n }\n }\n\n return distances;\n}\n"})}),"\n",(0,t.jsx)(s.h2,{id:"running-on-our-map",children:"Running on our map"}),"\n",(0,t.jsx)(s.p,{children:"For this algorithm I will also show the example of a call:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-cpp",children:'distances = astar(g, std::make_pair(1, 9), [](const auto& u) {\n auto [x, y] = u;\n return std::abs(1 - x) + std::abs(7 - y);\n});\nstd::cout << "[A*] Cost: " << distances[7][1] << "\\n";\n'})}),"\n",(0,t.jsxs)(s.p,{children:["First argument to the function is the graph itself. Second argument is the\nsource vertex where we start. And finally the lambda returns\n",(0,t.jsx)(s.em,{children:"Manhattan distance"})," to the goal."]}),"\n",(0,t.jsx)(s.p,{children:"And we get the following result:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{children:"Normal cost: 1\nVortex cost: 5\nGraph:\n#############\n#..#..*.*.**#\n##***.....**#\n#..########.#\n#...###...#.#\n#..#...##.#.#\n#..#.*.#..#.#\n#D...#....#.#\n########*.*.#\n#S..........#\n#############\n[Finite BF] Cost: 22\n[Bellman-Ford] Cost: 22\n[Dijkstra] Cost: 22\n[A*] Cost: 22\n"})}),"\n",(0,t.jsx)(s.h2,{id:"comparison",children:"Comparison"}),"\n",(0,t.jsx)(s.p,{children:"Now you may wonder how does it compare to the previous algorithms. Supposedly it\nshould be faster. Let's add counters and debugging output when we update\ndistance to our goal. And now if we run our code, we get the following output:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{children:"Normal cost: 1\nVortex cost: 5\nGraph:\n#############\n#..#..*.*.**#\n##***.....**#\n#..########.#\n#...###...#.#\n#..#...##.#.#\n#..#.*.#..#.#\n#D...#....#.#\n########*.*.#\n#S..........#\n#############\nRelaxing path to goal in 40. relaxation\nRelaxing path to goal in 68. relaxation\nRelaxing path to goal in 89. relaxation\n[Finite BF] Cost: 22\nRelaxing path to goal in 40. relaxation\nRelaxing path to goal in 68. relaxation\nRelaxing path to goal in 89. relaxation\n[Bellman-Ford] Cost: 22\nRelaxing path to goal in 41. iteration\n[Dijkstra] Cost: 22\nRelaxing path to goal in 31. iteration\n[A*] Cost: 22\n"})}),"\n",(0,t.jsx)(s.p,{children:"From the output we can easily deduce that for both brute-force and Bellman-Ford,\nwhich are in our case identical, we actually relax three times and for the last\ntime in the 89th iteration."}),"\n",(0,t.jsx)(s.p,{children:"Dijkstra's algorithm manages to find the shortest path to our goal already in\nthe 41st iteration."}),"\n",(0,t.jsx)(s.p,{children:"And finally after introducing some heuristic, we could find the shortest path\nin the 31st iteration."}),"\n",(0,t.jsx)(s.admonition,{type:"danger",children:(0,t.jsx)(s.p,{children:"Please keep in mind that choosing bad heuristic can actually lead to worse\nresults than using no heuristic at all."})}),"\n",(0,t.jsx)(s.h2,{id:"summary",children:"Summary"}),"\n",(0,t.jsx)(s.p,{children:"And there we have it. We have made our way from the brute-force algorithm all\nthe way to more optimal ones. Hopefully we could notice how the small\nimprovements of the already existing algorithms made them much better."})]})}function m(e={}){const{wrapper:s}={...(0,n.a)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},11151:(e,s,a)=>{a.d(s,{Z:()=>l,a:()=>r});var t=a(67294);const n={},i=t.createContext(n);function r(e){const s=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:r(e.components),t.createElement(i.Provider,{value:s},e.children)}}}]);