๋ฐฑํธ๋ํน (Backtracking)
๋ฐฑํธ๋ํน (Backtracking)¶
๊ฐ์¶
๋ฐฑํธ๋ํน์ ํด๋ฅผ ์ฐพ๋ ๋์ค ๋งํ๋ฉด ๋๋์๊ฐ์ ๋ค์ ํด๋ฅผ ์ฐพ๋ ๊ธฐ๋ฒ์ ๋๋ค. ๊ฐ์ง์น๊ธฐ(pruning)๋ฅผ ํตํด ๋ถํ์ํ ํ์์ ์ค์ ๋๋ค.
๋ชฉ์ฐจ¶
1. ๋ฐฑํธ๋ํน ๊ฐ๋ ¶
๊ธฐ๋ณธ ์๋ฆฌ¶
๋ฐฑํธ๋ํน:
1. ํด๋ฅผ ํ๋์ฉ ๊ตฌ์ฑํด ๋๊ฐ
2. ์กฐ๊ฑด์ ๋ง์กฑํ์ง ์์ผ๋ฉด ์ด์ ๋จ๊ณ๋ก ๋๋์๊ฐ
3. ๊ฐ์ง์น๊ธฐ๋ก ํ์ ๊ณต๊ฐ ์ถ์
DFS + ์กฐ๊ฑด ๊ฒ์ฌ + ๋๋์๊ฐ๊ธฐ
์ํ ๊ณต๊ฐ ํธ๋ฆฌ¶
N=3์ผ ๋ ์์ด ํ์:
[]
/ | \
[1] [2] [3]
/ \ / \ / \
[1,2][1,3] [2,1][2,3] [3,1][3,2]
| | | | | |
[1,2,3][1,3,2][2,1,3][2,3,1][3,1,2][3,2,1]
๊ฐ์ง์น๊ธฐ ์: ์ฒซ ์์๊ฐ ์กฐ๊ฑด ์๋ฐ ์ ํด๋น ์๋ธํธ๋ฆฌ ์ ์ฒด ์คํต
๊ธฐ๋ณธ ํ ํ๋ฆฟ¶
def backtrack(candidate):
if is_solution(candidate):
output(candidate)
return
for next_choice in choices(candidate):
if is_valid(next_choice): # ๊ฐ์ง์น๊ธฐ
candidate.append(next_choice)
backtrack(candidate)
candidate.pop() # ๋๋๋ฆฌ๊ธฐ
2. ์์ด๊ณผ ์กฐํฉ¶
2.1 ์์ด (Permutation)¶
n๊ฐ ์ค r๊ฐ๋ฅผ ์์ ์๊ฒ ๋์ด
nPr = n! / (n-r)!
[1, 2, 3]์ ๋ชจ๋ ์์ด:
[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]
// C++
void permute(vector<int>& nums, int start, vector<vector<int>>& result) {
if (start == nums.size()) {
result.push_back(nums);
return;
}
for (int i = start; i < nums.size(); i++) {
swap(nums[start], nums[i]);
permute(nums, start + 1, result);
swap(nums[start], nums[i]); // ๋๋๋ฆฌ๊ธฐ
}
}
vector<vector<int>> permutations(vector<int>& nums) {
vector<vector<int>> result;
permute(nums, 0, result);
return result;
}
def permutations(nums):
result = []
def backtrack(start):
if start == len(nums):
result.append(nums[:])
return
for i in range(start, len(nums)):
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return result
# ๋๋ itertools
from itertools import permutations
list(permutations([1, 2, 3]))
2.2 ์กฐํฉ (Combination)¶
n๊ฐ ์ค r๊ฐ๋ฅผ ์์ ์์ด ์ ํ
nCr = n! / (r! ร (n-r)!)
[1, 2, 3, 4]์์ 2๊ฐ ์ ํ:
[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]
// C++
void combine(int n, int r, int start, vector<int>& current,
vector<vector<int>>& result) {
if (current.size() == r) {
result.push_back(current);
return;
}
for (int i = start; i <= n; i++) {
current.push_back(i);
combine(n, r, i + 1, current, result);
current.pop_back();
}
}
vector<vector<int>> combinations(int n, int r) {
vector<vector<int>> result;
vector<int> current;
combine(n, r, 1, current, result);
return result;
}
def combinations(n, r):
result = []
def backtrack(start, current):
if len(current) == r:
result.append(current[:])
return
for i in range(start, n + 1):
current.append(i)
backtrack(i + 1, current)
current.pop()
backtrack(1, [])
return result
# ๋๋ itertools
from itertools import combinations
list(combinations([1, 2, 3, 4], 2))
2.3 ์ค๋ณต ์์ด/์กฐํฉ¶
# ์ค๋ณต ์์ด: ๊ฐ์ ์์ ์ฌ๋ฌ ๋ฒ ์ ํ ๊ฐ๋ฅ
def permutations_with_repetition(nums, r):
result = []
def backtrack(current):
if len(current) == r:
result.append(current[:])
return
for num in nums:
current.append(num)
backtrack(current)
current.pop()
backtrack([])
return result
# ์ค๋ณต ์กฐํฉ
def combinations_with_repetition(nums, r):
result = []
def backtrack(start, current):
if len(current) == r:
result.append(current[:])
return
for i in range(start, len(nums)):
current.append(nums[i])
backtrack(i, current) # i+1์ด ์๋ i
current.pop()
backtrack(0, [])
return result
3. N-Queens¶
๋ฌธ์ ¶
NรN ์ฒด์คํ์ N๊ฐ์ ํธ์ ์๋ก ๊ณต๊ฒฉํ ์ ์๊ฒ ๋ฐฐ์น
ํธ์ ๊ณต๊ฒฉ ๋ฒ์: ๊ฐ๋ก, ์ธ๋ก, ๋๊ฐ์
4ร4 ์์ (ํ๋์ ํด):
. Q . .
. . . Q
Q . . .
. . Q .
์๊ณ ๋ฆฌ์ฆ¶
ํ ๋จ์๋ก ํธ ๋ฐฐ์น:
1. ์ฒซ ํ์ ํธ ๋ฐฐ์น ์๋
2. ๋ค์ ํ์ ํธ ๋ฐฐ์น (์ถฉ๋ ๊ฒ์ฌ)
3. ์ถฉ๋ํ๋ฉด ๋ฐฑํธ๋ํน
4. N๊ฐ ๋ฐฐ์น ์๋ฃํ๋ฉด ํด ์ถ๋ ฅ
์ถฉ๋ ๊ฒ์ฌ:
- ๊ฐ์ ์ด: cols[col] == True
- ๋๊ฐ์ 1 (โ): row - col ๊ฐ์ด ๊ฐ์
- ๋๊ฐ์ 2 (โ): row + col ๊ฐ์ด ๊ฐ์
๊ตฌํ¶
// C++
class NQueens {
private:
int n;
vector<bool> cols, diag1, diag2;
vector<vector<string>> results;
void backtrack(int row, vector<int>& queens) {
if (row == n) {
results.push_back(generateBoard(queens));
return;
}
for (int col = 0; col < n; col++) {
if (cols[col] || diag1[row - col + n - 1] || diag2[row + col])
continue;
queens[row] = col;
cols[col] = diag1[row - col + n - 1] = diag2[row + col] = true;
backtrack(row + 1, queens);
cols[col] = diag1[row - col + n - 1] = diag2[row + col] = false;
}
}
vector<string> generateBoard(const vector<int>& queens) {
vector<string> board(n, string(n, '.'));
for (int i = 0; i < n; i++) {
board[i][queens[i]] = 'Q';
}
return board;
}
public:
vector<vector<string>> solveNQueens(int n) {
this->n = n;
cols.assign(n, false);
diag1.assign(2 * n - 1, false);
diag2.assign(2 * n - 1, false);
vector<int> queens(n);
backtrack(0, queens);
return results;
}
};
def solve_n_queens(n):
results = []
cols = set()
diag1 = set() # row - col
diag2 = set() # row + col
def backtrack(row, queens):
if row == n:
board = ['.' * q + 'Q' + '.' * (n - q - 1) for q in queens]
results.append(board)
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
backtrack(row + 1, queens + [col])
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
backtrack(0, [])
return results
# ํด์ ๊ฐ์๋ง ์ธ๊ธฐ
def count_n_queens(n):
count = 0
cols = set()
diag1 = set()
diag2 = set()
def backtrack(row):
nonlocal count
if row == n:
count += 1
return
for col in range(n):
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
backtrack(row + 1)
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
backtrack(0)
return count
4. ๋ถ๋ถ์งํฉ¶
๋ชจ๋ ๋ถ๋ถ์งํฉ ์์ฑ¶
[1, 2, 3]์ ๋ถ๋ถ์งํฉ:
[], [1], [2], [3], [1,2], [1,3], [2,3], [1,2,3]
์ด 2^n๊ฐ
// C++
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> current;
function<void(int)> backtrack = [&](int start) {
result.push_back(current);
for (int i = start; i < nums.size(); i++) {
current.push_back(nums[i]);
backtrack(i + 1);
current.pop_back();
}
};
backtrack(0);
return result;
}
// ๋นํธ๋ง์คํฌ ๋ฐฉ๋ฒ
vector<vector<int>> subsetsBitmask(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> result;
for (int mask = 0; mask < (1 << n); mask++) {
vector<int> subset;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) {
subset.push_back(nums[i]);
}
}
result.push_back(subset);
}
return result;
}
def subsets(nums):
result = []
def backtrack(start, current):
result.append(current[:])
for i in range(start, len(nums)):
current.append(nums[i])
backtrack(i + 1, current)
current.pop()
backtrack(0, [])
return result
# ๋นํธ๋ง์คํฌ
def subsets_bitmask(nums):
n = len(nums)
result = []
for mask in range(1 << n):
subset = [nums[i] for i in range(n) if mask & (1 << i)]
result.append(subset)
return result
ํฉ์ด target์ธ ๋ถ๋ถ์งํฉ¶
def subset_sum(nums, target):
result = []
def backtrack(start, current, current_sum):
if current_sum == target:
result.append(current[:])
return
if current_sum > target: # ๊ฐ์ง์น๊ธฐ
return
for i in range(start, len(nums)):
current.append(nums[i])
backtrack(i + 1, current, current_sum + nums[i])
current.pop()
backtrack(0, [], 0)
return result
5. ์ค๋์ฟ ¶
๋ฌธ์ ¶
9ร9 ๊ฒฉ์, ๊ฐ ํ/์ด/3ร3 ๋ฐ์ค์ 1-9๊ฐ ํ ๋ฒ์ฉ
5 3 . | . 7 . | . . .
6 . . | 1 9 5 | . . .
. 9 8 | . . . | . 6 .
------+-------+------
8 . . | . 6 . | . . 3
4 . . | 8 . 3 | . . 1
7 . . | . 2 . | . . 6
------+-------+------
. 6 . | . . . | 2 8 .
. . . | 4 1 9 | . . 5
. . . | . 8 . | . 7 9
๊ตฌํ¶
def solve_sudoku(board):
def is_valid(board, row, col, num):
# ํ ๊ฒ์ฌ
if num in board[row]:
return False
# ์ด ๊ฒ์ฌ
for r in range(9):
if board[r][col] == num:
return False
# 3ร3 ๋ฐ์ค ๊ฒ์ฌ
box_row, box_col = 3 * (row // 3), 3 * (col // 3)
for r in range(box_row, box_row + 3):
for c in range(box_col, box_col + 3):
if board[r][c] == num:
return False
return True
def solve():
for row in range(9):
for col in range(9):
if board[row][col] == '.':
for num in '123456789':
if is_valid(board, row, col, num):
board[row][col] = num
if solve():
return True
board[row][col] = '.' # ๋ฐฑํธ๋ํน
return False # ๋ชจ๋ ์ซ์ ์คํจ
return True # ๋น ์นธ ์์ = ์๋ฃ
solve()
6. ์ฐ์ต ๋ฌธ์ ¶
๋ฌธ์ 1: ๋ฌธ์์ด์ ๋ชจ๋ ์์ด¶
์ค๋ณต ๋ฌธ์๊ฐ ์์ ๋ ์ค๋ณต ์์ด ์์ด ์์ฑ
์ ๋ต ์ฝ๋
def permute_unique(nums):
result = []
nums.sort()
def backtrack(current, remaining):
if not remaining:
result.append(current[:])
return
for i in range(len(remaining)):
# ์ค๋ณต ์คํต
if i > 0 and remaining[i] == remaining[i-1]:
continue
backtrack(current + [remaining[i]],
remaining[:i] + remaining[i+1:])
backtrack([], nums)
return result
์ถ์ฒ ๋ฌธ์ ¶
| ๋์ด๋ | ๋ฌธ์ | ํ๋ซํผ | ์ ํ |
|---|---|---|---|
| โญโญ | N๊ณผ M | ๋ฐฑ์ค | ์์ด |
| โญโญ | N-Queens | ๋ฐฑ์ค | N-Queens |
| โญโญ | Subsets | LeetCode | ๋ถ๋ถ์งํฉ |
| โญโญโญ | Sudoku Solver | LeetCode | ์ค๋์ฟ |
| โญโญโญ | Combination Sum | LeetCode | ์กฐํฉ |
๋ฐฑํธ๋ํน ํ ํ๋ฆฟ¶
def backtrack(state):
if is_goal(state):
save_solution(state)
return
for choice in get_choices(state):
if is_valid(choice, state):
make_choice(state, choice)
backtrack(state)
undo_choice(state, choice)
๋ค์ ๋จ๊ณ¶
- 09_Trees_and_BST.md - ํธ๋ฆฌ, BST
์ฐธ๊ณ ์๋ฃ¶
- Backtracking
- Introduction to Algorithms (CLRS) - Backtracking