关于递归一

下面内容截取自我平常给学生讲课的内容


从数学归纳法谈起:

什么是 数学归纳法

从两个有趣的问题谈起:
1)怎么证明一堆人中所有人都是希腊人?

2)思考题:怎么证明所有人都是秃子?

什么是数学归纳法?
最简单和常见的数学归纳法是证明当 n等于任意一个自然数时某命题成立。证明分下面两步:
  1. 证明当n= 1时命题成立。
  2. 假设n=m时命题成立,那么可以推导出在n=m+1时命题也成立。(m代表任意自然数)
这种方法的原理在于:首先证明在某个起点值时命题成立,然后证明从一个值到下一个值的过程有效。当这两点都已经证明,那么任意值都可以通过反复使用这个方法推导出来。把这个方法想成 多米诺效应也许更容易理解一些。例如:你有一列很长的直立着的多米诺骨牌,如果你可以:
  1. 证明第一张骨牌会倒。
  2. 证明只要任意一张骨牌倒了,那么与其相邻的下一张骨牌也会倒。

2)怎么证明所有人都是秃子?
我们知道有0根头发的人是秃子,有1根头发的人也是秃子;
假设有n根头发的人是秃子,那么有n+1根头发的人也是秃子;
所以,所有人都是秃子;

再来看一个实际的例子:

证明: 一棵完全有k层的完全二叉树有2^k-1个节点。

1)基准点:k=1 , 节点个数为2^k-1 = 1,很显然成立;
2)假设,对k=n, 这个结论仍然成立,2^n-1;
3) 要证明,对 k =n+1时,这个结论仍然成立 2^(n+1)-1
根据假设,现在的总结点是: (2^n-1)*2+1 = 2^(n+) - 1

来看看数学归纳法在计算机递归算法证明中的作用:
斐波那契数列的例子:
这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以 递归的方法定义:f(0)=1,f(1)=1, f(n)=f(n-1)+f(n-2)(n>2,n∈N*)
我们一般在做斐波那契数列时递归的代码很简单:
def Fib(n):
if n <= 1:
return 1
else:
return Fib(n-1)+ Fib(n-2)
递归: 指函数的定义中使用函数自身的方法
怎么证明利用数学归纳法来证明这段程序的正确性?
1)对n = 0, Fib(0) 返回1, 等于f(0);
对n = 1, Fib(1)返回1, 等于f(1);
2)假设n >= 2, 对于所有的 2 <= m < n, Fib(m)返回f(m)
3) 下面,我们需要证明:F(n) = f(n)
则 F(n) = F(n-1) + F(n-2)
= f(n-1) + f(n-2) # 因为假设的存在
= f(n)
###########################################
# -*- coding:utf-8 -*-
# Fab(n) = Fab(n-1)+Fab(n-2) n > 2
# 1 n = 1,2
# n是大于0的自然数
# 用三种方法来实现:1.递归; 2.循环 3.yield
# 1,1,2,3,5,8,13,21,34,...
def Fab(n):
if n == 1 or n == 2:
return 1
return Fab(n-1)+Fab(n-2)

def Fab2(n):
index, a, b = 0, 1, 1
while index < n-2:
a, b = b, a+b
index += 1
return b

def Fab3(n):
index, a, b = 0, 0, 1
while index < n:
yield b
a, b = b, a+b
index += 1

import numpy as np
def fabFromMatrix(n):
if n <= 1:
return 1
f = np.mat('1 1; 1 0')
return (f**(n-1))[0,0]

if __name__ == '__main__':
print(Fab(7)) # 13
print(Fab2(7))

f = Fab3(7)
#print(f.next()) # python2 f.next()
print(next(f)) # python3 next(f)
print(next(f))
print(next(f))
#print(fabFromMatrix(7))
###########################################

思考题:
用递归算法求解一个数组的最大最小值,并用数学归纳法来证明算法的正确性;

再次来审视一下递归:
到底什么是递归?
一个过程或 函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归能解决什么问题?
有很多数学公式可以使用递归(recursive)的方式来声明:
F(0) = 0 X = 0
F(X) = 2F(X-1)+X^2 X > 0

怎么来写一段递归代码?
作为练习;

递归一般来说要考虑两个基本法则:
1)基准情形(base case); 必须得有最基准的情形,不用递归就能解决
2)不断推进(making progress);对于那些需要递归求解的情形,递归调用必须总能朝着基准情形的方向推进

递归中需要注意哪些问题?
Note:
def Bad( n ):# n必须是正整数
if n == 0:
return 0
else:
return Bad(n/3+1)+n-1 # 注意:这里有什么问题???
事实上Bad(1) 得不到!!!

其实,斐波那契数列可以使用循环重写。
循环和递归到底有什么差别?
循环: 为了获得一个结果,不断重复执行同一代码块;必须得有一个终止条件;
循环时状态不断的更新;
递归:重复执行某个执行流程;但是代码段有自己 独立的栈空间;终止条件通过基线条件来确定,当前状态变化通过参数传递的方式来完成的;独立的 栈空空间是递归崩溃的本源可读性很高

四个基本的法则:
1)基准情形;必须有基准的情形,它无须递归就能解出;
2)不断推进;每一次递归调用都必须使求解状况朝接近基准情形的方向推进;
3)设计法则;假设所有的递归调用都能运行;
4)合成效益法则(compound interest rule); 在 求解一个问题的同一个实例时,切勿在不同的递归调用中做重复性的工作;使用递归来计算诸如斐波那契数列数列之类的简单数学函数的值的想法并不是一个好主意,因为第四条;

递归最大的缺陷:
1)空间上开辟大量的栈空间;
2)时间上有避免重复;

什么是 尾递归(tail-call)优化?
举个例子:
def print100(n):
if n <= 100:
print(n)
print100(n+1)
print100(1)
尾递归: 最后一件事情是一个递归的函数,这种行为称为尾递归;


实例:
汉诺塔问题

汉诺塔是印度一个古老传说的益智玩具。汉诺塔的移动也可以看做是递归函数。

我们对柱子编号为a, b, c,将所有圆盘从a移到c可以描述为: 
如果a只有一个圆盘,可以直接移动到c; 
如果a有N个圆盘,可以看成a有1个圆盘(底盘) + (N-1)个圆盘,首先需要把 (N-1) 个圆盘移动到 b,然后,将 a的最后一个圆盘移动到c,再将b的(N-1)个圆盘移动到c。 
请编写一个函数,给定输入 n, a, b, c,打印出移动的步骤: 
move(n, a, b, c) 
例如,输入 move(2, ‘A’, ‘B’, ‘C’),打印出: 
A –> B 
A –> C 
B –> C
背景资料: 
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。 
简单的实现(有没有办法实现非递归的实现呢?):
def move(n, a, b, c):
if n==1:
print(a,'-->',c)
return
else:
move(n-1,a,c,b) #首先需要把 (N-1) 个圆盘移动到 b
move(1,a,b,c) #将a的最后一个圆盘移动到c
move(n-1,b,a,c) #再将b的(N-1)个圆盘移动到c
#move(4, 'A', 'B', 'C')
#move(3, 'A', 'B', 'C')
move(2, 'A', 'B', 'C')
印度传说是有 64个金片,这就是所谓梵塔;一秒移动一次,大约需要5800亿年
我们太阳系大约还能维持100-150亿年

递归例题:
1)用递归的方法,找出从1到最大的N位整数;
例子:给出N=1,返回【1,2,3,。。。,9】;
给出N=2,返回【1,2,3,。。。,99】;
class Solution:
#@param n:An interger.
# return: A list of integer storing 1 to the largest number with n digtis.



2)斐波那契数列, 动态规划等做时间上的优化;

怎么优化 递归的操作
压栈,空间的效率低;层次过多可能导致栈溢出;
怎么办?可以改成循环;
如果没有压栈操作,可以做尾递归优化(等价于优化);

3)快速排序算法 (nlogn)
# 5 2 6 8 7 9 0
#以5作为参考点pivot
# 目标: 2 0 5 6 8 7 9
#过程:
# i: 0
# 1 record 1 i+1 = 1
# 2:6 3:8 4:7 5:9
# 6: 0 i+1 = 2

class MySort:
# 快速排序
def quickSort(self, array, start, end):
if start < end:
# povit_index是基准点
povit_index = self.partition(array, start, end)
self.quickSort(array, start, povit_index)
self.quickSort(array, povit_index+1, end)
# 快速排序的分区过程
def partition(self, array, start, end):
povit_index = start # 基准点默认使用第一个元素的下标
povit = array[povit_index]
for i in range(start+1, end):
if array[i] < povit:
povit_index += 1
if povit_index != i:
array[i],array[povit_index] = array[povit_index],array[i]
array[start],array[povit_index] = array[povit_index], array[start]
return povit_index
def sort(self, A):
self.quickSort(A, 0, len(A))

A = [5, 2, 6, 8, 7, 9, 0]
mySort = MySort()
mySort.sort(A)
print(A)


  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 47
    评论
评论 47
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值