时间复杂度计算
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N - 1) * N;
}
int main()
{
return 0;
}
- N!为N*(N-1)*(N-2)*....*1
- 时间复杂度为O(N)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
int main()
{
return 0;
}
- 等比数列 :2^0+2^1+2^2+2^3+2^4......2^(N-1)
- 斐波拉契数列的时间复杂度为O(2^N)
复杂度的比较
- 时间复杂度能优化到O(logN),也是十分优秀的,
- 比如说:在一个中国14亿人口中找一个人(已经从小到大的排序),最多找多少次
答案是31次,2^10=1024,2^30=10亿左右,2^31=20亿左右
常见空间复杂度的计算
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Factorial(size_t N)
{
return N < 2 ? N : Factorial(N - 1) * N;
}
int main()
{
return 0;
}
-
栈帧 也会消耗空间, 时间会累积,而空间不会累积
-
递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
long long Fib(size_t N)
{
if (N < 3)
return 1;
return Fib(N - 1) + Fib(N - 2);
}
int main()
{
return 0;
}
- 时间会累积,空间不会累积
- 斐波拉契数列的空间复杂度为O(N)
如何理解时间会累积,空间不会累积?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void f1()
{
int a = 0;
printf("%p\n", &a);
}
void f2()
{
int b = 0;
printf("%p\n", &b);
}
int main()
{
f1();
f2();
return 0;
}
- 明明是不同的临时变量,可是两个的地址却是一样的(与函数栈帧有关系)
- 时间用了,就用了,时间是没有办法保留的,所以时间是会累积的
顺序表和链表(带头双向循环链表)的区别
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需改变指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
- 顺序表优点:下标随机访问,cpu高速缓存命中率高
- 顺序表缺点:头部或者中间插入删除效率低(需要挪动数据),扩容(有一定程度性能消耗,可能存在一定程度空间浪费)
- 链表优点:任意位置插入擅长的O(1),按需申请释放
- 链表缺点:不支持下标随机访问
如何理解顺序表优点中的cpu高速缓存命中率高
- 简单来说cpu的读取速度很快,而内存又很慢,所以需要借助三级缓存或者寄存器
- 内存中的数据会加载一段进缓存,cpu会从三级缓存中读取一段,
- 上面也说了,顺序表的地址一定是连续的,而链表的地址不一定是连续的
- 对于顺序表,一个命中了,后面一段一定也会命中,
- 对于链表,一个命中了,后面一段不一定也会命中,
参考文献:与程序员相关的CPU缓存知识