遞歸
遞歸就是程序自己調用自己的過程。
本身理解遞歸的思想比較容易,舉一個求階乘的例子:
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
測試:
>>> fact(1)
1
>>> fact(5)
120
實際上遞歸程序不可能一直遞歸循環下去,需要利用其它條件來結束遞歸循環。這里求階乘的例子,就是當n==1
時就結束遞歸循環。
這里以fact(5)
為例,看程序是如何進行遞歸運行的
===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120
可以看出程序從fact(5)
遞歸到fact(1)
結束。從上到下遞歸至結束,然后從下至上依次計算。
漢諾塔游戲
上面這個遞歸求階乘很好理解。但是將遞歸的思想放到漢諾塔中,就不是那么容易明白了。(關於漢諾塔的文章,其實網上很多了,這里不會很詳細解說。)簡單說下漢諾塔游戲的規則和玩法:
- 有三個柱子: a, b, c;
- a 上有數量為N個的圓盤;
- 從a柱將數量為N個的圓盤拿到 c 柱上;
- 一次只能拿一個圓盤,a,b,c 柱都可以利用;
- 無論在哪根柱子上,都是較大的圓盤永遠在較小圓盤下面。
為了方便后面的理解,這里先說明幾個字母含義:
N:一個標量,在下文中表示,第N個圓盤,N-1,第N-1個圓盤
n: 代表前n個圓盤, n-1 待變前n-1個圓盤;
a,b,c:分別表示三根柱子;
—>: 箭頭表示從...移動到...
玩漢諾塔游戲可以分為三步:
- 將n-1個圓盤從 a 移到 b上;
- 將N圓盤從a 移到 c 上;
- 將n-1 個圓盤,從b 移到c 上。
玩漢諾塔就是不斷重復那三步,直到n-1=1
。
要將 n 個圓盤從 a 移到 c ,
則先將n-1個從a移到b,然后將N從a移動c,再將n-1從b移到c;
要將n-1個從a移到b,
則先將n-2個從a移到c,然后將N-1從a移動b,再將n-2從c移到b;
要將n-2個從a移到c,
則先將n-3個從a移到b,然后將N-2從a移動c,再將n-3從b移到c;
要將n-3個從a移到b,
則先將n-4個從a移到c,然后將N-3從a移動b,再將n-4從c移到b;
......
在遞歸到最后一層,n- (n-1)=1, 也就是 a 柱第一個圓盤移向 b 還是 c 取決於N是奇數還是偶數。(代幾個數測試下就知道了)
注:要將n-k個從一個柱子移到另一個柱子,需要借助第三個空閑柱子
紅色部分遞歸路徑,假設其為遞歸1號路線,在其下面還有其它遞歸分支(綠色表示)。等紅色的從上往下遞歸到底后,程序從底下,往上開始計算,遇到綠色則開始遞歸,等綠色遞歸完后繼續計算上一層的。
程序一開始的遞歸沿着紅色一直遞歸到最后一層(實際圓盤中最上面的那個)不再進行遞歸,直接開始搬運圓盤。然后運行最后一層的剩余步驟(黑色,紅色),最后一層運行完,
等計算完,紅色部分遞歸1號路線,
程序運行N(a)->c,
然后開始藍色部分的遞歸路徑(稱其遞歸2號路線),遞歸2號路線同1號路線一樣也有許多遞歸支路。
為幫助理解,下面是一個路線圖,挺丑的,湊合看。。
如果單純值拿紅色遞歸1號路線(不包括其遞歸支線),其實其和上面的遞歸階乘的例子是一樣的。但是漢諾塔這個有了許多遞歸支線,就感覺復雜了許多。同時也可能因為從a->b, a->c, b->c, a->c,a-b...這三個字母混去混來的,腦袋記不住它們關系了,容易犯渾。但是你像圖片那樣將遞歸推導列出來就容易明白了。
代碼
# Python
def move(n, a, b, c):
if n == 1:
print(f"{a} --> {c}")
else:
move(n-1, a, c, b)
print(f"{a} --> {c}")
move(n-1, b, a, c)
move(4,'a', 'b', 'c')
#include <stdio.h>
// C
void move(int, char, char, char);
void move(int n, char x, char y, char z)
{
if (n == 1)
{
printf("%c --> %c\n", x, z);
}
else
{
move(n-1, x, z, y);
printf("%c --> %c\n", x, z);
move(n-1, y, x, z);
}
}
int main()
{
int n;
scanf("%d", &n);
move(n, 'a', 'b', 'c');
return 0;
}
參考:
原文: 漢諾塔游戲的遞歸解析