王道视频-数据结构-笔记1:时间复杂度与空间复杂度


0 笔记说明

来源于2020 王道考研 数据结构,博客内容是对自己笔记的书面整理,根据自身学习需要,我可能会增加必要内容。


1 基本知识

【程序=数据结构+算法】,其中数据结构是要处理的信息,算法是处理信息的步骤。

1.1 数据结构三要素

(1)逻辑结构,包括集合、线性结构、树形结构、图状结构(也称为网状结构);

(2)物理结构(也称为存储结构),包括顺序存储、链式存储、索引存储、散列存储;

(3)数据上的运算

对存储结构需要重点理解的也是三点:

(1)若采用顺序存储,则各个数据元素在物理上必须是连续的;若采用非顺序存储,则各个数据元素在物理上可以是离散的;

(2)数据的存储结构会影响存储空间分配的方便程度;

(3)数据的存储结构会影响对数据运算的速度。

1.2 算法的五大特性

(1)有穷性,算法在有穷时间内能执行完;

(2)确定性,相同输入只会产生相同输出;

(3)可行性,可以用已有的基本操作实现算法;

(4)输入,让算法处理的数据;

(5)输出,算法处理的结果。

1.3 设计算法考虑这些

(1)正确性,能正确解决问题;

(2)可读性,对算法的描述要让其他人也看得懂;

(3)健壮性,算法能处理一些异常状况;

(4)高效率与低存储量需求,算法执行省时、省内存,空间复杂度与时间复杂度低。


2 复杂度

2.1 时间复杂度

算法的时间复杂度考虑的是去预估算法的时间(Time)开销T(n)与问题规模n的关系。使用大O记法来表示时间复杂度T(n),如问题的规模为f(n)=n3+n2+10时,T(n)=O(f(n))=O(n3+n2+10)。而时间复杂度只需要考虑阶数最高的项即可,所以T(n)=O(f(n))=O(n3+n2+10)=O(n3),即此时的时间复杂度为O(n3)。

时间复杂度的运算有两条规则:

(1)加法法则:多项相加时,只保留最高阶项,且系数为1,公式为:
在这里插入图片描述
(2)乘法法则:多项相乘时都保留,公式为:
在这里插入图片描述
下面是复杂度排序表:
在这里插入图片描述
计算时间复杂度的三个步骤:

(1)找一条关键语句,如果有循环则找最深层循环中的关键语句;

(2)分析该语句的执行次数x与问题规模n的关系,即x=f(n);

(3)算法的时间复杂度T(n)=O(x)=O(f(n))。

示例代码片段1:

n = int(input()) # n为问题规模
count = 1 # 计数变量
while(count <= n): # 第一层循环执行n次
    count = count + 1
    for j in range(n): # 第二层循环
        print('helloworld') # 第二层循环执行n*n次

计算时间复杂度时,不需要考虑顺序执行的代码,并且有循环时只需要挑选循环中的一条关键语句去分析其执行次数与规模n的关系,若还有深层循环,只需要关注最内层循环。上述代码的时间复杂度为O(n2)。

示例代码片段2:

n = int(input()) # n为问题规模
count = 1 # 计数变量
while(count <= n):
    count = count * 2 # 关键语句
    print('helloworld')

上述代码中,第四行为循环的关键语句,count为2、4、8、16…2x…时,该语句执行的次数分别为1、2、3、4…x…假设在该关键语句执行x次时循环结束,此时count=2x>n,即x>log2n,取x=log2n,此时T(n)=O(n)=O(log2n)。

示例代码片段3:

import random

n = int(input()) # n为问题规模
list1 = [i+1 for i in range(n)] # 第一层循环
random.shuffle(list1) # 打乱列表
for i in range(n): # 第二层循环
    if list1[i] == n:
        print('OK!')
        break

注意上述代码中有两个循环,但不是互相嵌套的,所以分别求解:对于第一个循环来说,只有一条语句,很明显语句在规模n下执行了n次,即O(n);对于第二个循环来说,程序的输入会影响算法时间复杂度,第8行为关键语句,有三种情况:① 最好为O(1),此时元素n在第一个位置;② 最差为O(n),此时元素n在倒数第一个位置;③ 平均来说,假设n在任意一个位置的概率均为1/n,则循环次数x=(1+2+…+n)·1/n=(1+n)/2,即T(n)=O(f(n))=O((1+n)/2)=O(n)。一般来说,只考虑平均情况和最坏情况。综上所述,对于第一个循环来说T(n)=O(n);对于第二个循环来说T(n)=O(n)。所以整个代码的复杂度为T(n)=O(n)+O(n)=O(n)。

有时候程序的输入会影响算法运行的时间,这时需要考虑平均时间复杂度,即认为所有输入数据都以等概率出现。

2.2 空间复杂度

算法的空间复杂度考虑的是去预估算法的内存空间(Space)开销S(n)与问题规模n的关系,使用大O记法来表示空间复杂度S(n),S(n)=O(f(n))。

计算空间复杂度的三个步骤:

(1)找到所占空间大小与问题规模n相关的变量,有的算法各层函数所需存储空间不同,分析方法略有区别;

(2)分析所占空间x与问题规模n的关系x=f(n),若有递归程序,找到递归调用的深度x与问题规模n的关系x=f(n);

(3)x的数量级O(x)就是算法空间复杂度S(n)。

除此之外,时间复杂度的加法法则、乘法法则、复杂度排序表同样适用于空间复杂度的运算。

示例代码片段1:

n = int(input()) # n为问题规模
count = 1 # 计数变量
while(count <= n):
    count = count + 1
    print('helloworld')

在上述代码中,没有与问题规模n相关的变量,只有两个变量n和count,即无论问题规模n为多少,算法运行的内存空间均是固定常量,即空间复杂度为S(n)=O(1),此时算法所需内存空间为常量,称算法原地工作

示例代码片段2:

n = int(input()) # n为问题规模
count = 1 # 计数变量
list1 = list(range(n))
... # 省略后续代码

在上述代码中,与问题规模n相关的变量为list1,其所需内存空间与n成正比,所以空间复杂度为S(n)=O(n)。

示例代码片段3:

n = int(input()) # n为问题规模
count = 1 # 计数变量
list1 = list(range(n))
list2 = [i for j in range(n) for i in range(n)]
... # 省略后续代码

在上述代码中,与问题规模n相关的变量为list1、list2,其所需内存空间为n、n2,所以空间复杂度为S(n)=O(n2+n)=O(n2)。

示例代码片段4:

def f(n):
    if n > 1:
        f(n-1) # 递归
    print(n)

在上述代码中有递归程序,但是没有与问题规模n相关的变量,而递归深度为n,所以S(n)=O(n),即空间复杂度就等于递归调用的深度。

示例代码片段5:

def f(n):
    list1 = list(range(n)) # 与问题规模n相关的变量
    if n > 1:
        f(n-1) # 递归
    print(n)

在上述代码中有递归程序,与问题规模n相关的变量为list1,而递归深度为n。当问题规模为n时,第1层递归所需内存空间为n(因为list(range(n))),第2层递归所需内存空间为n-1(因为list(range(n-1)))、第3层递归所需内存空间为n-2(因为list(range(n-2)))…第n层递归所需内存空间为1(因为list(range(1)))。故x=f(n)=(1+n)·n/2,所以S(n)=O(f(n))=O((1+n)·n/2)=O(n2)。


END

猜你喜欢

转载自blog.csdn.net/qq_40061206/article/details/112434755