【初阶数据结构】——算法复杂度

一、前言

1、数据结构是什么?

数据结构(Data Structure)是计算机存储、组织数据的⽅式,指相互之间存在⼀种或多种特定关系的数 据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤,所以我们要学各式各样的数据结构, 如:线性表、树、图、哈希等

下面我们就要踏上学习数据结构的旅程了,我们这部分主要是通过C语言来学习初阶数据结构,后续我们学习C++的时候,就会继续高阶数据结构和算法。

2、算法是什么?

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

简单来说,算法就是我们在程序中,为了解决一些问题,使用某些方法,然后让其可以的得到正确的值,那么这个方法就是算法。

比如,我们要求一个数的几次放,那么我们使用一个函数实现,然后调用这个函数,输入一个n就是求n次方,那么其也是一种算法,我们求某些问题,其合适的算法不是唯一的。

那么可以解决问题的算法不是唯一的,那么我们遇到问题的时候,该如何选择合适的算法呢?如何去衡量一个算法的好坏呢?有没有啥标准衡量一个算法呢?

我们今天要学习的内容就是去衡量一个算法的好坏的。

我们通过对这个算法的时间复杂度、空间复杂度来衡量他的好坏。

3、数据结构和算法的重要性

我们前面学习C语言的时候,就经常听到数据结构和算法,还有我们看的这么多的竞赛基本都是对于数据结构和算法的竞赛,我们去看招聘网站看到的相关的工作也都基本上对于算法的熟练程度都是有要求的,然后再招聘的笔试和面试中也都是必考的项目,可想而知其的重要性。

如下:

那么我们要学好数据结构和算法有没有什么秘诀呢?要学好数据结构和算法的秘诀就是:1、死磕代码 2、画图+思考,做到这两点想不学会都难,前面我们学习C语言的时候,画图的好处其实已经显现出来了。

二、算法效率

如何衡量一个算法的好坏呢?

1、复杂度的概念

法在编写成可执⾏程序后,运⾏时需要耗费时间资源和空间(内存)资源。因此衡量⼀个算法的好坏,⼀般是从时间和空间两个维度来衡量的,即时间复杂度和空间复杂度

时间复杂度主要衡量⼀个算法的运⾏快慢,⽽空间复杂度主要衡量⼀个算法运⾏所需要的额外空间。在计算机发展的早期,计算机的存储容量很⼩。所以对空间复杂度很是在乎。但是经过计算机⾏业的迅速发展,计算机的存储容量已经达到了很⾼的程度。所以我们如今已经不需要再特别关注⼀个算法的空间复杂度。

三、时间复杂度

在计算机科学中,算法的时间复杂度是一个函数式T(N),它定量的描述了这个算法的运算时间,时间复杂度是衡量一个算法的时间效率,那么有同学就会问了,那么我们为啥不直接去算一个程序的运行时间呢?

1、程序的运行时间运行机器的配置都是有关系的,比如一个硬件好的机器和一个硬件一般的机器,其机器的算力就不一样了,那么其运行时间肯定就不一样的。

2、程序的运行时间和编译的环境也有关系,对于同一个算法程序,用一个老版本的编译器和一个新的编译器在同一台机器下的运行时间也是可能不同的。

3、程序的运行时间只能在程序写好后运行才好测试,没办法咋事前就通过理论进行计算。

4、同一个程序在同一台机器上的每一次的运行时间都会有差异。

所以我们算法的时间复杂度都是通过一个函数式T(N)来衡量的。

那么这个函数式T(N)到底是什么呢?

那么算法的时间这个T(N)函数式计算了程序的执⾏次数。通过c语⾔编译链接章节学习,我们知道算法程序被编译后⽣成⼆进制指令,程序运⾏,就是cpu执⾏这些编译好的指令。那么我们通过程序代码或者理论思想计算出程序的执⾏次数的函数式T(N),假设每句指令执⾏时间基本⼀样(实际中有差别,但是微乎其微),那么执⾏次数和运⾏时间就是等⽐正相关,这样也脱离了具体的编译运⾏环境。执⾏次数就可以代表程序时间效率的优劣。⽐如解决⼀个问题的算法a程序T(N)=N,算法b程序T(N)=N^2,那么算法a的效率⼀定优于算法b。

下面我们通过一个例子来学习:

我们不需要知道这个函数是实现啥功能的,我们只需要求++count语句执行了多少次:

首先是第一个二层循环,其执行的次数为N^2。然后就是第二个for循环,其执行的次数为2*N。第三个循环就执行了10次,那么总的执行次数为:T(N)=N^2+2*N+10

那么通过我们之前的数学的学习,当N达到很大的时候,只有N^2对于执行次数的影响是最大的,实际我们在计算时间复杂度的时候,计算的也不是程序的执行次数。

我们想知道的是输入N对于程序的执行次数的增长趋势的变化的影响,也就是当N变化的时候T(N)的变化咋样。

我们在对复杂度的表示通常使用大O渐进表示法

四、大O渐进表示法

大O符合:是用于描述函数渐进行为的数学符号,使用大O渐进表示法后,我们不需要再将程序的执行次数很精确的计算出来,主要是推算出其影响最大的即可。

下面为大O渐进表示法的规则:

1、时间复杂度函数式T(N)中,其只保留高阶项,去掉那些低阶项,这是因为当N不断变大的时候,低阶项对于结果的影响基本可以忽略不计的了。

如上面的那个案例,其时间复杂度为T(N)=N^2+2*N+10,那么我们使用大O渐进表示法,那么就为:O(N^2)。

2、如果最高阶项存在而且不是1,那么则除去这个项目的常数系数,这是因为当N不断增大的时候,这个系数对于结果的影响也是微乎其微的了,那么也就可以忽略不记了。

3、T(N)中如果只含有常数项,那么我们用常数1取代其所有的加法项,不过要注意的是1并不是代表其次数为1,而是表示其输入N对于时间复杂度的影响趋势是1,也就是一条平行于X轴的直线,即没有影响。

4、如果这个程序的算法的时间复杂度其会有多种情况,即其有最好的情况,平均情况,最坏的情况,那么我们就以最坏的情况为最后的结果。

五、空间复杂度

上面我们已经学习了时间复杂度,那么我们再学习另外一个衡量算法效率的东西:空间复杂度

空间复杂度也是一个函数表达式,其是对一个算法再运行过程中需要临时开辟的空间。

有的同学可能会以为其是开辟的空间的字节数,其实不是,这是以为每个变量的大小差异不是很大,我们所学的数据类型,就是1个字节,2个字节,4 个字节,8个字节等,其差异不是很大。所以空间复杂度算的是我们需要创建的变量个数。

空间复杂度的表达方式也是使用大O表示法,那么其使用规则也是一样的。

不过要注意的是:

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

六、练习

1、时间复杂度的练习

练习1:

上面的时间复杂度就很好计算了,我们首先求出其函数表达式T(N),然后再使用大O表示法。

那么我们现在开始计算吧:首先第一个循环,其执行的次数是2*N,然后是第二个循环其执行次数为10,那么其函数表达式T(N)=2*N+10,然后我们使用大O表达法,先保留最高阶次项,那么此时为2N,然后最高次的系数改为1,那么最终其大O表示法为O(N)。

练习2:

那么我们还是一样先求其函数表达式,上面的函数表达式很明显为:T(N)=1000。那么其和N 没有关系,那么其很明显使用大O表示法的话就需要使用到第三条规则,将其变成1表示:O(1)。

练习3:

可以看到这个时间复杂度和我们上面的计算有点不一样了,我们对N进行取值看看其规律:

当为2的时候,代码的执行次数为1,当N为4的时候,那么代码的执行次数为2,当N为8的时候,代码执行3次......那么我们假设代码的执行次数为x,然后我们可以得到2^x=N。那么我们可以得到x=log N 其中这个对数的底为2,那么我们这个程序的时间复杂度就是O(log N)了。

当我们遇到对数的时间复杂度的时候我们会发现,底数对于复杂度的变化趋势影响不大,那么我们可以不写这个底数,不同的教材间的写法也会有差异,但是区别不大,我们的话建议使用log N的形式。

练习4:

上面的代码是我们前面学习C语言的时候学习的冒泡排序,那么我们来分析其时间复杂度看看:

首先我们看看其两个循环,外层循环:end从n递减到1,那么一个n次迭代。

然后是内层循环遍历整个数组,比较相邻的两个元素,如果前面的大于后面的元素,那么就进行交换。

那么其时间复杂度会受到数组的排序受到影响:

1、最坏情况:完全倒置。

那么外层循环:还是一样要执行n次。

内层循环:

第一轮:n-1次比较,第二轮:n-2次比较.....第n-1轮:1次比较。

那么总的操作次数为:

(n-1)+(n-2)+(n-3)+(n-4)......+1=n(n-1)/2

那么此时的时间复杂度为O(n^2)。

最好的情况:(已经是降序)

那么外层循环执行1次。

然后内层循环遍历那么就执行n-1次比较。

那么就直接终止了所以其时间复杂度为O(n)。

平均情况:(顺序是随机的)

那么平均需要n(n-1)/4次,那么其时间复杂度为O(n^2)。

2、空间复杂度的练习

这里的的Fac函数很明显使用了递归,而且其每运行一次就要创建一个函数栈帧,然后其不继续递归的条件就是函数的参数变成0,那么就是当N减到0的时候,那么这个函数的空间复杂度为N。大O表达式为O(N)。

七、常见的复杂度的对比

相关推荐
张立龙66642 分钟前
有序二叉树各种操作实现(数据结构C语言多文件编写)
c语言·开发语言·数据结构
Y.O.U..1 小时前
力扣HOT100——560.和为k的子数组
数据结构·c++·算法·leetcode
wuqingshun3141591 小时前
经典算法 判断一个图中是否有环
java·开发语言·数据结构·c++·算法·蓝桥杯·深度优先
柃歌1 小时前
【LeetCode Solutions】LeetCode 160 ~ 165 题解
数据结构·算法·leetcode
爱编码的傅同学1 小时前
数据结构(六)——红黑树及模拟实现
数据结构·算法
小黑屋的黑小子1 小时前
【数据结构】HashMap源码 —— 简单介绍
数据结构·算法·面试·源码·hashmap
猛踹瘸子那条好腿の2 小时前
使用Form.List且有Select组件
javascript·数据结构·antd
灋✘逞_兇3 小时前
快速幂+公共父节点
数据结构·c++·算法·leetcode
十五年专注C++开发4 小时前
面试题:请描述一下你在项目中是如何进行性能优化的?针对哪些方面进行了优化,采取了哪些具体的措施?
开发语言·数据结构·c++·qt·设计模式·性能优化
wuqingshun3141595 小时前
经典算法 判断一个图是不是树
数据结构·c++·算法·蓝桥杯·深度优先