Algorithm Design Strategies (알고리즘 설계 전략)

"알고리즘을 설계할 때 완전 탐색, 백트래킹, 분할 정복, 동적 계획법(DP), 그리디를 어떤 조건으로 고르고, 서로 어떻게 이어지는가?" 이 페이지는 그 질문에 답한다. 원문 hub 노트는 백트래킹·완전 탐색·분할 정복을 탐색 기법으로, DP를 최적화로 분류한다.[1] 원문 노트들은 서로를 비교한다. 백트래킹 노트는 완전 탐색과 비교하고 자신을 DFS의 최적화 형태로 보며, DP 노트는 그리디와 비교한다.[2][3][4] 이 페이지는 다섯 전략을 그 비교 관계를 따라 차례로 놓고, 마지막에 고르는 기준을 한 표로 모은다.

백트래킹 (Backtracking)

항목 완전 탐색 백트래킹
탐색 범위 모든 경우의 수 탐색 유망하지 않은 경로 조기 차단
시간 복잡도 더 큼 실질적으로 훨씬 작음
구현 방식 완전 나열 DFS + 가지치기
루트
├─ 선택 1
│   ├─ 선택 1-1  ← [유망] 계속 탐색
│   │   └─ 선택 1-1-1  ← [해 발견]
│   └─ 선택 1-2  ← [비유망] 가지치기(Prune) → return
└─ 선택 2
    ├─ 선택 2-1  ← [비유망] 가지치기 → return
    └─ 선택 2-2  ← [유망] 계속 탐색
boolean[] visited = new boolean[N + 1];

void backtrack(int depth, ...) {
    // 종료 조건 (해를 찾은 경우)
    if (depth == N) {
        // 결과 처리
        return;
    }

    for (int i = 1; i <= N; i++) {
        // 가지치기: 이미 방문했거나 제약 조건 위반
        if (visited[i] || !isValid(i)) continue;

        // 선택
        visited[i] = true;

        // 재귀 탐색
        backtrack(depth + 1, ...);

        // 선택 취소 (복원)
        visited[i] = false;
    }
}

분할 정복 (Divide and Conquer)

flowchart TD
    P["문제 (크기 n)"] -->|분할| S1["부분 문제 1 (크기 n/2)"]
    P -->|분할| S2["부분 문제 2 (크기 n/2)"]
    S1 -->|정복| A1["부분 문제 1의 해"]
    S2 -->|정복| A2["부분 문제 2의 해"]
    A1 -->|결합| R["전체 문제의 해"]
    A2 -->|결합| R
Iterative_Power(x, n)
	result <- 1
	
	FOR i in 1 -> n
		result <- result * x
	
	RETURN result
Recursive_Power(x, n)
	IF n  == 1 : RETURN x
	IF n is even
		y <- Recursive_Power(x, n/2);
		RETURN y*y
	ELSE
		y <- Recursive_Power(x, (n-1)/2)
		RETURN y*y*x

동적 계획법 (Dynamic Programming)

int[] memo = new int[N + 1];
Arrays.fill(memo, -1);

int fib(int n) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n]; // 캐시 히트
    return memo[n] = fib(n - 1) + fib(n - 2);
}
int[] dp = new int[N + 1];
dp[0] = 0; dp[1] = 1;
for (int i = 2; i <= N; i++)
    dp[i] = dp[i - 1] + dp[i - 2]; // 점화식
항목 Memoization Tabulation
방식 재귀 (Top-down) 반복문 (Bottom-up)
계산 범위 필요한 부분 문제만 모든 부분 문제
스택 오버플로 위험 있음 없음
실전 선호 점화식 파악 전 점화식 확정 후
// 예: 계단 오르기 (dp[i] = i번째 계단까지의 최대 점수)
dp[i] = Math.max(dp[i-1] + stair[i], dp[i-2] + stair[i]);
// 예: 배낭 문제 (dp[i][w] = i번째 물건까지 무게 w 이하일 때 최대 가치)
if (weight[i] <= w)
    dp[i][w] = Math.max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i]);
else
    dp[i][w] = dp[i-1][w];

그리디와 DP

항목 그리디 DP
선택 방식 현재 최선을 탐욕적으로 선택 모든 경우를 저장하며 최적 탐색
적용 조건 탐욕 선택 속성 + 최적 부분 구조 최적 부분 구조 + 중복 부분 문제
속도 일반적으로 빠름 상대적으로 느림
정확성 조건 미충족 시 오답 위험 조건 충족 시 항상 최적해 보장

고르는 기준과 혼동 지점

전략 이런 조건이면 고른다
완전 탐색 경우의 수가 상대적으로 작다[6:3]
백트래킹 제약 조건으로 유망하지 않은 경로를 일찍 끊을 수 있다[9:2]
분할 정복 문제를 작은 부분으로 나눠 각각 풀고, 필요하면 해답을 모을 수 있다[14:1]
동적 계획법 중복 부분 문제와 최적 부분 구조가 함께 있다[19:1]
그리디 탐욕 선택 속성과 최적 부분 구조가 있다[4:4]

관련

테스트 질문

출처


  1. _Algorithm.md 27-35행 — 분류 제목 "탐색 기법", "최적화"와 항목 설명 "DFS + 가지치기, 상태 공간 트리", "브루트포스", "Divide & Conquer", "중복 부분 문제 + 최적 부분 구조" ↩︎

  2. Backtracking.md 16-22행 — "| 탐색 범위 | 모든 경우의 수 탐색 | 유망하지 않은 경로 조기 차단 |", "| 시간 복잡도 | 더 큼 | 실질적으로 훨씬 작음 |", "| 구현 방식 | 완전 나열 | DFS + 가지치기 |". 원문 표 머리의 완전 탐색 칸에 있던 노트 링크는 뺐다. ↩︎ ↩︎

  3. Backtracking.md 11-14행 — "상태 공간 트리(State Space Tree)", "현재 경로가 해가 될 수 없다고 판단되면 이전 단계로 되돌아가(Backtrack) 다른 경로를 탐색하는 기법.", "깊이 우선 탐색(DFS)의 최적화 형태." ↩︎ ↩︎

  4. Dynamic Programming.md 82-91행 — "| 선택 방식 | 현재 최선을 탐욕적으로 선택 | 모든 경우를 저장하며 최적 탐색 |", "| 적용 조건 | 탐욕 선택 속성 + 최적 부분 구조 | 최적 부분 구조 + 중복 부분 문제 |", "| 정확성 | 조건 미충족 시 오답 위험 | 조건 충족 시 항상 최적해 보장 |", "그리디로 해결 가능하면 그리디 우선. 불확실하면 DP." ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  5. Exhaustive Search.md 6-10행 — 제목 "완전검색", "완전 검색 방법은 문제의 해법으로 생각할 수 있는 모든 경우의 수를 나열해보고 확인하는 기법이다.", "Brute-force 혹은 generate-and-test 기법이라고도 부른다.", "force의 의미는 사람보다는 컴퓨터의 force를 의미한다". 9행 구호 "just do it"은 옮기지 않았다. ↩︎ ↩︎

  6. Exhaustive Search.md 11-14행 — "모든 경우의 수를 테스트한 후, 최종 해법을 도출한다.", "상대적으로 빠른 시간에 문제 해결(알고리즘 설계)을 할 수 있다.", "일반적으로 경우의 수가 상대적으로 작을 때 유용하다.", "모든 경우의 수를 생성하고 테스트하기 때문에 수행속도는 느리지만, 해답을 찾아내지 못할 확률이 작다." ↩︎ ↩︎ ↩︎ ↩︎

  7. Exhaustive Search.md 15행 — "우선 완전 검색으로 접근하여 해답을 도출한 후, 성능 개선을 위해 다른 알고리즘을 사용하고 해답을 확인하는 것이 바람직하다." ↩︎

  8. 구현 유형 접근법.md 19-26행 — "시간복잡도 기준 (1억 연산 ≈ 1초)", "| N 범위 | 허용 복잡도 |". 이 표를 완전 탐색의 판단 기준으로 쓰는 연결은 원문에 없다. ↩︎

  9. Backtracking.md 24-29행 — "탐색의 모든 경우의 수를 확인하되, 유망성을 검증하여 불필요한 탐색을 차단해야 한다.", "현재 상태에서 해를 찾을 가능성이 있음.", "제약 조건을 이미 위반했거나 최적해가 될 수 없음 → 즉시 return." ↩︎ ↩︎ ↩︎

  10. Backtracking.md 31-47행 — 텍스트 트리 도식과 "재귀 함수 호출 시 조건문으로 현재 상태가 제약 조건을 위반하는지 검사.", "하위 트리 전체를 탐색하지 않고". 47행은 이어서 "시간 복잡도를 기하급수적으로 감소"라고 쓰지만, 같은 노트 93-94행은 최악의 경우 완전 탐색과 같다고 한다. 두 서술이 부딪혀 이 페이지는 93-94행의 신중한 표현만 썼다. ↩︎

  11. Backtracking.md 51-75행 Java 틀 — "// 종료 조건 (해를 찾은 경우)", "// 가지치기: 이미 방문했거나 제약 조건 위반", "// 재귀 탐색", "// 선택 취소 (복원)" ↩︎

  12. Backtracking.md 79-89행 — "N개의 원소 중 R개를 순서 있게 선택", "visited 배열로 중복 선택 방지", "현재까지의 합이 목표값을 초과하면 가지치기", "if (currentSum > target) return;", "체스판에 N개의 퀸을 서로 공격 불가하도록 배치", "열, 대각선 충돌 여부를 가지치기 조건으로 활용" ↩︎

  13. Backtracking.md 93-94행 — "가지치기 효율에 따라 크게 달라짐.", "최악의 경우 완전 탐색과 동일하지만, 실전에서는 탐색 공간을 대폭 줄임." ↩︎

  14. 분할정복.md 13-16행 — "분할(Divide) : 해결할 문제를 여러 개의 작은 부분으로 나눈다.", "정복(Conquer) : 나눈 작은 문제를 각각 해결한다", "통합(Combine) : (필요하다면) 해결된 해답을 모은다." ↩︎ ↩︎ ↩︎

  15. 분할정복.md 17행이 embed한 그림 Raw/media/Knowledge/Computing/Pasted image 20260212095053.png — 2026-10-06에 육안으로 확인했다. 그림 속 글자는 'Top-down approach', '분할(2분할의 경우)', '문제의 크기 n', '크기 n/2인 부분문제1', '크기 n/2인 부분문제2', '정복', '부분 문제1의 해', '부분 문제2의 해', '결합', '전체 문제의 해'다. 이미지는 page에 embed하지 않고 Mermaid로 다시 그렸다. ↩︎ ↩︎

  16. 분할정복.md 20-29행 — 제목 "반복(Iterative) 알고리즘: O(N)"과 의사코드 "result <- result * x" ↩︎

  17. 분할정복.md 31-41행 — 제목 "분할 정복 기반의 알고리즘: O(logN)"과 의사코드 "IF n == 1 : RETURN x", "y <- Recursive_Power(x, n/2);", "RETURN yyx". 21·32행의 코드 블록 언어 표시는 pascal이다. ↩︎ ↩︎

  18. Dynamic Programming.md 11-12행 — "복잡한 큰 문제를 작은 하위 문제(Sub-problem)로 나누어 해결하고,", "그 결과를 저장(메모이제이션)해 재사용하여", "중복 연산을 방지" ↩︎ ↩︎

  19. Dynamic Programming.md 16-24행 — "동일한 작은 문제들이 반복해서 나타남.", "(예: 피보나치에서 fib(3)이 여러 번 호출됨)", "부분 문제의 최적해를 조합해 전체 문제의 최적해를 구성할 수 있음.", "(예: 최단 경로의 부분 경로도 최단 경로)", "두 조건이 충족될 때 DP 적용 가능." ↩︎ ↩︎ ↩︎

  20. Dynamic Programming.md 28-57행 — "재귀 + 결과 캐싱. 필요한 부분 문제만 계산.", "반복문 + 테이블 채우기. 작은 문제부터 순서대로 계산.", 코드 두 개, "| 스택 오버플로 위험 | 있음 | 없음 |", "| 실전 선호 | 점화식 파악 전 | 점화식 확정 후 |" ↩︎ ↩︎

  21. Dynamic Programming.md 61-63행 — "가 무엇을 의미하는지 정의", "이전 상태로부터 현재 상태를 유도하는 관계식 작성", "Base case (재귀의 종료 조건) 결정" ↩︎

  22. Dynamic Programming.md 65-80행 — "1D DP (단일 인덱스)", "// 예: 계단 오르기 (dp[i] = i번째 계단까지의 최대 점수)", "dp[i] = Math.max(dp[i-1] + stair[i], dp[i-2] + stair[i]);", "2D DP (두 인덱스)", "// 예: 배낭 문제 (dp[i][w] = i번째 물건까지 무게 w 이하일 때 최대 가치)". 문제 조건은 원문에 없다. ↩︎

  23. Dijkstra.md 11행 — "그리디(Greedy)"; Bellman-Ford.md 66행 — "DP 방식 반복"; Floyd-Warshall.md 11행 — "사이의 최단 경로를 구하는 DP 알고리즘." ↩︎