하노이의 탑 (The Tower of Hanoi)
하노이의 탑?
- 프랑스 수학자 에두아르 뤼카(Edouard Lucas)가 1883년에 만든 문제.
- 하노이의 탑 규칙은 위키백과를 참고.
하노이의 탑은 보통 두 가지 문제로 나뉜다.
- 원반을 이동하는 총 횟수를 구하는 문제.
- 원반을 이동하는 과정을 출력하는 문제.
요약
하노이의 탑에 있는 원반 n 개를 다른 한 기둥으로 옮기는 최소한의 이동 횟수 은 다음과 같다.
n 개의 원반을 옮기는 방법은 다음과 같이 생각하면 심플하다.
- 가장 작은 원반을 번, 가장 큰 원반을 번이라 한다.
- 중간 위치로 번부터 번 원반까지 옮긴다.
- 목적지 위치로 번 원반을 옮긴다.
- 중간 위치에서 목적지 위치로 번부터 번 원반을 옮긴다.
이 방법은 재귀를 사용하면 1~4 번 과정을 말로 설명한 것과 유사한 형태의 코드를 작성할 수 있다.
Golang
func hanoi(source, destination, temp string, n int) {
if n <= 0 {
return
}
// source -> temp 로 n-1 개를 옮긴다.
hanoi(source, temp, destination, n-1)
// 원반 하나를 source -> destination 으로 옮긴다.
fmt.Printf("%d 원반을 %s 에서 %s 로 옮깁니다.\n", n, source, destination)
// temp -> destination 으로 n-1 개를 옮긴다
hanoi(temp, destination, source, n-1)
}
Clojure
(defn hanoi
[source, destination, temp, n]
(when (> n 0)
(hanoi source temp destination (dec n))
(println n " 원반을 " source "에서 " destination " 로 옮깁니다.")
(hanoi temp destination source (dec n))))
(comment
;; A의 모든 원반3개를 B로 옮긴다. 옮길 때 C를 보조 기둥으로 사용한다.
(hanoi "A" "B" "C" 3)
;; 출력 결과
"
1 원반을 A 에서 B 로 옮깁니다.
2 원반을 A 에서 C 로 옮깁니다.
1 원반을 B 에서 C 로 옮깁니다.
3 원반을 A 에서 B 로 옮깁니다.
1 원반을 C 에서 A 로 옮깁니다.
2 원반을 C 에서 B 로 옮깁니다.
1 원반을 A 에서 B 로 옮깁니다."
;;
)
Gray Code
그레이 코드(Gray code)는 하노이의 탑 솔루션이기도 하다.
점화식
: 원반 n 개를 다른 한 기둥으로 옮기는 데 필요한 최소한의 이동 횟수
- : 3번 만에 원반 2 개를 다른 한 기둥으로 옮길 수 있다.
python 코드로 표현하자면 다음과 같이 함수 T
의 출력 결과 목록이라고 이해할 수 있다.
print( T(0) ) # 출력 결과는 0
print( T(1) ) # 출력 결과는 1
print( T(2) ) # 출력 결과는 3
다음 과정을 거치면 n
개의 원반이 있는 하노이의 탑을 클리어할 수 있다.
- 가장 큰 원반 하나를 제외한
n - 1
개의 원반을 다른 기둥으로 옮긴다.- 이동 횟수는 .
- 가장 큰 원반을 나머지 기둥으로 옮긴다.
- 이동 횟수는 1.
n - 1
개의 원반을 가장 큰 원반 위로 옮긴다.- 이동 횟수는 .
그렇다면 총 이동 횟수는 이므로, 다음과 같은 일반식을 만들 수 있다.
식 1.1
이와 같이 '경곗값(boundary value)'과 등식으로 구성되는 등식들을 다음과 같이 부른다.
- 점화식(recurrence)
- 점화 관계식(recurrence relation)
- 재귀식, 재귀관계식(recursion relation)
참고로 위의 점화식을 python 코드로 표현하자면 다음과 같다.
def T(n):
if n == 0:
return 0
return 2*T(n-1) + 1
clojure로는 다음과 같다.
(defn T [n]
(if (zero? n)
0
(-> (dec n) T (* 2) inc)))
(comment
(T 0) ;; => 0
(T 1) ;; => 1
(T 6) ;; => 63
;;
)
점화식의 해를 구하자
해를 구하기 위해 먼저 작은 n 값들을 살펴보자.
for n in range(7):
print(
"T_{input} = {result}"
.format(input=n, result=T(n))
)
# T_0 = 0
# T_1 = 1
# T_2 = 3
# T_3 = 7
# T_4 = 15
# T_5 = 31
# T_6 = 63
곰곰히 살펴보면 다음을 알 수 있다.
식 1.2
def T(n):
return 2**n - 1
수학적 귀납법을 사용한 증명
수학적 귀납법을 사용해 증명하려면 두 가지 단계를 증명하면 된다.
- 기초 단계(basis) : 에 대해 명제를 증명한다.
- 귀납 단계(induction) : (에서 에 대해 명제가 증명되었다는 가정 하에서) 에 대해 명제를 증명한다.
기초 단계는 쉽게 해결할 수 있다.
귀납 답계는 식 1.1
을 사용해 식 1.2
를 유도해 내어, 식 1.2
가 모든 에 대하여 성립함을 보인다.
점화식의 해를 간단하게 바꾸자
식 1.1
의 양 변에 1을 더해보자.
이 때, 이라 하면 다음과 같이 간단하게 바꿀 수 있다.
def U(n):
if n == 0:
return 1
return 2*U(n-1)
# T_0 = 0 U_0 = 1
# T_1 = 1 U_1 = 2
# T_2 = 3 U_2 = 4
# T_3 = 7 U_3 = 8
# T_4 = 15 U_4 = 16
# T_5 = 31 U_5 = 32
# T_6 = 63 U_6 = 64
원반을 옮기는 과정을 출력하자
다음은 원반을 옮기는 과정을 출력하는 go 코드이다.
func hanoi(source, destination, temp string, n int) {
if n <= 0 {
return
}
// source -> temp 로 n-1 개를 옮긴다.
hanoi(source, temp, destination, n-1)
// 원반 하나를 source -> destination 으로 옮긴다.
fmt.Printf("%d 원반을 %s 에서 %s 로 옮깁니다.\n", n, source, destination)
// temp -> destination 으로 n-1 개를 옮긴다
hanoi(temp, destination, source, n-1)
}
만약 hanoi("source", "dest", "temp", 3)
과 같이 호출하면 다음과 같은 결과가 나온다.
1 원반을 source 에서 dest 로 옮깁니다.
2 원반을 source 에서 temp 로 옮깁니다.
1 원반을 dest 에서 temp 로 옮깁니다.
3 원반을 source 에서 dest 로 옮깁니다.
1 원반을 temp 에서 source 로 옮깁니다.
2 원반을 temp 에서 dest 로 옮깁니다.
1 원반을 source 에서 dest 로 옮깁니다.
그레이 코드
그레이 코드(Gray code)는 하노이의 탑 원반 이동 과정을 출력하는 문제의 솔루션이다.
참고문헌
- CONCRETE MATHEMATICS / 로널드 L. 그레이엄, 도널드 E. 커누스, 오렌 파타슈닉 저/류광 역 / 인사이트(insight) / 초판 1쇄 2018년 04월 20일
- 다이내믹 프로그래밍 완전 정복 / 미나크시, 카말 라와트 저/박상은 역 / 한빛미디어 / 초판 1쇄 2019년 10월 04일