递归 | 二分查找

主要内容:

  • 1.递归
  • 2.列表查找  - 二分查找

1. 递归

1.1 递归的特点

  • 调用自身
  • 结束条件

判断下列函数是否是递归

def func(x):
    print(x)
    func(x-1)

#没有结束条件  --> 递归出口
def func(x):
    if x > 0:
        print (x)
        func(x +1)

 

# 递归调用之前对x进行 打印
def func(x):
    if x > 0:
        print(x)
        func(x-1)
func(3)

# 输出结果 3,2,1
#递归调用之后进行调用
def func(x):
    if x > 0:
        func(x-1)
        print(x)
func(3)

# 输出结果 1,2,3 

1.2 递归实例  --  斐波那契问题

斐波那契数列:

  斐波那契数列数列从第3项开始,每一项都等于前两项之和。 例子:数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ..........

求解斐波那契第 n 项

  • 使用递归  时间复杂度 O(2^n)
    #使用递归,但是注意此时最后的return 语句不是尾递归
    def func(n):
        if n==1 or n==2:
            return 1
        else:
            return func(n-1) + func(n-2)

    使用递归慢的原因: 由于需要多次的重复计算,导致很慢

  • 使用递归的改进方法 : 通过记录值解决重复计算的问题,但是时间复杂度没有改变
    def func(n):
        #创建一个列表,此时列表中的元素 未被赋值前是-1 ,当赋值后变为正数
        lst = [-1 for i in range(n+1)]
        def fib(n):
            if  n == 0 or n == 1:
                return 1
            elif lst[n] >= 0:
                return lst[n]
            else:
                v = fib(n-1) + fib(n-2)
                #只是记录一下,下次再用的时候直接扔出去
                lst[n] = v
                return lst[n]
        return fib(n)
  • 使用列表,将后一项添加到列表中,return 列表最后一项 时间复杂度O(n)
    def func(n):
        res = [1,1]
        for i in range(2,n+1):
            res.append(res[-1] + res[-2])
            return res[-1]

    上述方式的缺点: 虽然时间复杂度为O(n) ,但是创造了列表 ,空间复杂度增加

  • 使用3变量的方式 ,时间复杂度为 O(n) ,但是省空间
    def func(n):
        if n == 0 or n == 1:
            return 1
        a = 1 
        b = 1
        c = 0  #c可以是任何值
        
        for i in range(2,n+1):
            c = a + b
            a = b
            b = c     
        return c 

    更优化版:

    def func(n):
        a = 1
        b = 1
        c = 1  
        for i in range(2,n+1):
            c = a + b
            a = b
            b = c
        return c

1.3 递归实例2  -  汉诺塔问题

问题描述:

  • 假设有三个命名为 A B C 的塔座 ,在塔座A上插有n个直径大小不相同,由小到大编号为1 ,2 ,3 ,··· ,n的圆盘,要求将A座上的圆盘移至塔座C
  • 并按同样的顺序叠排

圆盘移动规则

  • 每次只能移动一个圆盘
  • 圆盘可以插在任意一个塔座上
  • 任何时刻都不能将一个较大的圆盘放在一个较小的圆盘上

问题简化 :

  • 将 n - 1  个圆盘 看成一个整体,将其从A经过C移动到B
  • 将第 n个盘子从A移动到C
  • 将开始的n - 1 个盘子从B 经过A移动到C

 

代码书写

  • 汉诺塔移动次数的递推式 h(x) = 2h(x-1) +1
    def hanoi(n,A,B,C):
        '''
        :param n: 需要从A移到C 的盘子总数
        :param A: 移动盘子的起始柱子
        :param B: 移动过程中经过的柱子
        :param C: 移动结束的柱子
        '''
        if n > 0 :
            hanoi(n-1,A,C,B)            #将起始A柱子上的n-1个盘子通过C移动到B
            print("%s -> %s" %(A,C))    #将底层的盘子从A放到C
            hanoi(n-1,B,A,C)            #将B上的n-1 个盘子经由A移动到C

1.4 题目练习

问题描述:

  • 一段有n个台阶组成的楼梯,小明从楼梯的最底层向最高处前进,
  • 他可以选择一次迈一级台阶或者一次迈两级台阶。问:他有多少种不同的走法?

 

2. 列表查找  - 二分查找

列表查找 :从列表中查找指定元素

  • 输入: 列表,带查找元素
  • 输出: 元素下标或未查找到的元素

顺序查找:从列表的第一个元素开始,顺序进行搜索,直到找到为止

# 顺序查找代码,时间复杂度为O(n)
def linear_search(data,value):
    '''
    :param data: 查找的目标列表
    :param value:目标查找元素
    :return:
    '''
    for i in range(data):
        if data[i] == value:
            return i
    return

 

二分查找: 从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半

 

def binary_search(lst,val):
    left = 0
    right = len(lst) -1
    while left <= right:
        mid = (left + right) // 2
        if val == lst[mid]:
            return mid
        elif val < lst[mid]:
            right = mid -1
        else:
            left = mid +1
    return

 

递归版本的二分查找

def bin_search(lst,val,left,right):
    if left <= right:
        mid = (left + right) // 2
        if lst[mid] == val:
            return mid
        elif lst[mid] > val:
            return bin_search(lst,val,left,mid-1)
        else:
            return bin_search(lst,val,mid+1,right)
    else:
        return

# 上述return属于尾递归 : 编译器会优化成迭代 但是Python没有针对尾递归进行优化

 

posted @ 2019-03-20 13:27  一路向北_听风  阅读(325)  评论(0编辑  收藏  举报