蓝桥杯 算法很美 笔记 2.递归与算法分析(Python实现)

本文介绍了递归的基本概念和常见应用,包括求阶乘、打印数列、数组求和、字符串翻转、斐波那契数列、最大公因数、插入排序、汉诺塔、二分查找以及希尔排序等。同时,探讨了算法复杂度评估,如时间复杂度和排序稳定性,并提供了几个算法设计题目及解决方案,如小白上楼梯问题和旋转数组的最小数字寻找。
摘要由CSDN通过智能技术生成

2.递归与算法分析

目录

2.递归与算法分析

递归习题

1.求n的阶乘

2.打印i到j

3.对数组元素求和

4.翻转字符串

5.斐波拉数列

6.辗转相除求最大公因数

7.递归形式插入排序

8.汉诺塔 

9.二分查找递归解法

10.希尔排序

算法分析

1.评估算法复杂度

2.2的幂表

3.递归算法复杂度

4.排序算法的稳定性

 5.题1:小白上楼梯(递归设计)

6.题2 :旋转数组的最小数字(改造二分法)

7.题3 :在有空字符串的有序字符串数组中查找

8.题4 :最长连续递增子序列(部分有序)

9.题5:设计一个高效的求a的n次幂的算法


递归习题

  • 递归设计经验
    找重复(子问题)
    找重复中的变化量→参数
    找参数变化趋势→设计出口

  • 练习策略
    循环改递归
    经典递归
    大量练习,总结规律,掌握套路
    找到感觉,挑战高难度

1.求n的阶乘

'''
*f1(n): 求n的阶乘 -->f1(n - 1)
求n - 1
的阶乘
*找重复:n * (n - 1)
的阶乘, 求n - 1
的阶乘是原问题的重复(规模更小)——子问题
*找变化:变化的量应该作为参数
*找边界:出口 '''

def f1(n):
    if ( n == 1):
        return 1
    else:
        return n * f1(n - 1)


print(f1(5))

2.打印i到j

'''
   * 打印i到j
   * 找重复:
   * 找变化:变化的量应该作为参数
   * 找边界:i大于j
 '''

def f2(i,j):
    if ( i>j):
        return
    else:
        print(i)
        f2(i+1,j)

f2(1,5)

3.对数组元素求和

def f(list1,begin):
    if (len(list1)-1==begin):
        return list1[begin]
    else:
        return list1[begin]+f(list1,begin+1)

list1=[1,2,3,4,5,6,7]
print(f(list1,0))

4.翻转字符串

def f(str1,end):
    if (end==0):
        return ''+str1[end]
    else:
        return str1[end]+f(str1,end-1)


str1='abcd'
print(f(str1,3))

5.斐波拉数列

# 斐波那契第n项
def fib(n):
    if(n==1 or n==2):
        return 1
    else:
        return fib(n-1)+fib(n-2)

print(fib(5))

6.辗转相除求最大公因数

def gcd(m,n):
    if(n==0):
        return m
    else:
        return gcd(n,m%n)
print(gcd(4,2))

7.递归形式插入排序

对数组0~倒数第一个排序等价于:
对数组的0~倒数第二个元素,这部分排序
然后把是后一个元素插入到这个有序的部分中

def insertSort(arr,  k) :
    if (k == 0) :
        return;

    # 对前k-1个元素排序
    insertSort(arr, k - 1);
    # 把位置k的元素插入到前面的部分
    x = arr[k];
    index = k - 1;
    while (index > -1 and x < arr[index]) :
        arr[index + 1] = arr[index]
        index-=1

    arr[index + 1] = x

8.汉诺塔 

1-N从A移动到B,C作为辅助
等价于:
1、1~N-1从A移动到C,B为辅助
2、把N从A移动到B
3、1-N-1从C移动到B,A为辅助

'''
* 将N个盘子从source移动到target的路径的打印
* N      初始的N个从小到达的盘子,N是最大编号
* source 原始柱子
* target 辅助的柱子
* help   目标柱子
'''
def printHanoiTower( N, source, target, help) :
    if (N == 1):
        print("move " + str(N) + " from " + source + " to " + target)
    else :
        printHanoiTower(N - 1, source, help, target) # 先把前N-1个盘子挪到辅助空间上去
        print("move " + str(N) + " from " + source + " to " + target)  # N可以顺利到达target
        printHanoiTower(N - 1, help, target, source) # 让N-1从辅助空间回到源空间上去


printHanoiTower(2,'A','B','C')  # 从1-N从A移动到B,C为辅助

9.二分查找递归解法

全范围内二分查找
等价于三个子问题:
左边找(递归)
中间比
右边找(递归)
注意:左查找和右查找只选其一

def binarySearch(arr, low, high, key):   #(排序后的数组,最小索引,最大索引,查找值),返回索引值
    if low > high:
        return -1
    mid = low + ((high - low) >> 1)
    midVal = arr[mid]
    if midVal < key:  # 中间值小于查找值,查找右边
        return binarySearch(arr, mid + 1, high, key)
    elif midVal > key:  # 中间值大于查找值,查找左边
        return binarySearch(arr, low, mid - 1, key)
    else:
        return mid

10.希尔排序

排序:冒泡                         选择                            插入                            希尔

           交换                     找最大最小                  挪动数组              插入排序的更改版本 

思路:

  • 如序列 9 8 7 6 5 4 3 2 1 
  • 确定一个增量序列,(length(arr)/2)4-2-1,从大到小使用增量
  • 使用第一个增量4,将序列划分为若干个子序列,下标组合为0-4-8,1-5,2-6,3-7
  • 依次对子序列使用直接插入排序法
  • 使用第二个增量2,将序列划分为若干个子序列(0-2-4-6-8),(1-3-5-7)
  • 依次对子序列使用直接插入排序法:
  • 使用第三个增量1,这时子序列就是元序列(0-1-2-3-4-5-6-7-8),使用直接插入法完成排序。

 

Alt

 在 for 循环中,由于每组的第一个元素不用进行插入排序,而它们的下标处于 0~step-1,所以从下标 step 开始遍历。

import random
def shell(nums):
    n = len(nums)  # 获得长度
    step = n // 2    # 增量序列 (length(arr)/2) 4-2-1
    while step > 0:
        for i in range(step, n): # 遍历需要进行插入排序的数
            while i >= step and nums[i - step] > nums[i]:  # 对每组进行插入排序
                nums[i - step], nums[i] = nums[i], nums[i - step]  # 互换位置
                i -= step
        step //= 2
    return nums
arr=[random.randint(0, 100) for _ in range(8)]
print(arr)
print(shell(arr))

算法分析

1.评估算法复杂度

O表示法,忽略低阶项,忽略常数因子。

  • n!的弱上界是n^n,因此增长速度非常快,这意味着单位时间内可求解的问题很小,换言之,超慢
  • 2^n这样的指数函数增长非常快,这种算法可以认为超慢
  • O(n2)和O(n3)增长很快,算法很慢,至少优化到nlgn,O(n2)的有冒泡排序,直接插入排序,选择排序
  • nlgn可以认为是及格的算法吧,一般分治法可以缩小层数为lgn,而每层的复杂度一般为O(n),例如归并排序算法、快速排序算法
  • O (n)叫做线性算法,这种算法比较优秀,或者问题本身比较简单,比如求连续求和最大子数组的线性解
  • O(sqrt(n))当然比O(n)更快,不是没有,但这种很少
  • lgn就是很优秀的算法了,比如二分查找法,但是这种算法往往对输入数据的格式是有要求的,二分查找要求输入数据有序
  • 还有一种是常量,无论规模怎么扩大,都花固定时间,这是为数极少的效率最高的算法了,多数是数据很规则

2.2的幂表

3.递归算法复杂度

递归关系结果举例
T(n)=T(n/2)+O(1)T(n)=O(logn)二分查找,辗转相除最大公因数
T(n)=T(n-1)+O(1)T(n)=O(n)线性查找
T(n)=2T(n/2)+O(1)T(n)=O(n)
T(n)=2T(n/2)+O(n)T(n)=O(nlogn)归并、快排
T(n)=2T(n/2)+O(nlogn)T(n)=O(n(logn)^2)
T(n)=T(n-1)+O(n)T(n)=O(n^2)选择排序、插入排序
T(n)=2T(n-1)+O(1)T(n)=O(2^n)汉诺塔
T(n)=T(n-1)+T(n-2)+O(1)T(n)=O(2^n)递归的斐波那契

4.排序算法的稳定性

  • 稳定:如果a原本在b前面,而a=b ,排序之后a仍然在b的前面。
  • 不稳定:如果a原本在b的前面,而a=b ,排序之后a可能会出现在b的后面。

 

 5.题1:小白上楼梯(递归设计)

小白正在上楼梯,楼梯有n阶台阶,小白一次可以上1阶,2阶或者3阶,实现一个方法,计算小白有多少种走完楼梯的方式。

提示:设n阶台阶的走法数为f(n)。如果只有1个台阶,走法有1种(一步上1个台阶),即f(1)=1;如果有2个台阶,走法有2种(一种是上1阶,再上1阶,另一种是一步上2阶),即f(2)=2;如果有3个台阶,走法有4种(一种每次1阶,共一种;另一种是2+1,共两种;第三种是3,共1种),即f(3)=4;

当有n个台阶(n>3)时,我们缩小问题规模,可以这样想:最后一步有三种情况,走1步(之前上了n-1个台阶,走法为f(n-1)种),走2步(之前上了n-2个台阶,走法为f(n-2)种),走3步,(之前上了n-1个台阶,走法为f(n-3)种,f(n)=f(n-1)+f(n-2)+f(n-3),n>3。
 

def  up_stairs(n):
        if(n==1):
            return 1
        if(n==2):
            return 2
        if(n==3):
            return 4
        else:
            return up_stairs(n-1)+up_stairs(n-2)+up_stairs(n-3)

print(up_stairs(4))

6.题2 :旋转数组的最小数字(改造二分法)

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一 个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一 个旋转,该数组的最小值为1。

解题思路:利用二分法,最小值一定藏在无序的那一边

"""
3 4 /5 1 2   ——>  5 /1 2   ——> 5 1 ——> 1 只剩两个元素,最小值一定是右边元素
"""
def f(arr):
    be = 0
    end = len(arr) - 1
    # 没有旋转直接返回第一个
    if (arr[be] < arr[end]):
        return arr[be]
    while (be + 1 < end):  # 只剩两个元素就退出循环返回右边元素
        mid = be + ((end - be) >> 1)
        if (arr[mid] >= arr[be]):  # 左边有序,最小值在右边(无序)
            be = mid
        else:
            end = mid
    return arr[end]  # 最后剩两个元素,右边的位最小值
arr=[4,5,6,2,3]
print(f(arr))

7.题3 :在有空字符串的有序字符串数组中查找

有个排序后的字符串数组,其中散布着一些空字符串,编写-一个方法,找出给定字符串(肯定不是空字符串)的索引。

  1. begin(0)         end(数组长度减一)

  2. while(begin<end)

    1. 取中值
    2. 对于出现空串的处理(中值如果是空格就右移动)
    3. 比较改变begin或end
  3. return -1(没找到)

def index(arr, p):
    begin=0
    end=len(arr)-1
    while(begin<end):
            # 取中值
            mid=begin+((end-begin)>>1)
            # 对于出现空串的处理
            while(arr[mid]==''):
                mid=mid+1
                if(mid>end):    # 防止死循环
                    return -1;
            #比较改变begin或end
            if(arr[mid]>p):
                end=mid-1
            elif(arr[mid]<p):
                begin=mid+1
            else:
                return mid
    return -1

arr = ["a", "", "ac", "", "ad", "b", "", "ba"]
res = index(arr, "a")
print(res)

8.题4 :最长连续递增子序列(部分有序)

(1,9,2,5,7,3,4,6,8,0)中最长的递增子序列为(3,4,6,8)。

def findLengthOfLCIS(nums):
        if (len(nums) == 0):
            return 0
        Max = 0
        count = 1
        index = 0  # 记录最长连续字段的开始索引
        for i in range (len(nums)-1):
            if(nums[i]<nums[i+1]):
                count=count+1
            else:
                Max=max(count,Max)
                index=i+1-Max   #获得索引
                count=1
        Max2 = max(count, Max)  # 判断最后哪一个连续字段是否最长
        if Max!=Max2:   # 说明最长序列是最后的连续字段
            index =  index=len(nums)-Max2  # 得到索引
        return Max2,index

nums = [1,3,2,4,7]
length,index = findLengthOfLCIS(nums)
print(length)
for i in range(length):
    print(nums[index])
    index=index+1

9.题5:设计一个高效的求a的n次幂的算法

def Pow(a,  n):
    if (n == 0):
        return 1
    res = a
    ex = 1
    # 能翻
    while ((ex << 1) <= n):
      res = res * res      # 翻
      ex <<= 1    # 指数翻倍
    # 不能翻
    # 差n-ex次方没有去乘到结果里面
    return res * Pow(a, n - ex)
print(Pow(2,15))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值