数据结构算法分析
算法分析概述
算法分析,分析算法占用的两类资源:
- CPU时间 → 时间性能分析
- 内存空间 → 空间性能分析
时间复杂度一般是看循环几次。时间复杂度越高,时间用的就越长。
算法分析的目的:分析算法的时空效率,以便改进算法性能。
算法效率的度量:
- 时间复杂度
- 空间复杂度
例题
算法分析的目的是()
A.找出数据结构的合理性
B.研究算法中输入和输出的关系
C.分析算法的效率以求改进
D.分析算法的易读性和可行性
答案:C
示例:设计算法求 1+(1+2)+(1+2+3)+...+(1+2+...+n), n>2
版本1:双重循环
c
int Sum1(int n)
{
int sum = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
sum = sum + j;
}
}
return sum;
}
版本2:一重循环
c
int Sum2(int n)
{
int sum = 0;
int s = 0;
for(int i=1;i<=n;i++)
{
s = s + i;
sum = sum + s;
}
return sum;
}
对比:双重循环时间复杂度更高;一重循环优化后效率更好。
1.3.2 算法时间复杂度分析
一个算法由控制结构(顺序、分支、循环) 和原操作构成。
- 原操作:固有数据类型的操作,如赋值、比较、算术运算 + - * /
- 顺序结构:按照顺序依次处理
- 分支结构:根据条件判断,改变执行路径
- 循环结构:当条件成立时,反复执行指定处理
示例代码
c
void fun(int a[], int n)
{
for(int i=0;i<n;i++)
{
a[i]=2*i;
for(int j=0;j<i;j++)
{
printf("%d ",a[i]);
}
printf("\n");
}
}
两种算法分析方式
① 事后分析统计方法
编写对应程序,统计实际运行时间。
缺点:
- 和机器性能有关(高性能计算机 vs 单片机)
- 和编程语言有关,高级语言执行效率低
- 和编译产生的机器指令质量有关
- 有些算法不能事后统计(如导弹控制系统)
② 事前估算分析方法
抛开机器、语言等外部因素,把算法执行时间看作问题规模n的函数。
算法时间复杂度:事前预估算法时间开销 T(n) 和问题规模 n 的关系。
T = time。
时间复杂度记号 O
前面这个O绝对不要忘记写!!!
T(n)=O(f(n))
读作:T(n)是f(n)的大O阶
含义:随问题规模n增大,算法执行时间的增长率和f(n)增长率相同。
大O是渐近上界,取最小的上界;忽略常数因子、低阶项。
例子:语句频度计算
c
//示例:爱你算法
void love(int n)
{
int i = 1;
while(i<=n)
{
printf("I love you\n");
i = i + 1;
}
printf("I love you More Than %d\n",n);
}
统计语句执行次数(频度):
- int i=1; → 1次
- while(i<=n) → n+1次
- printf("I love you\n"); → n次
- i=i+1; → n次
- printf("I love you More Than %d\n",n); →1次
T(n)=1+(n+1)+n+n+1 = 3n+3
当n足够大,常数项可以忽略:(就是这里的3,并且这里的最高次幂前面的系数也可以省略,就是这里的3n前面的系数3
再eg:f(n)=2n³+3n²+2n+1,这里只剩下n³,它是最高次幂,其他的都可以忽略。
)
T(n)=3n+3 = O(n)
化简规则:
- 常数项可以忽略;
- 只保留最高次幂;
- 最高次幂前面的系数忽略。
例:
- T(n)=n²+3n+1000 ⇒ O(n²)
- T(n)=5n³+999999 ⇒ O(n³)
循环嵌套时间复杂度计算
- 单层循环:循环执行次数和n成正比 ⇒ O(n)
- 两层嵌套循环:内外循环都和n相关 ⇒ O(n²)
- 三层嵌套循环 ⇒ O(n³)
示例1:两层循环
c
void fun(int n)
{
int i,j;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
printf("*");
}
}
}
外层循环n次,内层循环每次n次,总次数 n×n = n²,T(n)=n²,时间复杂度O(n²)
示例2:循环变量不断翻倍
c
void fun(int n)
{
int i=1;
while(i<=n)
{
i = i * 2;
}
}
i:1,2,4,8,...,2ᵏ ≤ n
2ᵏ ≤ n ⇒ k ≤ log₂n
时间复杂度:O(log n)
最好、最坏、平均时间复杂度
- 最坏时间复杂度:最坏输入情况下,算法的执行时间。考试最常考。
- 最好时间复杂度:最好输入情况下。
- 平均时间复杂度:所有输入等概率下的平均执行时间。
做题默认求最坏时间复杂度。
常见时间复杂度大小排序(重点)
O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ)
- O(1):常数阶,和n无关
- O(log n):对数阶
- O(n):线性阶
- O(n log n):线性对数阶
- O(n²):平方阶
- O(n³):立方阶
- O(2ⁿ):指数阶(效率很差)
例题
某算法时间复杂度为O(n²),表明该算法()
A.问题规模是n²
B.执行时间等于n²
C.执行时间与n²成正比
D.问题规模与n²成正比
答案:C(要说就说与它成正比。)
例题
下面算法时间复杂度最高的是()
A.O(log₂n) B.O(n) C.O(n²) D.O(2ⁿ)
答案:D
排序:O(log₂n) < O(n) < O(n²) < O(2ⁿ)
例题(真题)
c
count=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
count++;
时间复杂度:O(n²)
例题
c
void fun(int n)
{
int i=0,s=0;
while(s<n)
{
i++;
s = s + i;
}
}
分析:
s=1+2+3+...+i = i(i+1)/2 < n
i² ≈ 2n ⇒ i ≈ √(2n)
时间复杂度:O(√n)
时间复杂度做题通用步骤(记下来)
- 找出基本原操作(最内层循环里面的语句)
- 统计基本操作执行次数,得到 T(n)
- 去掉常数、低次项、系数,得到大O表示。
- 多层循环:看每层循环的次数,相乘。
小技巧:
- 一层循环:O(n)
- 两层嵌套:O(n²)
- 循环变量乘2/除2:O(log n)
空间复杂度
空间复杂度:算法执行过程中占用的存储空间,记为 S(n)
S(n)=O(f(n))
- 空间复杂度O(1):原地算法,额外空间不随n变化。
- 递归算法空间复杂度要看递归深度。
背诵总结
- 算法分析:分析时间复杂度、空间复杂度。
- 大O记号:只看最高次项,忽略常数、系数、低次项。
- 常见复杂度大小:O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ)
- 单层循环 O(n);双层嵌套 O(n²);循环成倍增长 O(log n)
- 考试优先求最坏时间复杂度。