递归定义与实例

[TOC]

  • 递归是一种解决问题的方法, 其精髓在于将问题分解为规模更小的相同问题,持续分解,直到问题规模小到可以用非常简单直接的方式来解决。
  • 递归的问题分解方式非常独特,其算法方面的明显特征就是:在算法流程中调用自身。

递归“三定律”

为了向阿西莫夫的“机器人三定律”致敬, 递归算法也总结出“三定律”
1,递归算法必须有一个基本结束条件(最小规模问题的直接解决)
2,递归算法必须能改变状态向基本结束条件演进(减小问题规模)
3,递归算法必须调用自身(解决减小了规模的相同问题)

实例应用:

整数转换为任意进制

以10进制为中间体,用整数除, 和求余数两个计算来将整数一步步拆开除以“进制基base”(// base)对“进制基”求余数(% base)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pythonds.basic.stack import Stack

def baseConverter(decNumber,base):
digits = "0123456789ABCDEF"

remstack = Stack()

while decNumber > 0:
rem = decNumber % base
remstack.push(rem)
decNumber = decNumber // base

newString = ""
while not remstack.isEmpty():
newString = newString + digits[remstack.pop()]

return newString

Python中的递归深度限制

在Python内置的sys模块可以获取和调整最大递归深度

1
2
3
import sys
sys.getrecursionlimit()# 1000
sys.setrecursionlimit(3000)

分形树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import turtle

def tree(branchLen,t):
if branchLen > 5:
t.forward(branchLen)
t.right(20)
tree(branchLen-15,t) # 先画右边枝杈的
t.left(40)
tree(branchLen-15,t) # 画左边枝杈的 主干两个枝杈
t.right(20)
t.backward(branchLen)

def main():
t = turtle.Turtle()
myWin = turtle.Screen()
t.left(90)
t.up()
t.backward(100)
t.down()
t.color("green")
tree(75,t)
myWin.exitonclick()

main()

谢尔宾斯基三角形

平面称谢尔宾斯基三角形, 立体称谢尔宾斯基金字塔

实际上,真正的谢尔宾斯基三角形是完全不可见的,其面积为0,但周长无穷,是介于一维和二
维之间的分数维(约1.585维)构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import turtle

def drawTriangle(points,color,myTurtle):
myTurtle.fillcolor(color)
myTurtle.up()
myTurtle.goto(points[0][0],points[0][1])
myTurtle.down()
myTurtle.begin_fill()
myTurtle.goto(points[1][0],points[1][1])
myTurtle.goto(points[2][0],points[2][1])
myTurtle.goto(points[0][0],points[0][1])
myTurtle.end_fill()

def getMid(p1,p2):
return ( (p1[0]+p2[0]) / 2, (p1[1] + p2[1]) / 2)

def sierpinski(points,degree,myTurtle):
colormap = ['blue','red','green','white','yellow',
'violet','orange']
drawTriangle(points,colormap[degree],myTurtle)
if degree > 0:
sierpinski([points[0],
getMid(points[0], points[1]),
getMid(points[0], points[2])],
degree-1, myTurtle)
sierpinski([points[1],
getMid(points[0], points[1]),
getMid(points[1], points[2])],
degree-1, myTurtle)
sierpinski([points[2],
getMid(points[2], points[1]),
getMid(points[0], points[2])],
degree-1, myTurtle)

def main():
myTurtle = turtle.Turtle()
myWin = turtle.Screen()
myPoints = [[-100,-50],[0,100],[100,-50]]
sierpinski(myPoints,3,myTurtle)
myWin.exitonclick()

main()

汉诺塔

汉诺塔问题:递归思路

  • 将盘片塔从开始柱, 经由中间柱, 移动到目标柱:
  • 首先将上层N-1个盘片的盘片塔,从开始柱,经由目标柱,移动到中间柱;
  • 然后将第N个(最大的)盘片,从开始柱,移动到目标柱;
  • 最后将放置在中间柱的N-1个盘片的盘片塔,经由开始柱,移动到目标柱
1
2
3
4
5
6
7
8
9
10
def moveTower(height,fromPole, toPole, withPole):
if height >= 1:
moveTower(height-1,fromPole,withPole,toPole)
moveDisk(fromPole,toPole)
moveTower(height-1,withPole,toPole,fromPole)

def moveDisk(fp,tp):
print("moving disk from",fp,"to",tp)

moveTower(3,"A","B","C")

探索迷宫

递归调用的“基本结束条件”归纳如下:
海龟碰到“墙壁”方格,递归调用结束,返回失败;海龟碰到“面包屑”方格,表示此方格已访问过
,递归调用结束,返回失败;海龟碰到“出口”方格,即“位于边缘的通道”方格,递归调用结束,返回成功!海龟在四个方向上探索都失败,递归调用结束,返回失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import turtle

PART_OF_PATH = 'O'
TRIED = '.'
OBSTACLE = '+'
DEAD_END = '-'

class Maze:
def __init__(self,mazeFileName):
rowsInMaze = 0
columnsInMaze = 0
self.mazelist = []
mazeFile = open(mazeFileName,'r')
rowsInMaze = 0
for line in mazeFile:
rowList = []
col = 0
for ch in line[:-1]:
rowList.append(ch)
if ch == 'S':
self.startRow = rowsInMaze
self.startCol = col
col = col + 1
rowsInMaze = rowsInMaze + 1
self.mazelist.append(rowList)
columnsInMaze = len(rowList)

self.rowsInMaze = rowsInMaze
self.columnsInMaze = columnsInMaze
self.xTranslate = -columnsInMaze/2
self.yTranslate = rowsInMaze/2
self.t = turtle.Turtle()
self.t.shape('turtle')
self.wn = turtle.Screen()
self.wn.setworldcoordinates(-(columnsInMaze-1)/2-.5,-(rowsInMaze-1)/2-.5,(columnsInMaze-1)/2+.5,(rowsInMaze-1)/2+.5)

def drawMaze(self):
self.t.speed(10)
for y in range(self.rowsInMaze):
for x in range(self.columnsInMaze):
if self.mazelist[y][x] == OBSTACLE:
self.drawCenteredBox(x+self.xTranslate,-y+self.yTranslate,'orange')
self.t.color('black')
self.t.fillcolor('blue')

def drawCenteredBox(self,x,y,color):
self.t.up()
self.t.goto(x-.5,y-.5)
self.t.color(color)
self.t.fillcolor(color)
self.t.setheading(90)
self.t.down()
self.t.begin_fill()
for i in range(4):
self.t.forward(1)
self.t.right(90)
self.t.end_fill()

def moveTurtle(self,x,y):
self.t.up()
self.t.setheading(self.t.towards(x+self.xTranslate,-y+self.yTranslate))
self.t.goto(x+self.xTranslate,-y+self.yTranslate)

def dropBreadcrumb(self,color):
self.t.dot(10,color)

def updatePosition(self,row,col,val=None):
if val:
self.mazelist[row][col] = val
self.moveTurtle(col,row)

if val == PART_OF_PATH:
color = 'green'
elif val == OBSTACLE:
color = 'red'
elif val == TRIED:
color = 'black'
elif val == DEAD_END:
color = 'red'
else:
color = None

if color:
self.dropBreadcrumb(color)

def isExit(self,row,col):
return (row == 0 or
row == self.rowsInMaze-1 or
col == 0 or
col == self.columnsInMaze-1 )

def __getitem__(self,idx):
return self.mazelist[idx]


def searchFrom(maze, startRow, startColumn):
# try each of four directions from this point until we find a way out.
# base Case return values:
# 1. We have run into an obstacle, return false
maze.updatePosition(startRow, startColumn)
if maze[startRow][startColumn] == OBSTACLE :
return False
# 2. We have found a square that has already been explored
if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END:
return False
# 3. We have found an outside edge not occupied by an obstacle
if maze.isExit(startRow,startColumn):
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
return True
maze.updatePosition(startRow, startColumn, TRIED)
# Otherwise, use logical short circuiting to try each direction
# in turn (if needed)
found = searchFrom(maze, startRow-1, startColumn) or \
searchFrom(maze, startRow+1, startColumn) or \
searchFrom(maze, startRow, startColumn-1) or \
searchFrom(maze, startRow, startColumn+1)
if found:
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
else:
maze.updatePosition(startRow, startColumn, DEAD_END)
return found


myMaze = Maze('maze2.txt')
myMaze.drawMaze()
myMaze.updatePosition(myMaze.startRow,myMaze.startCol)

searchFrom(myMaze, myMaze.startRow, myMaze.startCol)