1.phython算法基础

python算法基础

时间复杂度

下面四组代码,哪组运行时间最短?
用什么方式来体现代码(算法)运行的快慢

运行时间根机器有关:同样的代码,每个人跑的时间不一样

print('hello world')
for i in range(n):
    print('hello world')
for i in range(n):
    for j in range(n):
        print('hello world')
for i in range(n):
    for j in range(n):
        for k in range(n):
            print('hello world')
  • 类比生活中的一些事情,估计时间:
    • 眨一下眼 一瞬间/几毫秒
    • 口算"26+67" 几秒
    • 烧一壶水 几分钟
    • 睡一觉 几小时
    • 完成一个项目 几天/几星期/几个月
    • 飞船从地球飞往月球 几年

几代表大约,分钟代表时间单位

时间复杂度:用来评估算法运行效率的一个东西
o(1) :时间复杂度的基本单位,也是最小单位,o代表大约,1代表时间复杂度单位
比如加减乘除,打印时间复杂度就是o(1)

图 36

𝑛^2

找基础语句

再看下面的场景:

print('hello')
print('world')
print('hello world')
for i in range(n):
    print('hello world')
    for j in range (n):
        print('hello world')
for i in range(n):
    for j in range (i):
        print('hello world')

第一个为o(3)
第二个为:最外层的大循环,循环一次,里层就是1+n,加上最外层的循环n次,最终为n(n+1)
换算为o(n^2+n)
第三个为:当i取0时,里层循环不执行;当i取1时,里层循环执行1次;当i取2时,里层循环执行2次;当i取3时,里层循环执3次;当i取n-1时,里层循环执行n-1次;
0+1+2+3+……+(n-1)=(n-1-0)
n/2=(n^2/2-n/2)

那上面就是我们计算出的时间复杂度了吗?
再拿生活的举例
我们会说几分钟,不会说一个3分钟,几个三分钟,3个一分钟,说三分钟又是一个确数了,单位前面不用再乘以一个数
所以第一个时间复杂度最终是o(1)
我们不会说几分钟几秒的,可以说5分钟01秒,但不会说几分钟0几秒,0几秒没有意义
所以大单位后面就不用再挂一个小单位
所以第二个最终的时间复杂度是o(n^2)
同理,第二个,小单位没有必要,n/2就没有了,剩下n2/2=1/2*n2,1/2系数也不要了,所以第三个最终的时间复杂度也是o(n^2)
图 37

如果两个代码的时间复杂度是一样的,从我们的推导来看,两个代码的运行时间有可能不一样,因为前面有一个常数系数,后面还有一个小单位
Cn^2+m

随着n的增大,时间复杂度小的会增长的越慢,时间复杂度大的会增长的越快,差异就越来越明显

继续看以下的场景

while n>1:
    print (n)
    n = n //2

n=64输出: 输出6次
64
32
16
8
4
2

n=128输出: 输出7次
128
64
32
16
8
4
2

2^6=64
log_2⁡^64=6
图 38

对数,2的几次方等于64呢
时间复杂度是o(log_2^n)或o(logn)--计算机是以二进制来计算的,可以直接简写成logn
图 39

logn就是为了高效解决问题的,出现就是保证高效的

o(1) <o(logn) <o(n)

2**64
18446744073709551616

这么大的数,循环减半,64次就处理完了

时间复杂度是用来估算算法运行时间的一个式子(单位)
一般来说,时间复杂度高的算法比复杂度低的算法慢

常见的时间复杂度(按效率排序)

o(1) < o(logn) < o(n) < o(nlogn) < o(n^2) < o(n^2logn) < o(n^3)

o(n) < o(nlogn) < o(n^2) 除以n
o(n^2) < o(n^2logn) < o(n^3) 除以n

如何一眼判断复杂度?没有递归

  • 循环减半的过程-->o(logn)
  • 几层循环就是n的几次方的复杂度

空间复杂度

空间复杂度;用来评估算法内存占用大小的一个式子
S(n)=o(n)--space
T(n)=o(n^2)--time

一个变量是o(1),5个变量是o(5),也是o(1)
如果开辟一个长度为n的列表,那就是o(n)
如果开辟一个长度为n的二维列表,那就是o(n^2)

当然空间复杂度讨论不是很多,空间可以买,时间不可以买
一般来说,空间复杂度大,时间复杂度小,反之也成立

空间换时间

递归的两个特点

  • 调用自身
  • 结束条件

程序要进入还要出去

看下面几个函数,哪个是合法的递归?
x传入一个正数

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

func1没有结束条件

def func2(x):
    if x>0:
        print (x)
        func2(x+1)

fun2结束条件不符合要求

def func3(x):
    if x>0:
        print (x)
        func3(x-1)

图 40
进来的时候打印
fun3符合递归 打印1,2,3,...,n

def func4(x):
    if x>0:
        func4(x-1)
        print (x)

图 42

出去的时候打印
fun4符合递归 打印n,n-1,n-2,2,1

f(0)要回到f(1)调用它的地方,f(1)要回到f(2)调用它的地方...

斐波那契数列

#斐波那契数列
#1 1 2 3 5 8 ...
#F(n)=F(n-1) + F(n-2) F(0)=1 F(1)=1 求第n项
def fibnacci(n): #o(2^n)
    if n==0 or n==1:
        return 1
    else:
        return fibnacci(n-1)+fibnacci(n-2)

print(fibnacci(3))

来统计下时间
编写一个统计时间的装饰器

import time

def call_time(func):
    def wrapper(*args,**kwargs):
        t1=time.time()
        result=func(*args,**kwargs)
        t2=time.time()
        print("%s running time: %s secs:" %(func.__name__,t2-t1))
        return result
    return wrapper

使用装饰器统计运行时间

from timewrap import *

#斐波那契数列
#1 1 2 3 5 8 ...
#F(n)=F(n-1) + F(n-2) F(0)=1 F(1)=1 求第n项
@call_time
def fibnacci(n): #o(2^n)
    if n==0 or n==1:
        return 1
    else:
        return fibnacci(n-1)+fibnacci(n-2)

print(fibnacci(3))

执行发现没有统计到时间

图 43

原因:给一个递归函数加装饰器会出问题
装饰器统计的是递归函数,装饰器调用的还是递归函数,那怎么办呢?
装饰器的原理是把里面的函数替换成统计的函数,原来的函数递归,替换之后还是递归
解决:
给它套一个马甲

#斐波那契数列
#1 1 2 3 5 8 ...
#F(n)=F(n-1) + F(n-2) F(0)=1 F(1)=1 求第n项

def fibnacci(n): #o(2^n)
    if n==0 or n==1:
        return 1
    else:
        return fibnacci(n-1)+fibnacci(n-2)
@call_time
def fib(n):
    return fibnacci(n)

print(fib(20))

输出:
D:\soft\python3.8\python.exe "D:/py project/DevTools/算法/python_code_basic.py"
fib running time: 0.003919839859008789 secs:
10946

Process finished with exit code 0

n取30时间
fib running time: 0.3150191307067871 secs:

n取31时间
fib running time: 0.7027747631072998 secs:
2178309
n取32时间
fib running time: 1.1029481887817383 secs:
3524578
n取33时间
fib running time: 1.726712703704834 secs:
5702887
n取34时间
fib running time: 2.6420905590057373 secs:
9227465

当n=4 2重复计算
图 44

当n=5 3重复计算
图 45

差不多多一倍,重复计算
n=6,f(6)=f(5)+f(4),需要在n=5的基础上,再拷贝一份4,4重复计算

优化--列表追加方式

@call_time
def fib2(n):#o(n)
    li=[1,1]
    for i in range(2,n+1):
        li.append(li[-1]+li[-2])
    return li[n]

print(fib2(34))

n取34时间
fib2 running time: 0.0 secs:
9227465

n取1000时间,结果很大,耗时很小
fib2 running time: 4.553794860839844e-05 secs:
70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501

时间复杂度为o(n)
空间复杂度为o(n)--列表的长度
空间复杂度有点浪费,占用了前面的n-1位

优化--移位方式

@call_time
def fib3(n):
    a=1
    b=1
    c=0
    if n==0 or n==1:
        return 1
    for i in range(2,n+1):
        c=a+b
        a=b
        b=c
    return c

n取1000时间,结果很大,耗时很小
fib3 running time: 0.0005872249603271484 secs:
70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501

时间复杂度为o(n)
空间复杂度为o(1)--空间复杂度大大减少

最终return结果是c,当n0 or n1,return1,也就是return c为1
继续优化代码,使代码变得简洁

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

n取1000时间,结果很大,耗时很小
fib4 running time: 0.0 secs:
70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501

图 46

走台阶面试题

N个台阶,一次可以走一步或者两步,求走这n个台阶有多少种方法

posted @ 2021-09-06 21:14  幸福一家  阅读(496)  评论(0编辑  收藏  举报