Python - 递归和循环学习笔记

Recursion

基本概念

Recursive function: A function that calls itself. (直接或者间接)

递归算法解决问题的特点:

(1)递归就是在过程或函数里调用自身

(2)在使用递归策略时,必须有一个明确的递归结束条件,即Base case

(3)递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。

例如斐波那契数列:

def fibonacci(n):
    # Base Case
    if n <= 1:
        return 1
    #Recursive Case
    return (fibonacci(n-2) + fibonacci(n-1))

Call Stack

每一次function call都会生成一个新的call frame
例如:对于fibonacci()递归算法,其运算顺序如下
在这里插入图片描述

绘制Call Stack:

def skip(s):
    """Returns: copy of s
       Odd (from end) skipped"""
    result = ''
    # 从前往后一个个看,奇数项跳过
    if (len(s) % 2 = 1):
        result = skip(s[1:])
    #从前往后,偶数项return,递归计算后面字符    
    elif len(s) > 0:
        result = s[0]+skip(s[1:]) 
        
    return result

在这里插入图片描述

Numeric Examples

对于基本的数学计算,递归有一点类似于数学归纳法。已知 n=1 或 n=0(即Base Case)时的值,假设已知 n = k 时成立(即,n=k 时,方法return的是预期正确的值),可以用 n = k 的值(当Base Case有多种限制时,用自身方法表示:此时调用自身方法会进入Base Case)来表示n = k-1的值(从 k 到 k-1 运算步骤)

例题1: 从1加到n

def sum_to(n):
    """
    Returns the sum of numbers 1 to n.

        Example: sum_to(3) = 1+2+3 = 6,

    Parameter n: the number of ints to sum
    Precondition: n >= 1 is an int.
    """
    if n == 1:
        return 1
    else:
        return n + sum_to(n-1)

例题2: n的数字位数

def num_digits(n):
    """
    Returns the number of the digits in the decimal representation of n.

        Example: num_digits(0) = 1
        Example: num_digits(1356) = 4

    Parameter n: the number to analyze
    Precondition: n >= 0 is an int
    """
    if n//10 == 0:
        return 1
    else:
        return 1 + num_digits(n//10)

例题3: n每位数字之和

def sum_digits(n):
    """
    Returns the sum of the digits in the decimal representation of n.

        Example: sum_digits(0) = 0
        Example: sum_digits(345) = 12

    Parameter n: the number to analyze
    Precondition: n >= 0 is an int.
    """
    if n //10 == 0:
        return n
    else:
        return n%10 + sum_digits(n//10)

例题4: 返回n中数字2出现的次数

def number2(n):
    """
    Returns the number of 2's in the decimal representation of n.

        Example: number2(0) = 0
        Example: number2(234252) = 3

    Parameter n: the number to analyze
    Precondition: n >= 0 is an int.
    """
    if n == 0:
        return 0
    if n//10 == 0 and n%10 == 2:
        return 1
    if n//10 == 0 and n%10 != 2:
        return 0
    else:
        return number2(n%10) + number2(n//10)

例题5:

def into(n, c):
    """
    Returns the number of times that c divides n,

        Example: into(5,3) = 0 because 3 does not divide 5.
        Example: into(3*3*3*3*7,3) = 4.

    Parameter n: the number to analyze
    Precondition: n >= 1 is an int

    Parameter c: the number to divide by
    Precondition: c > 1 are ints.
    """
    if n%c != 0:
        return 0
    else:
        return 1 + into(n/c,c)

Divide and Conquer

步骤

  1. Decide what to do on “small” data
  2. Decide how to break up your data
  3. Decide how to combine your answers

例题(List & String)

  1. 返回数组中元素的和
def sum_list(thelist):
    """
    Returns the sum of the integers in list l.

        Example: sum_list([7,34,1,2,2]) is 46

    Parameter thelist: the list to sum
    Precondition: thelist is a list of ints
    """
    if thelist == []:
        return 0
    if len(thelist) == 1:
        return thelist[0]
    left = thelist[0]
    right = thelist[1:]
    return left + sum_list(right)
  1. 返回v在list中出现的次数
def numberof(thelist, v):
    """
    Returns the number of times v occurs in thelist.

    Parameter thelist: The list to count from
    Precondition: thelist is a list of ints

    Parameter v: The value to count
    Precondition: v is an int
    """
    if len(thelist)==0:
        return 0
    if len(thelist)==1:
        if thelist[0]==v:
            return 1
        else:
            return 0
    #也可以写成 return 1 if thelist[0] == v else 0
    left = thelist[0]
    right = thelist[1:]
    return numberof(left) + numberof(right)
  1. 返回一个新的数组,这个新数组的元素a都被替代成b
def replace(thelist,a,b):
    """
    Returns a COPY of thelist but with all occurrences of a replaced by b.

    Example: replace([1,2,3,1], 1, 4) = [4,2,3,4].

    Parameter thelist: The list to count from
    Precondition: thelist is a list of ints

    Parameter a: The value to replace
    Precondition: a is an int

    Parameter b: The value to replace with
    Precondition: b is an int
    """
    if len(thelist) == 0:
        return []
    if thelist[0] == a:
        return [b]
    else:
        return[thelist[0]]
    left = [thelist[0]]
    right = thelist[1:]
    return replace(left,a,b)+replace(right,a,b)
  1. 返回一个新的数组,其相邻重复的数字都被删除
def remove_dups(thelist):
    """
    Returns a COPY of thelist with adjacent duplicates removed.

    Example: for thelist = [1,2,2,3,3,3,4,5,1,1,1], return [1,2,3,4,5,1]

    Parameter thelist: The list to modify
    Precondition: thelist is a list of ints
    """
    if len(thelist) <= 1:
        return thelist
    left = [thelist[0]]
    right = thelist[1:]
    if thelist[0] ==  thelist[1]:
        return remove_dups(right)
    else:
        return left + remove_dups(right)
  1. 返回一个新的数组,删除第一次出现的元素v
def remove_first(thelist, v):
    """
    Returns a COPY of thelist but with the FIRST occurrence of v removed 
    (if present).
    
    Parameter thelist: the list to search
    Precondition: thelist is a list of ints

    Parameter v: the value to search for
    Precondition: v is an int
    """
    if len(thelist) == 0:
        return []
    if thelist[0] == v:
        return thelist[1:]
    else:
        return = [thelist[0]] + remove_first(thelist[1:],v)
  1. 返回新的数组,其中奇数在前,偶数在后
def oddsevens(thelist):
    """
    Returns a COPY of the list with odds at front, evens in the back.

    Odd numbers are in the same order as thelist. Evens are reversed.

    Example:
        oddsevens([2,3,4,5,6]) returns [3,5,6,4,2].

    Parameter thelist: The list to modify
    Precondition: thelist is a list of ints (may be empty)
    """
    if len(thelist) == 0:
        return []
    left = [thelist[0]]
    right = thelist[1:]
    if thelist[0] % 2 == 0:
        return oddsevens(right) + [left]
    else:
        return [left] + oddsevens(right)

Remark:Reversing a String or List,在recursive call写成right + left即可

  1. 返回新的,被展开的数组
def flatten(thelist):
    """
    Returns a COPY of thelist flattened to remove all nested lists

    Examples:
        flatten([[1,[2,3]],[[4],[5,[6,7]]]]) is [1,2,3,4,5,6,7]
        flatten([1,2,3]) is [1,2,3]

    Parameter thelist: the list to flatten
    Precondition: thelist is a list of either ints or lists
    """
    if len(thelist) == 0:
        return []
    left = thelist[0]
    right = thelist[1:]
    if type(left) == int:
        return [left] + flatten(right)
    else:
        return flatten(left)+flatten(right)
  1. 判断是否是回文字符
def ispalindrome(s):
    """Returns: True if s is a palindrome"""
    if len(s) <2:
        return True
    end = (s[0] == s[-1])
    middle = ispalindrome(s[1:-1])
    return end and middle
  1. 拆分(此题不再是分成left[0] + right[s:])
def split(s,delimiter):
    """Return: list of substrings of s separated by the delimiter.
    This function breaks up s into several substrings according to
    the delimiter (a single character separator like ',' or ':').
    Example: split('ab,c „ d', ',') is ['ab', 'c ', '', ' d']
             split('ab::cd ::  ef', '::')  is ['ab', 'cd ', ' ef']
             split('ab::cd ::  ef', ',') is ['ab::cd ::  ef']
             split('', ',') is ['']
    Precondition: s is a string (possibly empty).
    delimiter is a nonempty string"""
    pos = s.find(delimiter)
    if pos = -1:
        return s
    front = [s[:pos]]
    back = s[pos+len(delimiter):]
    return front + split(back,delimiter)

注意:上述9个例题均对list 或者 string 进行变换的递归算法。Python中List的优点:可以直接进行叠加(即,[2,3]+[4] >>> [2,3,4])因此我们可以先将Break up the data, 再通过连续不断call自己的方法来combine the answer。

但是,对于不可叠加的字典(dictionary) 我们需要做一些逻辑上的小改变。

例题(Dictionary & Tuple)

例题1: 返回字母在string s中出现的次数,并生成一个字典。

def histogram(s):
        """Return: a histogram (dictionary) of the # of letters in string s.
        The letters in s are keys, and the count of each letter is the value. If
        the letter is not in s, then there is NO KEY for it in the histogram.
        Example: histogram('') returns {}, while histogram('abracadabra')
        returns {'a':5,'b':2,'c':1,'d':1,'r':2}
        Precondition: s is a string (possibly empty)."""
     if len(s) == 0:
        return {}
    right = histogram(s[1:])
    if s[0] in right:
        right[s[0]] = right[s[0]] + 1
    else:
        right[s[0]] = 1
    return right

本题的难点在于:字典不可叠加。我们不能像对list一样,将每一个元素单独提出,进行判断,并在最后进行整合相加。字典的添加必须基于一个已知的字典,并在此基础上添加key和value。

我们唯一已知的字典是当len(s) == 0时的空字典(这里用到了python的一个List的特点,若s == '',则s[1:] return ‘’ instead of Error)所以,为了先生成可以进行修改的dictionary,我们首先要将字符串 s 通过调用自己的方法,将call frame一直拆分到base case,即len(s) == 0的情况。

Call Frame

Call Frame (从上到下)Return (从下往上)
histogram(‘abca’)right = {“a”: 1; “c”: 1; “b”: 1};进入判断句 ; {“a”: 2; “c”: 1; “b”: 1}
histogram(‘bca’)right = {“a”: 1; “c”: 1};进入判断句 ; {“a”: 1; “c”: 1; “b”: 1}
histogram(‘ca’)right = {“a”: 1};进入判断句 ; {“a”: 1; “c”: 1}
histogram(‘a’)right = {} ;进入判断句 ; {“a”:1}
histogram(’’)dict: {} (进入base case)

总结:对于字典这类不可以相加的类型,resursive call需要写在前面。先生成一个base dictionary(一般为空字典),并利用字典可以添加的功能,一个一个往里面添加。

类似字典这种不能相加的类型,我们还可以想到元组(Tuple)。虽然元组本质上是可以相加的(类似数组),如(1,2)+(2,3) >>> (1,2,2,3),但是元组不能修改,这也在一定程度上限制了递归算法。
Tuple的语法是利用小括号()。若只想生成一位元组,例(1),我们需要写成(1,)

例题2:

def segregate(nlist):
    """Returns: A tuple segregating nlist into negative and non-negative.
    This function returns a tuple (pos,rlist). The value rlist is a reordered copy
    of nlist where negatives come before the non-negatives. However, ordering inside
    each part (negative, non-negatives) should remain EXACTLY as it is in nlist.
    The value pos indicates the first position of a non-negative number in rlist.
    If there are no non-negative numbers, pos is -1.
    Examples: segregate([1, -1, 2, -5, -3, 0]) returns (3, [-1, -5, -3, 1, 2, 0])
            segregate([-1, -5, -3]) returns (-1, [-1, -5, -3])
    Precondition: nlist is a (possibly empty) list of numbers"""
    if nlist == []:
    	return (-1,[])
    left = [nlist[0]]
    right = segregate(nlist[1:])
    pos = right[0]
    rlist = right[1]
    if left[0]>=0:
    	if pos < 0:
    		pos += 1
    	return (pos+1,left + rlist)
    else:
    	return (pos,left+rlist)

正如我们在前面提到的,tuple是不可以修改的。可以想到我们要先将nlist一直拆到base case,再一步步return回上一级。所以本题的逻辑是:每一次call frame都return一个新的tuple,而此tuple(第n-1个call frame)的第0和1位的数字都是通过上一个(第n个call frame)return的tuple的0、1位提取、计算得到的。

例如我们call segregate([1, 0, -2, 3])

Call Frame (从上到下)left;rightReturn (从下到上)
segregate([1,0,-2,3])[1]; (2, [0, -2, 3])(3, [1, 0, -2, 3])
segregate([0,-2,3])[0]; (1, [-2, 3])(2, [0, -2, 3])
segregate([-2,3])[-2]; (1, [3])(1, [-2, 3])
segregate([3])[3];(-1,[])(1, [3])
segregate([])(-1, [])

For Loop

循环(For Loop & While Loop)和递归都是重复运算。二者可以相互替代,但在一定情况下,其中一种算法可能会更简单,更有效率。
For Loop 逻辑比较简单,这里不进行赘述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值