数据结构入门 时间 空间复杂度解析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 数据结构入门 时间 空间复杂度解析

一. 算法效率


算法效率分析分为两种:第一种是时间效率,第二种是空间效率。

时间效率被称为时间复杂度,而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间,在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。


总结经过这么久技术的发展 我们对于时间复杂度的要求高于空间复杂度的要求


二. 时间复杂度


2.1 时间复杂度的概念


时间复杂度的定义:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法执行所耗费的时间,从理论上说,是不能算出来的,只有你把你的程序放在机器上跑起来,才能知道。但是我们需要每个算法都上机测试吗?是可以都上机测试,但是这很麻烦,所以才有了时间复杂度这个分析方式。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。


2.2 大O的渐进表示法


我们直接上题目


// 请计算一下Func1基本操作执行了多少次?
void Func1(int N) {
int count = 0;
for (int i = 0; i < N ; ++ i) {
 for (int j = 0; j < N ; ++ j)
 {
       ++count;
 }
 for (int k = 0; k < 2 * N ; ++ k) {
 ++count;
}
int M = 10;
while (M--) {
 ++count; }
printf("%d\n", count);
}


我们经过计算可以发现 这个函数需要运算的次数是 n的平方 加上 2*n 再加上10


当我们的n等于10的时候 它的次数是130


放我们的n是100的时候 它的次数是10210


当我们的n是1000的时候 它的次数是1002010


我们可以发现 它执行的次数实际上只跟n的平方有强相关性


这个时候我们只需要了解到这个程序的最高次项 我们就能估算出这个程序所需要运行的数量级


那么这里就能够引生出我们的大O的渐进表示法


推导大O阶方法:

1、用常数1取代运行时间中的所有加法常数。


2、在修改后的运行次数函数中,只保留最高阶项。


3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。


使用大O的渐进表示法以后,Func1的时间复杂度为:


当n等于10的时候 它运行的次数就是100


当n等于100的时候 它运行的次数就是10000


当n等于1000的时候 它运行的次数就是1000000


此外 如果说随着我们输入的不同空间复杂度也有所改变的话 那么我们取最坏的空间复杂度作为我们的复杂度


2.3 题目练习


题目一


void Func2(int N) {
int count = 0;
for (int k = 0; k < 2 * N ; ++ k) {
 ++count; }
int M = 10;
while (M--) {
 ++count; }
printf("%d\n", count);
}


它计算的此时是2N+10


实际上的复杂度就是O(N)


题目二


void Func3(int N, int M) {
int count = 0;
for (int k = 0; k < M; ++ k) {
 ++count; }
for (int k = 0; k < N ; ++ k) {
 ++count; }
printf("%d\n", count);
}


它的计算的次数是 M+N次


实际的空间复杂度就是O(max(M,N))


题目三


void Func4(int N) {
int count = 0;
for (int k = 0; k < 100; ++ k) {
 ++count; }
printf("%d\n", count);
}

这里实际上和N没有关系 它会计算100次


所以说它的次数是一个常数


常数的时间复杂度是O(1)


题目四


// 计算BubbleSort的时间复杂度?
void BubbleSort(int* a, int n) {
assert(a);
for (size_t end = n; end > 0; --end) {
 int exchange = 0;
 for (size_t i = 1; i < end; ++i)
 {
 if (a[i-1] > a[i])
 {
 Swap(&a[i-1], &a[i]);
 exchange = 1;
 }
 }
 if (exchange == 0)
 break; }}


这里实际运行的次数是end的平方-n之后除以2


所以说 它的时间复杂度是O(N^2)


题目五


int BinarySearch(int* a, int n, int x) {
assert(a);
int begin = 0;
int end = n-1;
while (begin < end) {
 int mid = begin + ((end-begin)>>1);
 if (a[mid] < x)
 begin = mid+1;
 else if (a[mid] > x)
 end = mid;
 else
 return mid; }
return -1; }


它最好的运算情况是一次


运算最坏的情况是logN


所以说它的时间复杂度是logN


题目六


long long Factorial(size_t N) {
return N < 2 ? N : Factorial(N-1)*N; }
• 1
• 2


实际上它的它的空间复杂度是O(N)


标题七


long long Fibonacci(size_t N) {
return N < 2 ? N : Fibonacci(N-1)+Fibonacci(N-2);
}


实际上它的复杂度是 2的0次方加上2的一次放+…+2的N-1次方减去N


实际上它的它的空间复杂度是O(2^N)


实例答案及分析:


实例1基本操作执行了2N+10次,通过推导大O阶方法知道,时间复杂度为 O(N)

实例2基本操作执行了M+N次,有两个未知数M和N,时间复杂度为 O(N+M)

实例3基本操作执行了10次,通过推导大O阶方法,时间复杂度为 O(1)

实例4基本操作执行最好1次,最坏N次,时间复杂度一般看最坏,时间复杂度为 O(N)

实例5基本操作执行最好N次,最坏执行了(N*(N+1)/2次,通过推导大O阶方法+时间复杂度一般看最

坏,时间复杂度为 O(N^2)

实例6基本操作执行最好1次,最坏O(logN)次,时间复杂度为 O(logN) ps:logN在算法分析中表示是底

数为2,对数为N。有些地方会写成lgN。(建议通过折纸查找的方式讲解logN是怎么计算出来的)

实例7通过计算分析发现基本操作递归了N次,时间复杂度为O(N)。

实例8通过计算分析发现基本操作递归了2N次,时间复杂度为O(2N)。(建议画图递归栈帧的二叉树

讲解)


三. 空间复杂度


空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟实践复杂度类似,也使用大O渐进表示法。


3.1 题目练习


题目一


void BubbleSort(int* a, int n) {
 assert(a);
 for (size_t end = n; end > 0; --end)
 {
 int exchange = 0;
 for (size_t i = 1; i < end; ++i)
 {
 if (a[i-1] > a[i])
 {
 Swap(&a[i-1], &a[i]);
 exchange = 1;
 }
 }
 if (exchange == 0)
 break;
 }
}


实际上冒泡排序并没有占用额外的空间


所以说它使用的空间是常量级别的


所以说它的空间复杂度是O(1)


题目二


long long* Fibonacci(size_t n) {
 if(n==0)
 return NULL;
 long long * fibArray =
 (long long *)malloc((n+1) * sizeof(long long));
 fibArray[0] = 0;
 fibArray[1] = 1;for (int i = 2; i <= n ; ++i)
 {
 fibArray[i ] = fibArray[ i - 1] + fibArray [i - 2];
 }
 return fibArray ;
}


这里用来计算fib数 开辟了n+1个空间


实际上的空间复杂度是O(N)


题目三


long long Factorial(size_t N) {
 return N < 2 ? N : Factorial(N-1)*N; }


它实际上开辟了N到1数量的空间


所以说它的空间复杂度是O(N)


但是有的同学会认为这个跟时间复杂度一样是O(N^2)


实际上这里我们要明确一点


时间是不能复用的

空间是可以复用的


以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够


不吝赐教 在评论区或者私信指正 博主一定及时修正


那么大家下期再见咯

相关文章
|
2月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
2月前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
93 3
|
2月前
|
机器学习/深度学习 数据采集 数据挖掘
Python编程语言的魅力:从入门到进阶的全方位解析
Python编程语言的魅力:从入门到进阶的全方位解析
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析 - llm的输入
本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。
642 1
|
2月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
3月前
|
JSON JavaScript 前端开发
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
深入解析ESLint配置:从入门到精通的全方位指南,精细调优你的代码质量保障工具
117 0
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
241 9
|
2月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
40 1
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
71 5
|
2月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。

推荐镜像

更多