본문으로 건너뛰기
김신건의 로그

A* (A-star) 알고리즘

· 수정 · 📖 약 3분 · 1,104자/단어 #algorithm #search #heuristic #shortest-path
a-star, A*, A star, 에이스타, a* algorithm

정의

A* 알고리즘휴리스틱 기반 최단 경로 탐색 알고리즘. f(n)=g(n)+h(n) 을 평가 함수로 사용해 시작점에서 목표까지의 최단 경로를 찾는다. 1968년 Hart, Nilsson, Raphael이 제안.

Dijkstra의 일반화로, 휴리스틱 h(n) 이 admissible(목표까지 실제 비용 이하) 이면 최적해 보장.

문제 상황과 동기

가중치가 있는 그래프에서 s에서 t까지 최단 경로.

  • Dijkstra: 모든 방향 동등하게 확장. h(n)=0 인 A* 와 동일.
  • A*: “목표 방향” 정보를 h(n) 으로 주입. 불필요한 방향 확장 억제.

핵심 통찰: 남은 거리 예측치 h(n) 을 비용에 더하면 목표 방향으로 먼저 탐색. g(n) 은 이미 지나온 비용, h(n) 은 앞으로 남은 예측.

시각화

핵심 아이디어

open set 과 closed set 을 유지. open 에서 f(n)=g(n)+h(n) 이 가장 작은 노드를 꺼내 확장.

openSet = {start}
g[start] = 0, h[start] = heuristic(start, goal)
f[start] = g[start] + h[start]

while openSet not empty:
    current = node in openSet with smallest f
    if current == goal: 복원 후 반환
    
    openSet.remove(current)
    
    for each neighbor v of current:
        tentative_g = g[current] + w(current, v)
        if tentative_g < g[v]:
            g[v] = tentative_g
            f[v] = g[v] + heuristic(v, goal)
            openSet.insert(v)

h(n) 이 admissible(과대추정 안 함) 이면 A* 는 항상 최적 경로를 찾는다.

구현

// A*, priority queue + Manhattan heuristic (grid 예제)
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int,int>;

int heuristic(pii a, pii b) {
  return abs(a.first - b.first) + abs(a.second - b.second);
}

int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};

int astar(vector<string>& grid, pii start, pii goal) {
  int n = grid.size(), m = grid[0].size();
  vector<vector<int>> g(n, vector<int>(m, 1e9));
  priority_queue<pair<int,pii>,
                 vector<pair<int,pii>>,
                 greater<pair<int,pii>>> pq;

  g[start.first][start.second] = 0;
  int h_start = heuristic(start, goal);
  pq.push({h_start, start});

  while (!pq.empty()) {
      auto [f, cur] = pq.top(); pq.pop();
      auto [x, y] = cur;
      if (cur == goal) return g[x][y];

      for (int d = 0; d < 4; d++) {
          int nx = x + dx[d], ny = y + dy[d];
          if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
          if (grid[nx][ny] == '#') continue;
          int ng = g[x][y] + 1;
          if (ng < g[nx][ny]) {
              g[nx][ny] = ng;
              int f = ng + heuristic({nx, ny}, goal);
              pq.push({f, {nx, ny}});
          }
      }
  }
  return -1;
}

int main() {
  vector<string> grid = {
      ".....",
      ".#.#.",
      ".#.#.",
      ".#...",
      "....."
  };
  pii start = {0,0}, goal = {4,4};
  cout << astar(grid, start, goal);
  return 0;
}
stdin
..... .#.#. .#.#. .#... .....
결과
8

복잡도

항목
시간 (최선)O(E log V) - h(n) 이 목표 방향 완벽
시간 (평균)O(E log V) - typical
시간 (최악)O(V^2) - h(n) 이 나쁠 때 (Dijkstra 와 동일)
공간O(V)
최적성h(n) admissible 이면 보장

변형 / 활용

  • IDA*: 반복적 깊이 심화 A*, 메모리 적게 씀.
  • D* Lite: 동적 환경에서 경로 재계획 (로봇).
  • Theta*: any-angle pathfinding, 그리드 대각선 이동.
  • 게임 AI: NPC 길찾기 (Unity NavMesh, StarCraft).
  • 지도 내비게이션: 실제 도로망에서 A* 변형 사용.

함정

1. h(n) 이 admissible 하지 않으면 최적성 상실

과대추정 휴리스틱은 최단 경로를 놓칠 수 있다.

2. 중복 방문 관리

closed set 없이 open 만 쓰면 같은 노드를 여러 번 확장.

3. open set 이 너무 커지면 메모리 부담

격자형 맵에서 open set 수십만 개 가능. IDA* 고려.

탐색 과정 시각화

A* 의 노드 확장 순서: f 값이 낮은 노드를 먼저 확장한다.

flowchart LR
    S["시작, g=0, h=10"] -->|"f=10"| A["A, g=2, h=6"]
    A -->|"f=8"| B["B, g=4, h=3"]
    B -->|"f=7"| G["목표, g=7, h=0"]
    S -->|"f=15"| C["C, g=5, h=10"]

C 방향은 f=15 로 높아 나중에 확장. B 를 거쳐 목표에 먼저 도달. Dijkstra 와 달리 목표 방향으로 편향된 탐색.

휴리스틱 종류와 선택

휴리스틱수식적합한 상황
Manhattan|dx| + |dy|4방향 격자 (벽 없음)
Chebyshevmax(|dx|, |dy|)8방향 격자
Euclideansqrt(dx^2 + dy^2)연속 공간, any-angle
없음 (0)h(n) = 0Dijkstra 와 동일
Octilemax + 0.414 * min8방향 격자 근사

admissible 조건: 실제 비용 이하여야 최적성 보장. Manhattan 은 4방향 격자에서 항상 admissible.

h(n) 품질과 탐색 효율

h(n) 품질탐색 노드 수비고
h=0최대Dijkstra 와 동일, 방향 정보 없음
admissible 낮음많음보수적 추정
admissible 정확최소목표 방향 직진
inadmissible빠르지만 최적 아닐 수 있음Weighted A*

Weighted A*: f = g + w*h (w > 1) 로 속도 우선. 최적성은 포기하고 실행 시간을 줄임. 게임 실시간 NPC 에 자주 사용.

IDA* (Iterative Deepening A*)

메모리가 부족할 때 선택. open set 없이 DFS + 비용 임계값을 반복 상향.

// IDA* 골격: 메모리 O(d), 시간은 A* 보다 느릴 수 있음
int threshold, INF = 1e9;

int dfs(Node node, int g, int threshold) {
    int f = g + heuristic(node, goal);
    if (f > threshold) return f;   // 임계값 초과: 가지치기
    if (node == goal) return -1;   // 목표 도달
    int min_t = INF;
    for (Node next : neighbors(node)) {
        int t = dfs(next, g + cost(node, next), threshold);
        if (t == -1) return -1;    // 경로 발견
        if (t < min_t) min_t = t;
    }
    return min_t;  // 다음 임계값 후보
}

// 메인 루프
threshold = heuristic(start, goal);
while (true) {
    int result = dfs(start, 0, threshold);
    if (result == -1) { /* 경로 발견 */ break; }
    if (result == INF) { /* 경로 없음 */ break; }
    threshold = result;  // 임계값 상향
}

메모리 O(d) (d = 경로 깊이). 단, 동일 노드를 반복 탐색하는 오버헤드로 A* 보다 느릴 수 있음.

BOJ 연습 문제

번호제목정답률링크
BOJ 1238파티 (Dijkstra)-kokoa-lab
BOJ 13549숨바꼭질 3-kokoa-lab
BOJ 17835면접보는 승범이네-kokoa-lab

참고

이 글의 용어 (3개)
너비 우선 탐색 (BFS)algorithm
정의 너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (F…
다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…
벨만-포드 알고리즘 (Bellman-Ford Algorithm)algorithm
정의 벨만-포드 알고리즘 (Bellman-Ford Algorithm) 은 음수 가중치 간선을 허용하면서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 DP 기반 알고리…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기