4x4
로 이루어진 체스판에 네 개의 체스 퀸을 놓을 수 있는 위치를
마디로 표현한 상태 공간 나무상태 공간 나무의 뿌리로부터 깊이우선 탐색(DFS) 실행.
탐색 과정에서 유망하지 않은 마디를 만나면 가지치기 실행 후 부모 마디로 되돌아감(되추적, backtracking).
이후 다른 형제자매 마디를 대상으로 깊이우선 탐색 반복.
더 이상의 형제자매 마디가 없으면 형제자매가 있는 조상까지 되추적 실행.
탐색이 더 이상 진행할 수 없는 경우 알고리즘 종료
문제: n 개의 퀸(queen)을 서로 상대방을 위협하지 않도록 n x n 체스판에 위치시키기
변수: n 개의 퀸
도메인: {1, 2, ..., n}
조건: 두 개의 퀸이 하나의 행, 열, 또는 대각선 상에 위치하지 않음.
두 개의 퀸 $q_1, q_2$가 같은 대각선 상에 위치하려면 행과 열의 차이의 절댓값이 동일해야 함. (아래 그림 참조)
$$ \text{abs}(q_{1,r} - q_{2,r}) = \text{abs}(q_{1,c} - q_{2,c}) $$
단, $(q_{1,r}, q_{1,c})$ 와 $(q_{2,r}, q_{2,c})$ 는 각각 $q_1$과 $q_2$가 위치한 행과 열의 좌표를 가리킴.
from typing import List, Dict
# 변수: 네 개의 퀸의 번호, 즉, 1, 2, 3, 4
variables = [1, 2, 3, 4]
# 도메인: 각각의 퀸이 자리잡을 수 있는 가능한 모든 열의 위치.
domains: Dict[int, List[int]] = {}
columns = [1, 2, 3, 4]
for var in variables:
domains[var] = columns
domains
{1: [1, 2, 3, 4], 2: [1, 2, 3, 4], 3: [1, 2, 3, 4], 4: [1, 2, 3, 4]}
backtracking_search_queens()
는 일반적인 n-퀸 문제를 해결함.assignment
인자: 되추적 과정에서 일부의 변수에 대해 할당된 도메인 값의 정보를 담은 사전을 가리킴.assignment
가 확장되며 모든 변수에 대해 도메인 값이 지정될 때가지 재귀적으로
알고리즘이 진행됨.def backtracking_search_queens(assignment: Dict[int, int] = {}):
"""assignment: 각각의 변수를 키로 사용하고 키값은 해당 변수에 할당될 값"""
# 모든 변수에 대한 값이 지정된 경우 조건을 만족시키는 해가 완성된 것임
if len(assignment) == len(variables):
return assignment
# 아직 값을 갖지 않은 변수들이 존재하면 되추적 알고리즘을 아직 할당되지 않은 값을 대상으로 이어서 진행
unassigned = [v for v in variables if v not in assignment]
first = unassigned[0]
for value in domains[first]:
# 주의: 기존의 assignment를 보호하기 위해 복사본 활용
# 되추적이 발생할 때 이전 할당값을 기억해 두기 위해서임.
local_assignment = assignment.copy()
local_assignment[first] = value
# local_assignment 값이 유망하면 재귀 호출을 사용하여 변수 할당 이어감.
if promissing_queens(first, local_assignment):
result = backtracking_search_queens(local_assignment)
# 유망성을 이어가지 못하면 되추적 실행
if result is not None:
return result
return None
def promissing_queens(variable: int, assignment: Dict[int, int]):
"""새로운 변수 variable에 값을 할당 하면서 해당 변수와 연관된 변수들 사이의 제약조건이
assignment에 대해 만족되는지 여부 확인
n-퀸 문제의 경우: 제약조건이 모든 변수에 대해 일정함.
즉, 새로 위치시켜야 하는 퀸이 기존에 이미 자리잡은 퀸들 중 하나와
동일 행, 열, 대각산 상에 위치하는지 여부를 확인함"""
# q1r, q1c: 첫째 퀸이 놓인 마디의 열과 행
for q1r, q1c in assignment.items():
# q2r = 첫째 퀸 아래에 위치한 다른 모든 퀸들을 대상으로 조건만족여부 확인
for q2r in range(q1r + 1, len(assignment) + 1):
q2c = assignment[q2r] # 둘째 퀸의 열
if q1c == q2c: # 동일 열에 위치?
return False
if abs(q1r - q2r) == abs(q1c - q2c): # 대각선상에 위치?
return False
# 모든 변수에 대해 제약조건 만족됨
return True
backtracking_search_queens()
{1: 2, 2: 4, 3: 1, 4: 3}
n 개의 퀸이 주어졌을 때 상태공간트리의 마디의 수는 다음과 같음.
$$ 1 + n + n^2 + n^3 + \cdots + n^n = \frac{n^{n+1}-1}{n-1} $$
copy()
메서드는 얕은 복사 용도로 사용된.# 얕은 복사
aList = [1, 2, 3, 4]
bList = aList
cList = aList.copy()
aList[0] = 10
print("얕은 복사:", aList[0] == cList[0])
dList = [[5, 6], [7, 8]]
eList = dList.copy()
dList[0][1] = 60
print("얕은 복사:", dList[0] == eList[0])
얕은 복사: False 얕은 복사: True
copy
모듈의 deepcopy()
함수 활용copy()
함수: 얕은 복사. 리스트의 copy()
메서드와 동일하게 작동.deepcopy()
함수: 깊은 복사.# 얕은 복사 vs. 얕은 복사
from copy import copy, deepcopy
aList = [1, 2, 3, 4]
bList = aList
cList = copy(aList)
aList[0] = 10
print("얕은 복사:", aList[0] == cList[0])
dList = [[5, 6], [7, 8]]
eList = deepcopy(dList)
dList[0][1] = 60
print("깊은 복사:", dList[0] == eList[0])
얕은 복사: False 깊은 복사: False
colors = [빨강, 파랑, 갈색]
= [1 , 2 , 3 ]
from typing import List, Dict
# 변수: 네 마디의 번호, 즉, 1, 2, 3, 4
variables = [1, 2, 3, 4]
# 도메인: 각각의 마디에 칠할 수 있는 가능한 모든 색상
# 3-색칠하기: 1(빨강), 2(파랑), 3(갈색)
domains: Dict[int, List[int]] = {}
columns = [1, 2, 3]
for var in variables:
domains[var] = columns
domains
{1: [1, 2, 3], 2: [1, 2, 3], 3: [1, 2, 3], 4: [1, 2, 3]}
backtracking_search_colors()
는 일반적인 m-색칠하기 문제를 해결함.assignment
인자: 되추적 과정에서 일부의 변수에 대해 할당된 도메인 값의 정보를 담은 사전을 가리킴.assignment
가 확장되며 모든 변수에 대해 도메인 값이 지정될 때가지 재귀적으로
알고리즘이 진행됨.def backtracking_search_colors(assignment: Dict[int, int] = {}):
"""assignment: 각각의 변수를 키로 사용하고 키값은 해당 변수에 할당될 값"""
# 모든 변수에 대한 값이 지정된 경우 조건을 만족시키는 해가 완성된 것임
if len(assignment) == len(variables):
return assignment
# 아직 값을 갖지 않은 변수들이 존재하면 되추적 알고리즘을 아직 할당되지 않은 값을 대상으로 이어서 진행
unassigned = [v for v in variables if v not in assignment]
first = unassigned[0]
for value in domains[first]:
# 주의: 기존의 assignment를 보호하기 위해 복사본 활용
# 되추적이 발생할 때 이전 할당값을 기억해 두기 위해서임.
local_assignment = assignment.copy()
local_assignment[first] = value
# local_assignment 값이 유망하면 재귀 호출을 사용하여 변수 할당 이어감.
if promissing_colors(first, local_assignment):
result = backtracking_search_colors(local_assignment)
# 유망성을 이어가지 못하면 되추적 실행
if result is not None:
return result
return None
def promissing_colors(variable: int, assignment: Dict[int, int]):
"""새로운 변수 variable에 값을 할당 하면서 해당 변수와 연관된 변수들 사이의 제약조건이
assignment에 대해 만족되는지 여부 확인
m-색칠하기 문제의 경우: 이웃마디의 상태에 따라 제약조건이 달라짐.
즉, 마디 variable에 할당된 색이 이웃마디의 색과 달라야 함.
이를 위해 각각의 마디가 갖는 이웃마디들의 리스트를 먼저 확인해야 함."""
# 각 마디에 대한 이웃마디의 리스트
constraints = {
1 : [2, 3, 4],
2 : [1, 3],
3 : [1, 2, 4],
4 : [1, 3]
}
for var in constraints[variable]:
if (var in assignment) and (assignment[var] == assignment[variable]):
return False
return True
backtracking_search_colors()
{1: 1, 2: 2, 3: 3, 4: 2}
n 개의 마디를 m 개의 색으로 칠해야 하는 문제의 상태공간트리의 마디의 수는 다음과 같음.
$$ 1 + m + m^2 + m^3 + \cdots + m^n = \frac{m^{n+1}-1}{m-1} $$
<그림 출처: 위키피디아: 4색정리>
5-퀸 문제를 해결하는 되추적 알고리즘을 단계별로 설명하라.
앞서 소개한 n-퀸 알고리즘은 DFS 기법을 사용하며, 하나의 해답을 찾으면 바로 종료함. 따라서 아래 단계를 거치며 해답 하나를 구함.
backtracking_search_queens()
함수를 이용하여 확인하면 다음과 같음.# 변수: 네 개의 퀸의 번호, 즉, 1, 2, 3, 4, 5
variables = [1, 2, 3, 4, 5]
# 도메인: 각각의 퀸이 자리잡을 수 있는 가능한 모든 열의 위치.
domains: Dict[int, List[int]] = {}
columns = [1, 2, 3, 4, 5]
for var in variables:
domains[var] = columns
backtracking_search_queens()
{1: 1, 2: 3, 3: 5, 4: 2, 5: 4}
빨강(R), 초록(G), 파랑(B)이 주어졌을 때 되추적 알고리즘을 이용하여 아래 그래프를 색칠하는 과정을 단계별로 설명하라.
앞서 소개한 m-색칠하기 알고리즘은 DFS 기법을 사용하며, 하나의 해답을 찾으면 바로 종료함. 따라서 아래 단계를 거치며 해답 하나를 구함.
backtracking_search_colors()
함수를 이용하여 확인하면 다음과 같음.# 변수: 여섯 개 마디의 번호, 즉, 1, 2, 3, 4, 5, 6
variables = [1, 2, 3, 4, 5, 6]
# 도메인: 각각의 마디에 칠할 수 있는 색상 세 개(R, G, B)
domains: Dict[int, List[int]] = {}
columns = [1, 2, 3]
for var in variables:
domains[var] = columns
def promissing_colors(variable: int, assignment: Dict[int, int]):
"""새로운 변수 variable에 값을 할당 하면서 해당 변수와 연관된 변수들 사이의 제약조건이
assignment에 대해 만족되는지 여부 확인
m-색칠하기 문제의 경우: 이웃마디의 상태에 따라 제약조건이 달라짐.
즉, 마디 variable에 할당된 색이 이웃마디의 색과 달라야 함.
이를 위해 각각의 마디가 갖는 이웃마디들의 리스트를 먼저 확인해야 함."""
# 각 마디에 대한 이웃마디의 리스트
constraints = {
1 : [2, 4],
2 : [1, 3, 5],
3 : [2, 6],
4 : [1, 5],
5 : [2, 4, 6],
6 : [3, 5]
}
for var in constraints[variable]:
if (var in assignment) and (assignment[var] == assignment[variable]):
return False
return True
backtracking_search_colors()
{1: 1, 2: 2, 3: 1, 4: 2, 5: 1, 6: 2}