递归经典算法

①②③④⑤⑥⑦⑧⑨⑩

递归基本概念

递归

1.自己调用自己(简称俄罗斯套娃)
2.递去归来
递去:将递归问题分解为若干个规模小,和原来问题形式相同的子问题,这些子问题可以用相同的解题思路来解决
归来:当问题不断的缩小规模后,必须有一个明确的结束递去的临界点(递归的出口),一旦达到这个临界点就从该点原路返回到原点,最终问题得到解决

递归三原则

1.要有个基础条件,来退出递归(基线条件)
2.递归过程要向1靠拢(递归条件)
3.要不断的调用自身

递归调用栈(可参考其他博文)

栈:先进后出(压入和弹出)
递归调用的栈可能很长,这将占据很大的内存,如果没有设置递归深度,超过默认递归深度(好像是根据本身电脑的配置默认的?)则会报错,可以通过设置递归深度来避免这个错误

设置递归深度

import sys
sys.setrecursionlimit(3000) #设置最大深度为3000

递归的优缺点

优点:
1.代码简洁
2.便于理解
缺点:
1.时间和空间消耗大
2.可能存在栈溢出
3.可能存在重复计算

经典递归问题

1.阶乘

n!=n*(n-1)(n-2)(n-3)321

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)
print(factorial(5))
>>>120
2.计算列表的sum,length,max
def sum(list):
    if not list:
        return 0
    else:
        return list[0] + sum(list[1:])


def max(list):
    assert list, 'list is empty'
    if len(list) == 1:
        return list[0]
    else:
        return list[0] if list[0] >= max(list[1:]) else max(list[1:])
        # if list[0] >= max(list[1:]):
        #     return list[0]
        # else:
        #     return max(list[1:])


def len(list):
    return 0 if not list else 1 + len(list[1:])
    # if not list:
    #     return 0
    # else:
    #     return 1+len(list[1:])


list = [2, 3, 1, 6, 1, 7]
print(sum(list))
print(max(list))
print(len(list))
3.杨辉三角
def yanghuitriangle(n):
    result = []
    if n == 1:
        return 1
    for index in range(n):
        if index == 0 or index == n - 1:
            result.append(1)
        else:
            result.append(yanghuitriangle(n - 1)[index - 1] + yanghuitriangle(n - 1)[index])
    return result


print(yanghuitriangle(7))
4.斐波那契数列

前两个数的值分别为 0 、1 或者 1、1;
从第 3 个数字开始,它的值是前两个数字的和;
1 1 2 3 5 8 13 21 34…

def fibonacci(n):
    if n == 0 or n == 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(5))
5.青蛙跳台阶

①②③④⑤⑥⑦⑧⑨⑩
青蛙跳上n阶的台阶
1.一次可以跳1步或者2步
2.一次可以跳1步或者2步…或者n步
3.一次可以跳1步或者2步…或者m步
注意:
这里需要考虑0的情况也是一种情况

计算过程:
f(n-1)是青蛙跳一级台阶的情况
f(n-2)是青蛙跳两级台阶的情况

f(n-n)是青蛙跳N级台阶的情况

1.一次可以跳1步或者2步
f(n)=f(n-1)+f(n-2)

2.一次可以跳1步或者2步…或者n步
f(n)=f(n-1)+f(n-2)+f(n-3)+…+f(n-(n-2))+f(n-(n-1))+f(n-n)
即f(n)=f(n-1)+f(n-2)+…+f(2)+f(1)+f(0) ①
f(n-1)=f(n-1-(1))+f((n-1)-2)+…+f(n-1-(n-2))+f((n-1)-(n-1))
即f(n-1)=f(n-2)+f(n-3)+…f(2)+f(1)+f(0) ②
整理①②式子可得:
f(n)=2*f(n-1)

3.一次可以跳1步或者2步…或者m步
f(n)=f(n-1)+f(n-2)+f(n-3)+…+f(n-(m-2))+f(n-(m-1))+f(n-m)
整理得:
f(n)=f(n-1)+f(n-2)+…+f(n-m+2)+f(n-m+1)+f(n-m) ③

f(n-1)=f(n-1-1)+f(n-1-2)+…f(n-1-(m-2))+f(n-1-(m-1))+f(n-1-m)m
整理得:
f(n-1)=f(n-2)+f(n-3)+…f(n-m+1)+f(n-m)+f(n-m-1) ④

整理式子③④
f(n)=2*f(n-1)-f(n-m-1)

def frogjump(n):
    if n == 0:
        return 1
    elif n == 1 or n == 2:
        return n
    else:
        return frogjump(n - 1) + frogjump(n - 2)

def frogjump_n(n):
    if n == 0:
        return 1
    elif n == 1 or n == 2:
        return n
    else:
        return 2 * frogjump_n(n - 1)

def frogjump_m(m, n):
    if n == 0:
        return 1
    elif n == 1 or n == 2:
        return n
    elif m >= n:
        return 2 * frogjump_m(m, n - 1)
    else:
        return 2 * frogjump_m(m, n - 1) - frogjump_m(m, n - m - 1)
        
print(frogjump(10))
print(frogjump_n(5))
print(frogjump_m(3, 5))
>89
>16
>13
6.汉诺塔

汉诺塔问题源于印度一个古老传说。相传大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?

def hanni(n, x, y, z):
    """
    步骤:
    1.只有一层塔的话,直接将圆盘1从起始塔x移动到目标他z
    否则的话
    2.将圆盘1到n-1看作一个整体,从起始塔x借助目标塔z移动到中转塔y
    3.再将目标塔x移动到目标塔z
    4,将圆盘1daon-1看作一个整体,从中间塔y借助起点塔x移动到目标塔z
    :param n:层数
    :param x: 起始柱
    :param y: 中间柱
    :param z: 终端柱
    :return:
    """
    if n==1:
        print("{}--->{}".format(x,z))
        return
    else:
        hanni(n-1,x,z,y)
        #hanni(1,x,y,z)
        print("{}--->{}".format(x, z))
        hanni(n-1,y,x,z)
hanni(3,"x","y","z")
>>>>
>x--->z
x--->y
z--->y
x--->z
y--->x
y--->z
x--->z

7.快速排序!!!重要

基本思想

快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

快排三个步骤

(1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot)
(2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大
(3)递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。

选择基准的方式
对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。(如果选择的基准不好,快排的效率很低,时间复杂度能达到O(n^2)),一般来说快排的时间复杂度为O(n*log(n))

最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列
方法(1):固定位置(一般选取第一个或者最后一个)

def quicksort(list):
    if len(list) <2:
        return list
    else:
        pivot = list[0]
        less = [item for item in list[1:] if item < pivot]
        greater = [item for item in list[1:] if item >= pivot]
        return quicksort(less) + [pivot] + quicksort(greater)


print(quicksort([3, 8, 2, 6, 9, 10, 1, 24, 12]))
def quick_sort(lists,i,j):
    if i >= j:
        return list
    pivot = lists[i]
    low = i
    high = j
    while i < j:
        while i < j and lists[j] >= pivot:
            j -= 1
        lists[i]=lists[j]
        while i < j and lists[i] <=pivot:
            i += 1
        lists[j]=lists[i]
    lists[j] = pivot
    quick_sort(lists,low,i-1)
    quick_sort(lists,i+1,high)
    return lists

print(quick_sort([3, 8, 2, 6, 9, 10, 1, 24, 12],0,len([3, 8, 2, 6, 9, 10, 1, 24, 12])-1))

二叉树与递归

学习后再记录笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值