1递归
什么是递归?递归不是算法,函数内部调用函数本身。
递归一定要终止,怎么写终止条件很重要。
1.1斐波拉契数列
斐波拉契数列又称为黄金分割数列,因为数学家列昂纳多 斐波那契以兔子繁殖为列子而引入,故又称为“兔子数列”。
指的是这样一个数列:1、1、2、3、5、8、13、21、34、…后面的数都等于前面的数的和。
1.2斐波那契数列的计算
- 递归求解
from collections import defaultdict=
total = {}
total = defaultdict(int)
def fib_test(k):
# 递归求解第k个数的值
if k in [1, 2]:
return 1
global total
total[k] += 1
return fib_test(k - 1) + fib_test(k - 2)
if __name__ == "__main__":
from datetime import datetime
start_time = datetime.now()
fib_test(35)# 迭代9227464次
fib_test(34) # 迭代5702886次
# 递归由于重复计算的次数非常多,因此特别耗时
print("递归耗时:{}".format((datetime.now() - start_time).total_seconds()))
print(total)
- 循环求解
def fib_test2(k):
# 循环求解第k个数的值
assert k > 0, "k必须大于0"
if k in [1, 2]:
return 1
k_1 = 1
k_2 = 1
for i in range(3, k + 1):
temp = k_1
k_1 = k_1 + k_2
k_2 = temp
return k_1
if __name__ == "__main__":
from datetime import datetime
start_time = datetime.now()
print(fib_test2(100)) # 迭代5702886次
print("循环耗时:{}".format((datetime.now() - start_time).total_seconds()))
print(total)
1.3斐波那契数列的其他问题
- 1.爬楼梯问题
假设你现在在爬楼梯,楼梯有n阶,每次你只能爬1级或2级,那么你有多少种方法爬到楼梯的顶部。
分析:对于顶部而言,倒数第一步只有两种到达方式,爬1级或爬2级。 - 2.生兔子问题
有一对兔子,从出生第3个月起,每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假设兔子都不死,问每个月的兔子总数为多少?
分析:
第一个月 2=2
第二个月 2=2
第三个月 2+2(新)=4
第四个月 2+2(新1)+2(新)=6
第五个月 2+2(新)+2(新1)+2(新2) = 8
n>3
f(n) = f(n-1) + f(n-3)
1.4二分查找-非递归实现
二分查找的效率高,时间复杂度为O(log_2n ),即O(logn )
# data = [1, 7, 17, 18, 27, 29, 30, 35, 35, 39, 41, 63, 63, 66, 67, 78, 82, 82, 91, 92]
data = [1, 7, 17, 18, 27, 29, 30, 35, 39, 41, 63, 66, 67, 78, 82, 91, 92]
# 二分查找(折半查找)
# 假设有32个数,每运行一次,少一半。32->16->8->4->2
def search(data_list, target):
left = 0
right = len(data_list) - 1
target_pos = -1
while left <= right:
# 1.找到[left,right]中间的值,int()向下取整
mid = int((left + right) / 2)
# 2. 判断中间位置的值和目标值的大小
if data_list[mid] == target:
target_pos = mid
break
elif data_list[mid] < target:
# 如果中间值小于目标值,则在右侧继续二分查找
left = mid + 1
else:
# 如果中间值大于目标值,则在左侧继续二分查找
right = mid - 1
return target_pos
if __name__ == "__main__":
pos = search(data, 92)
if pos >= 0:
print("查询到数据,所在位置 :{}".format(pos))
else:
print("目标值不在列表中")
1.5二分查找-递归实现
import random
# data = [1, 7, 17, 18, 27, 29, 30, 35, 39, 41, 63, 66, 67, 78, 82, 91, 92]
def random_list(start, end, length):
# 随机生成长度为length,从start到end之间的列表
data_list = []
for i in range(length):
data_list.append(random.randint(start, end))
return data_list
def search(left, right, data_list, target):
if left > right:
return -1
mid = int((left + right) / 2)
if data_list[mid] == target:
return mid
elif data_list[mid] < target:
# 如果中间值小于目标值,则继续在右侧二分查找
return search(mid + 1, right, data_list, target)
else:
# 如果中间值大于目标值,则继续在左侧二分查找
return search(left, mid - 1, data_list, target)
if __name__ == "__main__":
# pos = search(0, len(data) - 1, data, 29)
# if pos >= 0:
# print("查询到数据,所在位置 :{}".format(pos))
# else:
# print("目标值不在列表中")
# 随机生成一组数,并排序
data = random_list(1, 100, 10)
data = sorted(data)
print(data)
target = random.randint(0, len(data) - 1)
print(data[target])
pos = search(0, len(data) - 1, data, data[target])
if pos >= 0:
print("查询到数据,所在位置 :{}".format(pos))
else:
print("目标值不在列表中")
1.6汉诺塔实现
# 1.大傻:只搬动一号盘
# 2.1.叫谁来做;2.从哪个柱子搬到哪个柱子,中间柱子
# 2.2.搬动自己负责的柱子 3. 叫刚刚那个谁从中间柱子个柱子搬到目标柱子
def move(index, start, mid, end):
if index == 1:
print("{}-->{}".format(start, end))
return
else:
move(index - 1, start, end, mid)
print("{}-->{}".format(start, end))
move(index - 1, mid, start, end)
if __name__ == "__main__":
move(3, "A", "B", "C")
运行结果:
1.7总结
- 递归优点:
1.写起来简单。
2.递归都能通过非递归的方式完成。 - 递归缺点:
1.递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址一级临时变量,而往栈中压入数据和弹出数据都需要时间。效率不高。
2.递归很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在互相重叠的部分,则存在重复计算,如Fibonacci斐波那契数列的递归实现。效率不高。
3.调用栈可能会溢出,其实每一次函数调用会在内存中分配空间,而每个进程栈的容量是有限的,当调用的层次太多,就会超出栈的容量,从而导致栈溢出。