数据结构与算法(11)—递归

  • 递归问题

将问题分解为更小的相同问题——>用非常简单直接地方式解决——>调用自身。

例子:数列求和问题

1 def listsum(numList):
2     if len(numList) ==1:
3         return numList[0]
4     else:
5         return numList[0] + listsum(numList[1:]) #送进去listsum的是一个list
6 print(listsum([1,2,3,4,5,9]))
  • 递归三定律

1.递归算法必须有一个基本结束条件

2.递归算法必须能改变状态向基本结束条件演进(减小问题规模)

3.递归算法必须调用自身

  • 递归应用

  1.整数任意进制转换

 1 def toStr(n,base):
 2     '''
 3     :param n: 要转换的十进制整数
 4     :param base: 转换成的n进制
 5     :return: 转换结果
 6     '''
 7     convertString = '0123456789ABCDEF'
 8     if n < base:
 9         return convertString[n]
10     else:
11         return toStr(n//base, base) + convertString[n%base]
12 print(toStr(76989,16))

 注:这里需要理解下递归的调用时依赖栈的。如toStr函数,当每次调用递归函数,toStr(n)入栈,但此时还没返回,最先入栈的在栈底,最后入栈的在栈顶。 最先返回的是toStr(1),此时调用顺序和返回顺序是相反的。

即:每次调用,压入栈的现场数据为栈帧,当函数返回时,要从调用栈的栈顶取得返回地址,恢复现场,弹出栈帧,按地址返回。

 

 

 

  2.递归作图

1 import turtle
2 #画箭头
3 t = turtle.Turtle()
4 t.forward(100)
5 turtle.done()
1 #画正方形
2 for i in range(4):
3     t.forward(100)
4     t.right(90)
5 turtle.done()
1 #画五角星
2 t = turtle.Turtle()
3 t.pencolor('red')
4 t.pensize(3)
5 for i in range(5):
6     t.forward(100) #长度
7     t.right(144) #右转角144°
8 t.hideturtle()
9 turtle.done()
 1 t = turtle.Turtle()
 2 def drawSpiral(t,lineLen):
 3     '''
 4     画螺旋,从外向内画
 5     '''
 6     if lineLen > 0:
 7         t.forward(lineLen)
 8         t.right(90)#右转90°
 9         drawSpiral(t, lineLen - 5)
10 drawSpiral(t, 100)
11 turtle.done()

  3.画分形树

 1 def tree(branch_len):
 2     if branch_len > 5:#树干太短不画
 3         t.forward(branch_len) #画树干
 4         t.right(20) #右倾斜20°
 5         tree(branch_len - 15) #画右小树,树干减15
 6         t.left(40) #向左倾斜20°
 7         tree(branch_len - 15) #画左小树,树干减15
 8         t.right(20) #向右回20°,回正
 9         t.backward(branch_len) #海龟退回原位置
10 t = turtle.Turtle()
11 t.left(90)
12 t.penup()
13 t.backward(100)
14 t.pendown()
15 t.pencolor('green')
16 t.pensize(2)
17 tree(75)
18 t.hideturtle()
19 turtle.done

 注:分形树对于我来说挺难理解的,全靠debug帮我理清逻辑,首先一定要记住递归函数栈的进栈顺序和函数调用顺序,这里很关键。

  4.谢尔宾斯基三角形

 

 就长这个样子,自己领会吧。它是由3个尺寸减半的谢尔宾斯基三角形按照品字形状叠拼起来的。

 

 画谢尔宾斯基三角形代码:

 1 import turtle
 2 
 3 def sierpinski(degree, points):
 4     colormap= ['blue','red','green','white','yellow','orange']
 5     drawTriangle(points,colormap[degree])
 6     if degree >0:
 7         #画左边小三角形
 8         sierpinski(degree-1,{'left':points['left'], #左边坐标不变
 9                              'top':getMid(points['left'],points['top']), #顶部坐标为左边和顶部的中点
10                              'right':getMid(points['left'],points['right'])}) #右边坐标为原来左边和右边的中点
11         # 画中间小三角形
12         sierpinski(degree - 1, {'left':getMid(points['left'], points['top']),
13                                 'top':points['top'],
14                                 'right':getMid(points['top'], points['right'])})
15         # 画右边小三角形
16         sierpinski(degree - 1, {'left': getMid(points['left'], points['right']),
17                                 'top': getMid(points['top'], points['right']),
18                                 'right': points['right']})
19 # 绘制填充等边三角形
20 def drawTriangle(points,color):
21     t.fillcolor(color)
22     t.penup()
23     t.goto(points['top'])
24     t.pendown()
25     t.begin_fill()
26     t.goto(points['left'])
27     t.goto(points['right'])
28     t.goto(points['top'])
29     t.end_fill()
30 
31 def getMid(p1,p2):
32     return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2) #((x1+x2)/2,(y1+y2)/2)
33 
34 t = turtle.Turtle()
35 
36 points = {'left':(-200,-100),#外轮廓三个顶点坐标
37           'top':(0,200),
38           'right':(200,-100)}
39 sierpinski(5,points) #画5阶三角形
40 turtle.done()

效果图:

   5.汉诺塔问题

问题表述是这样的:(懒得码字,直接截图啦)

 

 如果说要把64个盘片搬完,需要2^64 -1=18 446 744 073 709 551 615次。

首先先看下3个大中小盘,3根柱子,初始在1号柱,挪动方法:首先将小盘挪到3号柱子,中盘挪到2号柱,再将在3号的小盘挪到2号中盘上,再将1号大盘挪到3号柱,再将2号小盘挪到1号柱,再将2号重盘挪到3号柱,最后将1号小盘挪到3号柱,则完成。

对于有4个盘子,3根柱子问题可以考虑先将上面3个挪到2号柱,再将第4个挪到3号柱。

对于有5个盘子,3根柱子问题则考虑现将上面4个挪到2号柱,再将第5个挪到3号柱。

可以看到,这其实就是个递归问题。

将盘片从开始柱,经由中间柱,移动到目标柱的解决思路:

(1)将上层N-1个盘子的从开始柱(fromPole),经由目标柱(toPole),移动到中间柱(withPole);

(2)将第N个(最大的那个),从开始柱(fromPole),移动到目标柱(toPole);

(3)将放置在中间柱N-1个盘子的盘子塔,从中间柱(withPole),经由开始柱(fromPole),移动到目标柱(toPole)。

(4)递归

看下代码:

 1 def moveTower(height,fromPole,withPole,toPole):
 2     '''
 3     :param height: 汉诺塔高度
 4     :param fromPole: 起始移动所在柱子
 5     :param withPole:中间柱子
 6     :param toPole:目的柱子
 7     :return:
 8     '''
 9     if height >=1:
10         moveTower(height-1,fromPole,toPole,withPole)
11         moveDisk(height,fromPole,toPole)
12         moveTower(height-1,withPole,fromPole,toPole)
13 #直接从fromPole到toPole
14 def moveDisk(disk,fromPole,toPole):
15     print(f'moving disk[{disk}] from {fromPole} to {toPole}')
16 
17 moveTower(5,'#1','#2','#3')

运行结果:

moving disk[1] from #1 to #3
moving disk[2] from #1 to #2
moving disk[1] from #3 to #2
moving disk[3] from #1 to #3
moving disk[1] from #2 to #1
moving disk[2] from #2 to #3
moving disk[1] from #1 to #3
moving disk[4] from #1 to #2
moving disk[1] from #3 to #2
moving disk[2] from #3 to #1
moving disk[1] from #2 to #1
moving disk[3] from #3 to #2
moving disk[1] from #1 to #3
moving disk[2] from #1 to #2
moving disk[1] from #3 to #2
moving disk[5] from #1 to #3
moving disk[1] from #2 to #1
moving disk[2] from #2 to #3
moving disk[1] from #1 to #3
moving disk[3] from #2 to #1
moving disk[1] from #3 to #2
moving disk[2] from #3 to #1
moving disk[1] from #2 to #1
moving disk[4] from #2 to #3
moving disk[1] from #1 to #3
moving disk[2] from #1 to #2
moving disk[1] from #3 to #2
moving disk[3] from #1 to #3
moving disk[1] from #2 to #1
moving disk[2] from #2 to #3
moving disk[1] from #1 to #3

Process finished with exit code 0

  6.递归可视化,探索迷宫

迷宫的数据结构:用矩阵方式来实现。

 1 class Maze:
 2     def __init__(self,mazeFileName):
 3         columnsInmaze = 0
 4         self.mazelist = []
 5         mazeFile = open(mazeFileName,'r')
 6         rowsInMaze = 0
 7         for line in mazeFile:
 8             rowList = []
 9             col = 0
10             for ch in line[:-1]:
11                 rowList.append(ch)
12                 if ch == 's': #海龟投放点S
13                     self.startRow = rowsInMaze
14                     self.startCo = col
15                 col = col+1
16             rowsInMaze = rowsInMaze + 1
17             self.mazelist.append(rowList)
18             columnsInmaze = len(rowList)
19 maze = Maze('maze.txt')

探索思路为:将海龟从原位置向北移东一步,以新位置递归调用探索迷宫寻找出口,并边走边洒面包屑,如果向北找不到出口,则海龟从原始位置向南移动,更新迷宫位置,如向南找不到出口,则从原始位置向西移动一步,更新迷宫位置,如向西找不到出口,则从原始位置向东移动一步,如果都找不到,则迷宫没有出口,但如果一旦发现某前进方格上有面包屑,则立即从递归调用返回上一级。

递归结束的基本条件为:

海龟碰到墙壁方格,递归调用结束,返回失败;

海龟碰到面包屑方格,递归调用结束,返回失败;

海龟碰到出口方格,递归调用结束,返回成功;

海龟在四个方向上探索都失败,递归调用结束,返回失败。

探索迷宫的核心代码:

 1 def searchFrom(selfmaze,startRow,startColumn):
 2         #1.碰到墙壁,返回失败
 3         maze.updatePosition(startRow,startColumn)
 4         if maze[startRow][startColumn] == OBSTACLE:
 5             return False
 6         #2.碰到面包屑,或者死胡同,返回失败
 7         if maze[startRow][startColumn] == TRIED or maze[startRow][startColumn] == DEAD_END
 8             return False
 9         #3.碰到了出口,返回成功
10         if maze.isExis(startRow,startColumn):
11             maze.updatePosition(startRow,startColumn,TRIED)
12         #4.洒面包屑,继续探索
13         maze.uodataPosition(startRow,startColumn,TRIED)
14 
15         #向北南东4个方向依次探索,or操作符具有短路效应
16         found = searchFrom(maze, startRow-1, startColumn) or\
17             searchFrom(maze, startRow+1, startColumn) or\
18             searchFrom(maze, startRow, startColumn-1) or\
19             searchFrom(maze, startRow, startColumn+1)
20         if found:
21             maze.updatePosition(startRow,startColumn,PART_OF_PATH)
22         else:
23             maze.updatePosition(startRow,startColumn,DEAD_END)
24         return found

 

posted @ 2020-04-03 00:34  Yelush  阅读(293)  评论(0编辑  收藏  举报