DS:时间复杂度和空间复杂度

                                                         创作不易,感谢三连!

一、算法

1.1 什么是算法

     算法(Algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出。简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果。

1.2 算法的效率

     算法在编写成可执行程序的时候,运行的时候需要耗费时间资源和空间资源。因此衡量一个算法的效率,就是从时间和空间两个维度来衡量的,我们把他细分出了两个概念——时间复杂度和空间复杂度。

     时间复杂度主要衡量一个算法的运行快慢,而空间复杂度主要衡量一个算法运行所需要的额外空间。在计算机发展的早期,计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的迅速发展,计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复杂度。也就是说,现如今的我们判断算法的好坏重点是判断他的时间复杂度,在条件允许的情况下,我们也会非常乐意用空间去换时间。

二、时间复杂度

2.1 时间复杂度的概念

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

即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。

// 请计算一下Func1中++count语句总共执行了多少次?
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);
}

Func1时间复杂度:F(N)=N^2+2*N+10

N = 10时,F(N) = 130
N = 100时,F(N) = 10210
N = 1000时,F(N) = 1002010

当N取越大时,2*N以及10对F(N)的影响越来越小,而影响最大的是N^2,所以引入了大O的渐进表示法,即计算一个大概的次数就行。

2.2 大O的渐进表示法

大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。(函数中只有常数)
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
使用大O的渐进表示法以后

Func1的时间复杂度为:O(N)

N = 10时,F(N) = 100
N = 100时,F(N) = 10000
N = 1000时,F(N) = 1000000

大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。

另外有些算法的时间复杂度存在最好、平均和最坏情况:
 最坏情况:任意输入规模的最大运行次数(上界)
 平均情况:任意输入规模的期望运行次数
 最好情况:任意输入规模的最小运行次数(下界)
例如:在一个长度为N数组中搜索一个数据x
 最好情况:1次找到
 最坏情况:N次找到
 平均情况:N/2次找到
在实际中一般情况关注的是算法的最坏运行情况,所以数组中搜索数据时间复杂度为O(N)

2.3 为什么要考虑最坏情况(卡瑞尔公式)

        我是这样理解的:时间复杂度也是人为设计的,参考了心理学上的卡瑞尔公式,即"接收最坏的,往往才能有最好的"

卡瑞尔公式:强迫自己接受最坏的情况,首先在精神上接受它,然后集中精力从容解决问题,从根本上抹除忧虑,甚至有时候能给你带来惊喜。

最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长 。

三、空间复杂度

空间复杂度也是一个数学表达式,是对一个算法在运行过程中临时占用存储空间大小的量度 。

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

注意:函数运行时所需要的栈空间(存储参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定。

四、常见的复杂度对比

五、时间复杂度和空间复杂度例题

特点:时间一去不复返,但是空间可以重复利用!!

// 计算Func3的时间复杂度?
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);
}

O(N)

// 计算Func4的时间复杂度?
void Func4(int N)
{int count = 0;for (int k = 0; k < 100; ++ k){++count;}printf("%d\n", count);
}

O(1)

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if(0 == N)return 1;return Fac(N-1)*N;
}

每次调用函数都是O(1)的复杂度,调用N次就是O(N)的复杂度

// 计算阶乘递归Fac的时间复杂度?
long long Fac(size_t N)
{if(0 == N)return 1;for(int i=0;i<N;++i)
{
……
}return Fac(N-1)*N;
}

递归函数,第一次执行了N次循环,第二次执行N-1次循环,以此类推,最后执行N次时结束,所以调用总次数为等差数列,求和N(N+1)/2,时间复杂度是O(N^2)

// 计算斐波那契递归Fib的时间复杂度和空间复杂度
long long Fib(size_t N)
{if(N < 3)return 1;return Fib(N-1) + Fib(N-2);
}

最左侧会逐步减少到Fib(1),有N层,但是右侧未必能走到N层,所以呈现的三角形并不是等腰三角形。但是不影响大O阶表示时间复杂度O(N^2)

时间一去不复返,但是空间是可以重复利用的,新销毁的函数栈帧释放后可以马上被新的函数栈帧替代,重复利用的空间,所以空间复杂度是O(N) 

// 计算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;}
}

嵌套for循环,所以时间复杂度是O(N^2),虽然每次循环都有存在创建i和end变量,但其实使用的都是一块空间,空间一直在被重复利用,所以空间复杂度O(1)

六、二分查找法 

6.1 时间复杂度

// 计算BinarySearch的时间复杂度?
int BinarySearch(int* a, int n, int x)
{assert(a);int begin = 0;int end = n-1;// [begin, end]:begin和end是左闭右闭区间,因此有=号 while (begin <= end){int mid = begin + ((end-begin)>>1);if (a[mid] < x)begin = mid+1;else if (a[mid] > x)end = mid-1;elsereturn mid;}return -1;
}

如上图,空间复杂度是logN

 6.2 效率以及实用性

7、内存、外存、CPU、缓存的一些相关知识

7.1 内存和外存的区别

内存:快、小、8G-16G左右、带电存储

外存:慢、大、500G左右、不带电存储

      cpu只能在内存访问,要想访问外存就得先把数据拿到内存中去,运行速度会比较慢,所以我们平时处理数据都是在内存中处理的,处理之后要存储时才会拿到外存中保存起来,这其实和文件操作很类似,文件也是属于外存,可以永久化地保存数据。

      举个例子:我们打开word写论文,在word还没保存的时候,该数据是存储在内存的缓存中的,如果这个时候突然断电,那么数据在缓存中没有及时保存到外存里,就会造成数据丢失,而如果我们保存在外存里,即使断电也不会出现数据丢失。

7.2 数据结构和数据库

      我们学习数据结构的本质意义,是帮助我们在内存中管理数据,而因为不同的数据结构有不同的特点,对应着不同的需求,所以没有一种数据结构可以完美的解决所有的问题,因此需要学习大量的数据结构类型,根据场景和需要去使用

     而我们在外存中管理数据就是通过数据库、文件。

7.3 CPU、寄存器、三级缓存

一般我们CPU在访问内存数据的时候,需要优先将数据放在寄存器或者三级缓存中。

寄存器是最快的,但是一般只有4-8字节的大小,对于大一点的数据,一般都是加载到缓存中再由cpu进行读取。

缓存命中率:在说明这两个问题之前。我们需要要解一个术语 Cache Line。缓存基本上来说就是把后面的数据加载到离自己近的地方,对于CPU来说,它是不会一个字节一个字节的加载的,因为这非常没有效率,一般来说都是要一块一块的加载(有利于提高缓存命中率)的,对于这样的一块一块的数据单位,术语叫“Cache Line”,一般来说,一个主流的CPU的Cache Line 是 64 Bytes(也有的CPU用32Bytes和128Bytes),64Bytes也就是16个32位的整型,这就是CPU从内存中捞数据上来的最小数据单位。

所以cpu在读取数据的时候,如果在缓存中找到该数据,就可以直接处理,这种情况就是缓存命中率高。而如果在缓存中找不到该数据,那么就需要先从内存中加载到缓存里再读取数据,这种情况就是缓存命中率低。

对于数组而言,由于其连续存放的特点,CPU在访问第一个数据的时候,会顺便把后面的数据加载进缓存,而CPU访问第二个数据的时恰好第二个数据就在缓存,甚至可能第三个、第四个数据都在缓存(取决于cpu的处理数据容量),所以数组(顺序表)的缓存命中率高!而对于链表来说,各个结点直接在物理结构上不存在连续,所以即使cpu加载了后续的空间,大概率也是无用的,所以链表的缓存命中率低。并且无用的数据还挤占了原先缓存区的位置,容易造成缓存污染。

八、顺序表和链表的再总结

顺序表

优点:1、下标随机访问(排序、二分查找)

           2、cpu高速缓存命中率高

缺点:1、指定位置插入和删除元素效率低下

           2、扩容存在效率损失,还可能存在一定的空间浪费

应用场景:适用于高效存储以及频繁访问的场景

链表

优点:1、任意位置插入和删除效率都高

           2、按需申请和释放,不存在空间的浪费

缺点:1、不支持下标的随机访问

           2、cpu告诉缓存命中率低

应用场景:适用于频繁任意位置插入和删除的场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/451781.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PM圆桌派:同事不愿意告诉你的职场套路有哪些?

职场是社会的缩影&#xff0c;想要崭露头角&#xff0c;获得更多升职加薪的机会&#xff0c;就不要做着和多数人一样的事情&#xff0c;却期待着不一样的结果。 职场上有很多潜在的规则&#xff0c;要会做事&#xff0c;也要会说话&#xff0c;更要会做人。如果不懂规则&#…

设置 相关

记录使用过程中做的设置相关事宜。方便后续查询 vscode如何自动生成html格式&#xff1a; vscode快速生成html模板 --两种方法&#xff0c;亲测有用_vscode自动生成html模板-CSDN博客 使用第二个方式。存储html格式后缀。输入&#xff01;&#xff0c;vscode自动补全。 安装…

浪漫的通讯录(顺序表篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

【iOS ARKit】3D 人体姿态估计

与基于屏幕空间的 2D人体姿态估计不同&#xff0c;3D人体姿态估计是尝试还原人体在三维世界中的形状与姿态&#xff0c;包括深度信息。绝大多数的现有3D人体姿态估计方法依赖2D人体姿态估计&#xff0c;通过获取 2D人体姿态后再构建神经网络算法&#xff0c;实现从 2D 到 3D人体…

2024年:用OKR管理你的生活

在科技高速发展的时代&#xff0c;越来越多的企业和团队开始采用OKR&#xff08;Objectives and Key Results&#xff09;管理方法来设定目标并跟踪进度。你是否想过&#xff0c;将OKR理念引入个人生活&#xff0c;以更有效地实现人生目标&#xff1f;本文将探讨如何在2024年运…

聊聊比特币----比特币地址

⽐特币地址是⼀个标识符&#xff08;帐号&#xff09;&#xff0c;包含27-34个字母数字拉丁字符&#xff08;0&#xff0c;O&#xff0c;I除外&#xff09;。地址可以以QR码形式表⽰&#xff0c;是匿名的&#xff0c;不包含关于所有者的信息。 地址⽰例&#xff1a;14qViLJfdG…

【AI绘画】Stable Diffusion 保姆级教程,必收藏!!!

手把手教你入门绘图超强的AI绘画&#xff0c;用户只需要输入一段图片的文字描述&#xff0c;即可生成精美的绘画。给大家带来了全新保姆级教程资料包 &#xff08;文末可获取&#xff09; 2022年绝对是人工智能爆发的元年&#xff0c;前有 stability.ai 开源 Stable Diffusion…

JAVA Web 学习(四)RabbitMQ、Zookeeper

十、消息队列服务器——RabbitMQ RabbitMQ是使用Erlang语言开发的开源消息队列系统&#xff0c;基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、 安全。AMQP协议更多用在企业系统内&#xff0c;对数据一致性、稳定性和可靠性要求…

【边缘服务】

目录 边缘服务是一种新兴的技术趋势边缘服务的应用领域非常广泛边缘服务的核心特点之一是分布式部署另一个核心特点是低延迟边缘服务还可以提供更好的安全性和隐私保护总的来说 边缘服务是一种新兴的技术趋势 它将计算、存储和网络资源推向网络边缘&#xff0c;以实现更低的延…

计算机毕业设计 | springboot 高校新生报到系统(附源码)

1&#xff0c;绪论 1.1 开发背景 学校新生报到仅仅靠原始的手工管理&#xff0c;面对大量的新生信息&#xff0c;无法有效率地将其中的重要部分提取出来&#xff0c;并做出相应的判断和处理。学校的决策只能依据报表数据&#xff0c;在浪费大量人力、物力的同时无法做到实时监…

Unity制作随风摇摆的植物

今天记录一下如何实现随风摇摆的植物&#xff0c;之前项目里面的植物摇摆实现是使用骨骼动画实现的&#xff0c;这种方式太消耗性能&#xff0c;植物这种东西没必要&#xff0c;直接使用顶点动画即可。 准备 植物不需要使用标准的PBR流程&#xff0c;基础的颜色贴图加上法向贴…

VMWare下载安装(包含Window是和Mac)

VMWare下载安装&#xff08;包含Window是和Mac&#xff09; 文章目录 VMWare下载安装&#xff08;包含Window是和Mac&#xff09;一、windows下载VMWare①&#xff1a;下载01&#xff1a;网盘下载02&#xff1a;官方下载 ②&#xff1a;安装③&#xff1a;密钥 二、Mac下载VMWa…