
탐색 방법
순서 행위 1 그래프상에서 모든 정점의 부모노드를 -1로 초기화하고, 비용은 INF(무한대)값으로 설정 2 시작노드의 비용을 0으로 초기화하고, 시작노드의 부모노드는 시작노드를 가르킨다. 3 현재노드를 기준으로 모든 방향의 노드에 대한 비용을 계산한다. 4 비용이 계산된 노드들중 최소비용을 가지는 노드가 다음노드가 된다. 5 비용노드의 부모노드는 현재노드를 가르킨다. 6 3~5를 반복한다. 7 만약 다음노드가 도착노드라면, 탐색을 중지하고 최적의 경로를 계산한다.
최적의 경로를 계산하는 방법
순서 행위 1 도착노드를 스택에 푸쉬(push)한다. (현재 가르키고 있는 노드는 부모노드이다.) 2 현재 가르키고있는 노드의 부모노드를 스택에 푸쉬(push)한다. 3 2를 반복한다. 4 만약 현재 가르키고 있는 노드와 부모노드가 같다면(시작노드라면) 3을 중지한다. 5 그러면 이 스택은 최적의 경로에 대한 정보를 담고 있게 된다.
탐색하는 방법에서 설명했던 '비용을 계산한다' 부분에는 계산 방법을 담고있다.
이를 평가함수라고 부르는데 평가함수는 다음과 같다.

일 때, 다익스트라 알고리즘과 동일하게 동작(, 역추적 방식)
열린 목록(Open List) : 탐색 중인 노드들을 저장
닫힌 목록(Closed List) : 탐색을 완료한 노드들을 저장

1. 시작 배열

2. 6을 움직인 경우에서
3. 1와 8을 움직인 경우에서
(1) 1을 움직인 경우에서
(2) 8을 움직인 경우에서
4. 2를 움직인 경우에서
5. 1을 움직인 경우에서
class Node:
def __init__(self, data, level, fval):
self.data = data # 현재 노드의 상태 데이터
self.level = level # 현재 노드의 깊이 레벨
self.fval = fval # 현재 노드의 f값 (f = g + h, 여기서 g는 루트 노드부터 현재 노드까지의 경로 비용, h는 현재 노드에서 목표 노드까지의 예상 비용)
def generate_child(self):
x, y = self.find(self.data, '_') # 현재 상태에서 빈 공간 위치 찾기
val_list = [[x+1, y], [x, y+1], [x-1, y], [x, y-1]] # 빈 공간 주변으로 이동할 수 있는 방향 (상, 하, 좌, 우)
children = []
for i in val_list:
child = self.shuffle(self.data, x, y, i[0], i[1]) # 가능한 이동 후보 생성
if child is not None:
child_node = Node(child, self.level+1, 0) # 새로운 자식 노드 생성
children.append(child_node)
return children
def shuffle(self, puz, x1, y1, x2, y2):
if x2 >= 0 and x2 < len(self.data) and y2 >= 0 and y2 < len(self.data): # 보드 범위 내에서 이동하는지 확인
temp_puz = self.copy(puz) # 보드 복사
temp = temp_puz[x2][y2]
temp_puz[x2][y2] = temp_puz[x1][y1]
temp_puz[x1][y1] = temp
return temp_puz # 새로운 상태 반환
else:
return None # 이동이 불가능한 경우 None 반환
def copy(self, root):
temp = [] # 복사할 보드
for i in root:
t = []
for j in i:
t.append(j)
temp.append(t) # 보드의 각 행을 복사하여 temp에 추가
return temp # 새로운 보드 반환
def find(self, puz, x): # 공백의 위치를 찾기 위한 함수
for i in range(0, len(self.data)):
for j in range(0, len(self.data)):
if puz[i][j] == x: # 주어진 문자 x가 있는 위치 찾기 여기서 x는 '_'
return i, j # 해당 위치 반환
class Puzzle: # 퍼즐 크기를 지정된 크기로, 열린 리스트/ 닫힌 리스트를 공백으로 초기화
def __init__(self, size):
self.n = size
self.open = [] # open 리스트 (탐색할 노드들이 들어감)
self.closed = [] # closed 리스트 (이미 탐색한 노드들이 들어감)
def accept(self):
puz = []
for i in range(0, self.n):
temp = input().split(" ") # 사용자로부터 보드 상태 입력 받음
puz.append(temp)
return puz # 입력받은 보드 상태 반환
def f(self, start, goal): # 평가함수(휴리스틱) 값 계산
return self.h(start.data, goal) + start.level # f = g + h 반환
def h(self, start, goal): #h(n) 값 계산 함수 현재 배열과 목표 배열의 다른 숫자판 카운트 함수
temp = 0
for i in range(0, self.n):
for j in range(0, self.n):
if start[i][j] != goal[i][j] and start[i][j] != '_':
temp += 1 # 현재 상태와 목표 상태를 비교하여 맞지 않는 타일 수를 카운트하여 반환
return temp
def process(self):
print("초기 퍼즐 상태 입력 : \n")
start = self.accept() # 초기 상태 입력 받음
print("\n")
print("퍼즐 목표 상태 입력 : \n")
goal = self.accept() # 목표 상태 입력 받음
start = Node(start, 0, 0) # 초기 노드 생성
start.fval = self.f(start, goal) # 초기 노드의 f값 설정
self.open.append(start) # 초기 노드를 open 리스트에 추가
while True:
cur = self.open[0] # open 리스트에서 현재 노드 선택
print("")
print(" ↓ ")
print("")
for i in cur.data:
for j in i:
print(j, end="")
print("")
print("f 값:", cur.fval) # 현재 노드의 f값 출력
if(self.h(cur.data, goal) == 0): # 현재 상태가 목표 상태와 일치하는지 확인 h(n)함수가 0이면 숫자판 배열이 같으닌가!
break
for i in cur.generate_child(): # 현재 노드의 자식 노드들 생성
i.fval = self.f(i, goal) # 자식 노드의 f값 설정
self.open.append(i) # 자식 노드를 open 리스트에 추가
self.closed.append(cur) # 현재 노드를 closed 리스트에 추가
del self.open[0] # open 리스트에서 현재 노드 제거
self.open.sort(key=lambda x: x.fval, reverse=False) # open 리스트를 f값에 따라 정렬하여 최적의 다음 노드 선택
puz = Puzzle(3) # 퍼즐 사이즈가 3x3인 퍼즐 객체 생성
puz.process() # 퍼즐 해결 알고리즘 실행
실행 결과
초기 퍼즐 상태 입력 :
2 8 3
1 6 4
7 _ 5
퍼즐 목표 상태 입력 :
1 2 3
8 _ 4
7 6 5
↓
283
164
7_5
f 값: 4
↓
283
1_4
765
f 값: 4
↓
2_3
184
765
f 값: 5
↓
283
_14
765
f 값: 5
↓
_23
184
765
f 값: 5
↓
123
_84
765
f 값: 5
↓
123
8_4
765
f 값: 5
위에서 직접 풀어본 결과와 같은 결과가 나옴