λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ’» μ½”λ”©ν…ŒμŠ€νŠΈ/μ•Œκ³ λ¦¬μ¦˜, 자료ꡬ쑰

[μ•Œκ³ λ¦¬μ¦˜] 완전탐색 μ•Œκ³ λ¦¬μ¦˜

by λ½€μ§œκΌ¬ 2025. 5. 1.
728x90
λ°˜μ‘ν˜•

완전탐색(Brute Force)μ΄λž€? 

κ°€λŠ₯ν•œ λͺ¨λ“  경우λ₯Ό μ „λΆ€ νƒμƒ‰ν•΄μ„œ 정닡을 μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜ 기법

  • “이게 정닡일 μˆ˜λ„ μžˆμ§€ μ•Šμ„κΉŒ?” → λͺ¨λ“  경우λ₯Ό λ‹€ ν•΄λ³΄λŠ” 방식
  • 정닡을 보μž₯ν•˜μ§€λ§Œ, 느릴 수 있음
  • 데이터 양이 μž‘μ„ λ•Œ κ°•λ ₯ν•œ μ „λž΅
ν•­λͺ© μ„€λͺ…
πŸ’‘ μž₯점 κ΅¬ν˜„ 간단, μ •λ‹΅ 보μž₯
🐒 단점 μ‹œκ°„ 였래 걸릴 수 있음
βœ… μ“Έλ§Œν•œ 경우 경우의 μˆ˜κ°€ μž‘μ„ λ•Œ (ex. N ≤ 10⁴ μ΄ν•˜)
❌ λΉ„μΆ” 상황 경우의 μˆ˜κ°€ λ„ˆλ¬΄ λ§Žμ„ λ•Œ (N! λ˜λŠ” 2^N 이상)

 

λŒ€ν‘œ 문제 μœ ν˜•

μœ ν˜• μ„€λͺ…
λͺ¨λ“  μ‘°ν•© μ‹œλ„ ex. 둜또 번호, 숫자 자릿수 μ‘°ν•©
λͺ¨λ“  μˆœμ—΄ μ‹œλ„ ex. μ™ΈνŒμ› 순회(TSP), λ‚˜μ΄νŠΈ 이동
λΆ€λΆ„μ§‘ν•© 탐색 ex. λΆ€λΆ„ν•©, λ°°λ‚­λ¬Έμ œ (μž‘μ€ μž…λ ₯일 λ•Œ)
λ°±νŠΈλž˜ν‚Ή 완전탐색에 κ°€μ§€μΉ˜κΈ°λ₯Ό λ”ν•œ μ§„ν™” 버전

 

μ™„μ „ νƒμƒ‰μ˜ κΈ°λ³Έ κ°œλ…μ€ "μ „λΆ€ 해보기"μ΄λ‚˜, μ—¬λŸ¬ μ’…λ₯˜μ˜ 탐색방법이 μžˆλ‹€.


μ™„μ „νƒμƒ‰μ˜ λŒ€ν‘œμ μΈ μ’…λ₯˜

: 크게 5κ°€μ§€λ‘œ 보자면 λ‹¨μˆœ BruteForse, μˆœμ—΄, μž¬κ·€ν˜ΈμΆœ, λΉ„νŠΈλ§ˆμŠ€ν¬, BFS/DFS κ°€ μžˆλ‹€.

μ•„λž˜μ˜ ν‘œλŠ” κ·Έκ±Έ 더 μ„Έμ„Έν•˜κ²Œ λ‚˜λˆ λ³Έκ²ƒ.

μœ ν˜• 핡심 ν‚€μ›Œλ“œ μ‚¬μš© 도ꡬ/μ˜ˆμ‹œ μ‹œκ°„λ³΅μž‘λ„
1. μˆœμ—΄(permutation) μˆœμ„œ μ€‘μš”, 쀑볡 ❌ itertools.permutations() O(N!) (보톡 N ≤ 8 μ΄ν•˜)
2. μ‘°ν•©(combination) μˆœμ„œ μƒκ΄€μ—†μŒ, 쀑볡 ❌ itertools.combinations() O(NCr) ≈ O(2ⁿ)
3. 쀑볡 μˆœμ—΄(product) μˆœμ„œ μ€‘μš”, 쀑볡 ν—ˆμš© itertools.product() O(N^R)
4. λΆ€λΆ„μ§‘ν•© 탐색(subset)
- λΉ„νŠΈλ§ˆμŠ€ν¬
μ„ νƒν•˜κ±°λ‚˜ λ§κ±°λ‚˜ λΉ„νŠΈλ§ˆμŠ€ν¬, 1 << N O(2ⁿ)
5. 일반 μˆ˜μ—΄/λ²”μœ„ 완전탐색
(λ‹¨μˆœ Brute Force)
λͺ¨λ“  수λ₯Ό λ‹€ λŒλ €λ΄„ range(), 쀑첩 forλ¬Έ O(N), O(N²), O(N³) … 상황에 따라
6. BFS / DFS 완전탐색 트리, κ·Έλž˜ν”„, λ§΅ λ“± ꡬ쑰 탐색 deque(BFS), μž¬κ·€/μŠ€νƒ(DFS), visited O(V + E), O(Nα΄°)
7. μž¬κ·€ 호좜 기반 완전탐색 깊이 μš°μ„ μœΌλ‘œ 직접 λͺ¨λ“  경우 생성 dfs(depth) + λ°±νŠΈλž˜ν‚Ή (append/pop) 보톡 O(N!) λ˜λŠ” O(2ⁿ)

 

* μ‹œκ°„ λ³΅μž‘λ„ 감 μž‘λŠ” κΈ°μ€€

: N이 μž‘μœΌλ©΄ (N ≤ 10~15): 완전탐색 μ‚¬μš© κ°€λŠ₯  / N이 크면 (N ≥ 20~30): λ°±νŠΈλž˜ν‚Ή or DPκ°€ ν•„μš”ν•¨

μš°μ„  μ™„μ „ 탐색뢀터 μ•Œμ•„λ³΄κ³  μ•„λž˜μ—μ„œ λ°±νŠΈλž˜ν‚Ήμ„ 보자.

1. μˆœμ—΄ (Permutation)

μ£Όμ–΄μ§„ n개의 μ›μ†Œ μ€‘μ—μ„œ r개λ₯Ό 뽑아 μˆœμ„œ 있게 λ‚˜μ—΄

from itertools import permutations

for p in permutations([1, 2, 3], 2):
    print(p)  # (1,2), (1,3), (2,1), (2,3), ...

2. μ‘°ν•© (Combination)

μˆœμ„œλŠ” μ‹ κ²½ μ•ˆ μ“°κ³ , r개λ₯Ό λ½‘κΈ°λ§Œ ν•˜λ©΄ 됨

from itertools import combinations

for c in combinations([1, 2, 3], 2):
    print(c)  # (1,2), (1,3), (2,3)

 

μˆœμ„œ 상관 없이 2개만 λ½‘λŠ”λ‹€. (볡ꢌ, νŒ€ λ‚˜λˆ„κΈ°, 삼총사 문제 λ“±)

 

3. 쀑볡 μˆœμ—΄ (Product)

쀑볡 ν—ˆμš©, λͺ¨λ“  경우의 수λ₯Ό λ‚˜μ—΄

from itertools import product

for p in product([0, 1], repeat=3):
    print(p)  # (0,0,0), (0,0,1), ..., (1,1,1)

 

자릿수 μ‘°ν•©, λΉ„λ°€λ²ˆν˜Έ λ§Œλ“€κΈ°, μƒμ„±μž 문제 λ“±

4. λΆ€λΆ„μ§‘ν•© 탐색 (Subset) - λΉ„νŠΈλ§ˆμŠ€ν¬ 탐색

n개의 μ›μ†Œμ— λŒ€ν•΄ 포함할지 말지(선택할지 말지)λ₯Ό 2^n 경우둜 λ‚˜λˆ”

arr = [1, 2, 3]
n = len(arr)
for i in range(1 << n):  # 2^n 번 순회
    subset = []
    for j in range(n):
        if i & (1 << j):
            subset.append(arr[j])
    print(subset)

 

λΆ€λΆ„ν•© 문제, νŠΉμ • 쑰건을 λ§Œμ‘±ν•˜λŠ” μ‘°ν•©

" 1 << n " λΉ„νŠΈ μ—°μ‚°μžλ‘œ, 1을 μ™Όμͺ½μœΌλ‘œ nλΉ„νŠΈλ§ŒνΌ μ΄λ™ν•œλ‹€λŠ” 뜻.

1 << 3  은  8이닀.
# μ™œ? μ΄μ§„μˆ˜ 1 →  (3μΉΈ λΉ„νŠΈ 이동)→ μ΄μ§„μˆ˜ 1000 => (2^3 = 8)

 

 

κ·Έλž˜μ„œ 즉, range(1 << n)은 0λΆ€ν„° 2ⁿ - 1κΉŒμ§€λΌλŠ” 뜻이 λœλ‹€.

 

n개의 μ›μ†Œκ°€ 있으면 λΆ€λΆ„μ§‘ν•©μ˜ κ°œμˆ˜λŠ” 2ⁿ κ°œμ΄λ―€λ‘œ, λͺ¨λ“  뢀뢄집합을 μˆœνšŒν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.

 

5. 일반적인 수치 완전탐색

숫자 λ²”μœ„λ₯Ό μ§€μ •ν•˜κ³  λͺ¨λ“  경우의 수λ₯Ό λ‹€ 돌림

for i in range(1000):
    for j in range(1000):
        if i * j == 123456:
            print(i, j)

그런데 μ•„κΉŒ μœ„μ—μ„œ,

* μ‹œκ°„ λ³΅μž‘λ„ 감 μž‘λŠ” κΈ°μ€€

: N이 μž‘μœΌλ©΄ (N ≤ 10~15): 완전탐색 μ‚¬μš© κ°€λŠ₯  / N이 크면 (N ≥ 20~30): λ°±νŠΈλž˜ν‚Ή or DPκ°€ ν•„μš”ν•¨

μš°μ„  μ™„μ „ 탐색뢀터 μ•Œμ•„λ³΄κ³  μ•„λž˜μ—μ„œ λ°±νŠΈλž˜ν‚Ήμ„ 보자.

라고 λ§ν–ˆμ—ˆλ‹€.

λ°±νŠΈλž˜ν‚Ή(Backtracking)μ΄λž€?

ν•΄κ²°μ±…μœΌλ‘œ κ°€λŠ” 도쀑에 λ§‰νžˆκ²Œ 되면, κ·Έ μ§€μ μœΌλ‘œ λ‹€μ‹œ λŒμ•„κ°€μ„œ λ‹€λ₯Έ 경둜λ₯Ό 탐색.

λͺ¨λ“  κ°€λŠ₯ν•œ 경우의 수λ₯Ό νƒμƒ‰ν•˜μ§€λ§Œ, λ§‰νžˆλ©΄ λŒμ•„κ°€λ―€λ‘œ μ‹œκ°„ μ ˆμ•½λ¨.

완전탐색 + κ°€μ§€μΉ˜κΈ°(pruning) 기법
= λΆˆν•„μš”ν•œ 탐색은 일찍 μ€‘λ‹¨ν•΄μ„œ μ‹œκ°„ μ ˆμ•½!

 

N자리 숫자 λ§Œλ“€κΈ°μ˜ μ˜ˆμ‹œλ₯Ό 듀어보면,

def dfs(depth, path):
    if depth == N:
        print(path)
        return
    for i in range(10):
        if i in path:  # 쀑볡 제거 (κ°€μ§€μΉ˜κΈ°!)
            continue
        dfs(depth + 1, path + [i])

 

- depth == N이면 λλ‚˜κ³ ,

- i in path인 κ²½μš°λŠ” λ³Ό ν•„μš” μ—†μœΌλ‹ˆκΉŒ λ„˜μ–΄κ°„λ‹€.

 

βœ… 보톡 DFSλ₯Ό 기반으둜 κ΅¬ν˜„ν•œλ‹€. ν•œ 경둜λ₯Ό λκΉŒμ§€ 파고 λ“€μ–΄κ°”λ‹€κ°€, 쑰건에 따라 λ‹€μ‹œ λŒμ•„μ˜¬ 수 μžˆμœΌλ―€λ‘œ(μž¬κ·€)

def backtrack(path):
    if 쑰건 만쑱:
        μ •λ‹΅ μ €μž₯ or 좜λ ₯
        return
    for 선택 in 후보ꡰ:
        if 쑰건에 μ•ˆ 맞으면 continue  # κ°€μ§€μΉ˜κΈ°
        path.append(선택)
        backtrack(path)
        path.pop()  # 원상볡ꡬ

 

append → μž¬κ·€ → pop

728x90
λ°˜μ‘ν˜•